#!/usr/bin/env python # This script is intended to read an Aeolus/CloudForms page, parse the output, # find deployment names matching a pattern, then extract the MAC Address # from that deployment's page, then run arp-scan (with sudo if possible) to find the # matching IP for that deployment. The output is a list of deployments # and their data. # It need to be in the cgi-bin directory # This version requires /usr/sbin/arp-scan to be setuid (sorry, sudo didn't work from cgi-bin, I had it working with sudo before, and will add that functionality back in the future) _author_ = 'Vinny Valdez ' _version_ = '0.7' # This software is licensed to you under the GNU General Public License, # version 2 (GPLv2). There is NO WARRANTY for this software, express or # implied, including the implied warranties of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 # along with this software; if not, see # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. # # Red Hat trademarks are not licensed under GPLv2. No permission is # granted to use or replicate Red Hat trademarks that are incorporated # in this software or its documentation. from BeautifulSoup import * # doing this kind of import brings in the classes such as Tag needed for comparison later import urllib, subprocess, sys import cgi, cgitb, socket, time, os cgitb.enable() ####################################################################### # User variables webapp_url = "/conductor/deployments" # The following is to prevent some of the header names from being matched as a name # This script will break if a deployment has a name containing one of these ignore_names = [ '', 'Default', 'Deployment name', 'Deployed on', 'Instances', 'Pool', 'Pool Family', 'Owner' ] ####################################################################### # TODO: replace with opts parser - for now, using cgi form #pattern = "usecase2" # Name of deployment must match this, change as needed #username = "admin" #password = "password" #network = "--localnet" # by default scan the localnet with arp-scan #server = "cf-cloudforms5" def htmlHeader(): '''Build HTML data header''' print "Content-Type: text/html" print print """ CloudForms Deployed Instance Report """ def gen_form(): print "
" # select box - this could easily be a straight input box instead, but made it easy for my lab print "Choose CloudForms server: " print "" print "
" # standard single line text field print "CloudForms username: " print "" print "
" print "CloudForms password: " print "" print "
" print "Instance Network (man arp-scan for valid parameters. default: --localnet): " print "" print "
" print "Pattern Search: " print "" print "
" print "" print "
" def htmlCloser(): '''Close out HTML tags''' print """ """ def find_instances(instances): '''Converts CloudForms Deployments page table into a list of deployments matching a pattern''' try: soup_main.findAll('tr') for i in soup_main.table: if isinstance(i, Tag): for j in i.contents: if isinstance(j, Tag): for k in j.contents: if isinstance(k, Tag): instance_name = k.string if instance_name: if pattern in instance_name: print "

-Found matching instance: %s

" % instance_name for l, link in enumerate(j.findAll('a')): instance_url = link['href'] instances[str(instance_name)] = { 'url': str(instance_url) } #Initializing new key else: if not instance_name.strip('\n') in ignore_names: # Hopefully a deployment doesn't contain this print "

-Discarding unmatched instance: %s

" % instance_name return instances except: raise def find_instance_mac(instances): '''Find MAC Address for each instance from collected Deployments and stores it as a key:value pair of "mac" : "ADDR"''' try: for instance in instances: mac_found = False print "

Grabbing %s's MAC Address from: %s

" % (instance, instances[instance]['url']) url = "https://%s:%s@%s" % (username, password, server+instances[instance]['url']) html = urllib.urlopen(url).read() # read in the instance url soup = BeautifulSoup(html) soup.findAll('li') # MAC is listed in a list for m in soup.li: if isinstance(m, Tag): for n in m.contents: if isinstance(n, Tag): chomped = n.string.split('\n') if "IP Address:" in chomped[1]: mac_addr = chomped[2] instances[instance]['mac'] = str(mac_addr) mac_found = True elif "State:" in chomped[1]: state = chomped[2] instances[instance]['state'] = str(state) if not mac_found: instances[instance]['mac'] = 'NOT FOUND' return instances except: raise def arp_scan(): try: print "

Scanning network ...

" # TODO: since this will run from CGI, arp-scan needs to be suid with chmod +s. THIS IS NOT SAFE, # however, because of the nature of what we have to do here, this seems to be the best way to # get the IP from a MAC address. CGI was not able to run sudo without a tty # TODO: Investigate arp calls directly from python #scan = subprocess.Popen(["sudo", "arp-scan", network], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) scan = subprocess.Popen(["arp-scan", network], shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) scan.wait() except: raise output, errors = scan.communicate() if not errors == '': print "

%s

" % errors return False elif "arp-scan: command not found" in output: print "

%s

" % output return False else: return output def finalizeData(instances): for instance in instances: found = False for host in ips: if not 'running' in instances[instance]['state']: instances[instance]['mac'] = 'UNAVAILABLE' instances[instance]['ip'] = 'UNAVAILABLE' instances[instance]['webserver'] = 'UNAVAILABLE' else: if instances[instance]['mac'] in host: ip = host.split('\t')[0] print "

-Found match, adding: %s for %s

" % (ip, instance) instances[instance]['ip'] = str(ip) instances[instance]['webserver'] = 'Dynamic Report' % ip found = True if not found: instances[instance]['ip'] = 'NOT FOUND ON %s' % network instances[instance]['webserver'] = 'UNAVAILABLE' def printTables(instances): if instances: print "

The following instances have been found:

" # table for instance in instances: print '' print '' % (instance) print '' % (instances[instance]['state']) print '' % (instances[instance]['url']) print '' % (instances[instance]['mac']) print '' % (instances[instance]['ip']) print '' % (instances[instance]['webserver']) print '
Deployment Name%s
State%s
Conductor URL%s
MAC Address%s
IP Address%s
Web Server%s
' print '
' #main htmlHeader() form = cgi.FieldStorage() # Check if form values were filled out, else keep asking if (form.has_key('server') and form.has_key('username') and form.has_key('password') and form.has_key('network') and form.has_key('pattern')): for field_name in form: field=form[field_name] if field.name == "username": username = str(field.value) elif field.name == "password": password = str(field.value) elif field.name == "server": server = str(field.value) elif field.name == "network": network = str(field.value) elif field.name == "pattern": pattern = str(field.value) print '

Script source: get_instances.py

' # Construct URL url = "https://%s:%s@%s" % (username, password, server+webapp_url) # Read in web page and instantiate a BeautifulSoup object html_main = urllib.urlopen(url).read() soup_main = BeautifulSoup(html_main) instances = {} # Find matching instances instances = find_instances(instances) # Extract each instance's MAC Address instances = find_instance_mac(instances) if not instances: print "

No matching instances found.

" htmlCloser() sys.exit(0) # Get IP list for any matching instances try: ips = arp_scan() except: raise # Verify list is valid if ips: ips = ips.split('\n') else: print "

FATAL ERROR: 'arp-scan' must be installed, and set uid with chmod +s.

" htmlCloser() sys.exit(1) # Ensure all data is valid finalizeData(instances) # Print known data printTables(instances) else: # This means not all data has been filled in gen_form() htmlCloser()