def _get_ch_params(): # Initialise variables when required from core.config import FullConfParser fcp = FullConfParser() username = fcp.get("auth.conf").get("certificates").get("username") ch_host = fcp.get("auth.conf").get("clearinghouse").get("host") ch_port = fcp.get("auth.conf").get("clearinghouse").get("port") ch_end = fcp.get("auth.conf").get("clearinghouse").get("endpoint") return (username, ch_host, ch_port, ch_end)
def getusercred(geni_api=3): """Retrieve your user credential. Useful for debugging. If you specify the -o option, the credential is saved to a file. If you specify --usercredfile: First, it tries to read the user cred from that file. Second, it saves the user cred to a file by that name (but with the appropriate extension) Otherwise, the filename is <username>-<framework nickname from config file>-usercred.[xml or json, depending on AM API version]. If you specify the --prefix option then that string starts the filename. If instead of the -o option, you supply the --tostdout option, then the usercred is printed to STDOUT. Otherwise the usercred is logged. The usercred is returned for use by calling scripts. e.g.: Get user credential, save to a file: omni.py -o getusercred Get user credential, save to a file with filename prefix mystuff: omni.py -o -p mystuff getusercred """ from core.config import FullConfParser fcp = FullConfParser() username = fcp.get("auth.conf").get("certificates").get("username") creds_path = os.path.normpath( os.path.join(os.path.dirname(__file__), "../../..", "cert")) cert_path = os.path.join(creds_path, "%s-cert.pem" % username) # Retrieve new credential by contacting with GCF CH try: user_cert = open(cert_path, "r").read() cred = ch_call("CreateUserCredential", params=[user_cert]) # Exception? -> Retrieve already existing credential from disk (CBAS) except: cred_path = os.path.join(creds_path, "%s-cred.xml" % username) cred = open(cred_path).read() if geni_api >= 3: if cred: cred = credentials.wrap_cred(cred) credxml = credentials.get_cred_xml(cred) # pull the username out of the cred # <owner_urn>urn:publicid:IDN+geni:gpo:gcf+user+alice</owner_urn> user = "" usermatch = re.search( r"\<owner_urn>urn:publicid:IDN\+.+\+user\+(\w+)\<\/owner_urn\>", credxml) if usermatch: user = usermatch.group(1) return ("Retrieved %s user credential" % user, cred)
class XMLRPCDispatcher(object): """ Please see documentation in FlaskXMLRPC. """ def __init__(self, log): self._log = log self.__load_config() def __load_config(self): self.config = FullConfParser() self.policies_category = self.config.get("policies.conf") self.general_section = self.policies_category.get("general") self.policies_enabled = ast.literal_eval( self.general_section.get("enabled")) def requestCertificate(self): """ Retrieve the certificate which the client has sent. """ # Get Cert from the request's environment if "CLIENT_RAW_CERT" in request.environ: return request.environ["CLIENT_RAW_CERT"] if "SSL_CLIENT_CERT" in request.environ: return request.environ["SSL_CLIENT_CERT"] return None def _dispatch(self, method, params): self._log.info("Called: <%s>" % (method)) try: meth = getattr(self, "%s" % (method)) except AttributeError, e: self._log.warning("Client called unknown method: <%s>" % (method)) raise e # Apply policies if self.policies_enabled: print "XMLRPCDispatcher > method=", method, ", params=", params print "XMLRPCDispatcher > Policies enabled..." from policies.manager import PolicyManager print "XMLRPCDispatcher > Evaluating policies..." PolicyManager.evaluate_rules() try: return meth(*params) except Exception, e: # TODO check if the exception has already been logged self._log.exception("Call to known method <%s> failed!" % (method)) raise e
def getusercred(geni_api = 3): """Retrieve your user credential. Useful for debugging. If you specify the -o option, the credential is saved to a file. If you specify --usercredfile: First, it tries to read the user cred from that file. Second, it saves the user cred to a file by that name (but with the appropriate extension) Otherwise, the filename is <username>-<framework nickname from config file>-usercred.[xml or json, depending on AM API version]. If you specify the --prefix option then that string starts the filename. If instead of the -o option, you supply the --tostdout option, then the usercred is printed to STDOUT. Otherwise the usercred is logged. The usercred is returned for use by calling scripts. e.g.: Get user credential, save to a file: omni.py -o getusercred Get user credential, save to a file with filename prefix mystuff: omni.py -o -p mystuff getusercred """ from core.config import FullConfParser fcp = FullConfParser() username = fcp.get("auth.conf").get("certificates").get("username") creds_path = os.path.normpath(os.path.join(os.path.dirname(__file__), "../../..", "cert")) cert_path = os.path.join(creds_path, "%s-cert.pem" % username) # Retrieve new credential by contacting with GCF CH try: user_cert = open(cert_path, "r").read() cred = ch_call("CreateUserCredential", params = [user_cert]) # Exception? -> Retrieve already existing credential from disk (CBAS) except: cred_path = os.path.join(creds_path, "%s-cred.xml" % username) cred = open(cred_path).read() if geni_api >= 3: if cred: cred = credentials.wrap_cred(cred) credxml = credentials.get_cred_xml(cred) # pull the username out of the cred # <owner_urn>urn:publicid:IDN+geni:gpo:gcf+user+alice</owner_urn> user = "" usermatch = re.search(r"\<owner_urn>urn:publicid:IDN\+.+\+user\+(\w+)\<\/owner_urn\>", credxml) if usermatch: user = usermatch.group(1) return ("Retrieved %s user credential" % user, cred)
class GENIv3DelegateBase(object): """ Please find more information about the concept of Handlers and Delegates via the wiki (e.g. https://github.com/motine/AMsoil/wiki/GENI). The GENIv3 handler (see above) assumes that this class uses RSpec version 3 when interacting with the client. For creating new a new RSpec type/extension, please see the wiki via https://github.com/motine/AMsoil/wiki/RSpec. General parameters for all following methods: {client_cert} The client's certificate. See [flaskrpcs]XMLRPCDispatcher.requestCertificate(). Also see http://groups.geni.net/geni/wiki/GeniApiCertificates {credentials} The a list of credentials in the format specified at http:// groups.geni.net/geni/wiki/GAPI_AM_API_V3/CommonConcepts#credentials Dates are converted to UTC and then made timezone-unaware (see http:// docs.python.org/2/library/datetime.html#datetime.datetime.astimezone). """ ALLOCATION_STATE_UNALLOCATED = "geni_unallocated" """The sliver does not exist. (see http://groups.geni.net/geni/wiki/ GAPI_AM_API_V3/CommonConcepts#SliverAllocationStates)""" ALLOCATION_STATE_ALLOCATED = "geni_allocated" """The sliver is offered/promissed, but it does not consume actual resources. This state shall time out at some point in time.""" ALLOCATION_STATE_PROVISIONED = "geni_provisioned" """The sliver is/has been instanciated. Operational states apply here.""" OPERATIONAL_STATE_PENDING_ALLOCATION = "geni_pending_allocation" """Required for aggregates to support. A transient state.""" OPERATIONAL_STATE_NOTREADY = "geni_notready" """Optional. A stable state.""" OPERATIONAL_STATE_CONFIGURING = "geni_configuring" """Optional. A transient state.""" OPERATIONAL_STATE_STOPPING = "geni_stopping" """Optional. A transient state.""" OPERATIONAL_STATE_READY = "geni_ready" """Optional. A stable state.""" OPERATIONAL_STATE_READY_BUSY = "geni_ready_busy" """Optional. A transient state.""" OPERATIONAL_STATE_FAILED = "geni_failed" """Optional. A stable state.""" OPERATIONAL_ACTION_START = "geni_start" """Sliver shall become geni_ready. The AM developer may define more states (see http://groups.geni.net/geni/wiki/GAPI_AM_API_V3/ CommonConcepts#SliverOperationalActions)""" OPERATIONAL_ACTION_RESTART = "geni_restart" """Sliver shall become geni_ready again.""" OPERATIONAL_ACTION_STOP = "geni_stop" """Sliver shall become geni_notready.""" OPERATIONAL_ACTION_UPDATE_USERS = "geni_update_users" OPERATIONAL_ACTION_UPDATING_USERS_CANCEL = "geni_updating_users_cancel" """ Following operational actions not fully implemented. """ OPERATIONAL_ACTION_CONSOLE_URL = "geni_console_url" OPERATIONAL_ACTION_SHARELAN = "geni_sharelan" OPERATIONAL_ACTION_UNSHARELAN = "geni_unsharelan" def __init__(self): super(GENIv3DelegateBase, self).__init__() self.config = FullConfParser() self.general_section = self.config.get("geniv3.conf").get("general") self.certificates_section = self.config.get("auth.conf").get("certificates") def get_request_extensions_list(self): """Not to overwrite by AM developer. Should retrun a list of request extensions (XSD schemas) to be sent back by GetVersion.""" return [uri for prefix, uri in self.get_request_extensions_mapping().items()] def get_request_extensions_mapping(self): """Overwrite by AM developer. Should return a dict of namespace names and request extensions (XSD schema's URLs as string). Format: {xml_namespace_prefix : namespace_uri, ...} """ return {} def get_manifest_extensions_mapping(self): """Overwrite by AM developer. Should return a dict of namespace names and manifest extensions (XSD schema's URLs as string). Format: {xml_namespace_prefix : namespace_uri, ...} """ return {} def get_ad_extensions_list(self): """Not to overwrite by AM developer. Should retrun a list of request extensions (XSD schemas) to be sent back by GetVersion.""" return [uri for prefix, uri in self.get_ad_extensions_mapping().items()] def get_ad_extensions_mapping(self): """Overwrite by AM developer. Should return a dict of namespace names and advertisement extensions (XSD schema URLs as string) to be sent back by GetVersion. Format: {xml_namespace_prefix : namespace_uri, ...} """ return {} def is_single_allocation(self): """Overwrite by AM developer. Shall return a True or False. When True (not default), and performing one of (Describe, Allocate, Renew, Provision, Delete), such an AM requires you to include either the slice urn or the urn of all the slivers in the same state. see http://groups.geni.net/geni/wiki/GAPI_AM_API_V3/ CommonConcepts#OperationsonIndividualSlivers""" return False def get_allocation_mode(self): """Overwrite by AM developer. Shall return a either 'geni_single', 'geni_disjoint', 'geni_many'. It defines whether this AM allows adding slivers to slices at an AM (i.e. calling Allocate multiple times, without first deleting the allocated slivers). For description of the options see http://groups.geni.net/geni/wiki/ GAPI_AM_API_V3/CommonConcepts#OperationsonIndividualSlivers""" return "geni_single" def list_resources(self, client_cert, credentials, geni_available, inner_call=False): """Overwrite by AM developer. Shall return an RSpec version 3 (advertisement) or raise an GENIv3...Error. If {geni_available} is set, only return availabe resources. For full description see http://groups.geni.net/geni/wiki/ GAPI_AM_API_V3#ListResources""" raise exceptions.GENIv3GeneralError("Method not implemented yet") def describe(self, urns, client_cert, credentials): """Overwrite by AM developer. Shall return an RSpec version 3 (manifest) or raise an GENIv3...Error. {urns} contains a list of slice identifiers (e.g. ['urn:publicid:IDN+ofelia:eict:gcf+slice+myslice']). For more information on possible {urns} see http://groups.geni.net/ geni/wiki/GAPI_AM_API_V3/CommonConcepts#urns For full description see http://groups.geni.net/geni/wiki/ GAPI_AM_API_V3#Describe""" raise exceptions.GENIv3GeneralError("Method not implemented yet") def allocate(self, slice_urn, client_cert, credentials, rspec, end_time=None): """Overwrite by AM developer. Shall return the two following values or raise an GENIv3...Error. - a RSpec version 3 (manifest) of newly allocated slivers - a list of slivers of the format: [{'geni_sliver_urn' : String, 'exceptionspires' : Python-Date, 'geni_allocation_status' : one of the ALLOCATION_STATE_xxx}, ...] Please return like so: "return respecs, slivers" {slice_urn} contains a slice identifier (e.g. 'urn:publicid:IDN+ofelia:eict:gcf+slice+myslice'). {end_time} Optional. A python datetime object which determines the desired expiry date of this allocation (see http://groups.geni.net/ geni/wiki/GAPI_AM_API_V3/CommonConcepts#geni_end_time). >>> This is the first part of what CreateSliver used to do in previous versions of the AM API. The second part is now done by Provision, and the final part is done by PerformOperationalAction. For full description see http://groups.geni.net/geni/wiki/ GAPI_AM_API_V3#Allocate""" raise exceptions.GENIv3GeneralError("Method not implemented yet") def renew(self, urns, client_cert, credentials, expiration_time, best_effort): """Overwrite by AM developer. Shall return a list of slivers of the following format or raise a GENIv3...Error: [{'geni_sliver_urn' : String, 'geni_allocation_status' : one of the ALLOCATION_STATE_xxx, 'geni_operational_status' : one of the OPERATIONAL_STATE_xxx, 'exceptionspires' : Python-Date, 'geni_error' : optional String}, ...] {urns} contains a list of slice identifiers (e.g. ['urn:publicid:IDN+ofelia:eict:gcf+slice+myslice']). {expiration_time} is a python datetime object {best_effort} determines if the method shall fail in case that not all of the urns can be renewed (best_effort=False). If the transactional behaviour of {best_effort}=False can not be provided, throw a GENIv3OperationUnsupportedError. For more information on possible {urns} see http://groups.geni.net/ geni/wiki/GAPI_AM_API_V3/CommonConcepts#urns For full description see http://groups.geni.net/geni/wiki/ GAPI_AM_API_V3#Renew""" raise exceptions.GENIv3GeneralError("Method not implemented yet") def provision(self, urns, client_cert, credentials, best_effort, end_time, geni_users): """Overwrite by AM developer. Shall return the two following values or raise an GENIv3...Error. - a RSpec version 3 (manifest) of slivers - a list of slivers of the format: [{'geni_sliver_urn' : String, 'geni_allocation_status' : one of the ALLOCATION_STATE_xxx, 'geni_operational_status' : one of the OPERATIONAL_STATE_xxx, 'exceptionspires' : Python-Date, 'geni_error' : optional String}, ...] Please return like so: "return respecs, slivers" {urns} contains a list of slice/resource identifiers (e.g. ['urn:publicid:IDN+ofelia:eict:gcf+slice+myslice']). {best_effort} determines if the method shall fail in case that not all of the urns can be provisioned (best_effort=False) {end_time} Optional. A python datetime object which determines the desired expiry date of this provision (see http://groups.geni.net/ geni/wiki/GAPI_AM_API_V3/CommonConcepts#geni_end_time). {geni_users} is a list of the format: [ { 'urn' : ..., 'keys' : [sshkey, ...]}, ...] If the transactional behaviour of {best_effort}=False can not be provided, throw a GENIv3OperationUnsupportedError. For more information on possible {urns} see http://groups.geni.net/ geni/wiki/GAPI_AM_API_V3/CommonConcepts#urns For full description see http://groups.geni.net/geni/wiki/ GAPI_AM_API_V3#Provision""" raise exceptions.GENIv3GeneralError("Method not implemented yet") def status(self, urns, client_cert, credentials): """Overwrite by AM developer. Shall return the two following values or raise an GENIv3...Error. - a slice urn - a list of slivers of the format: [{'geni_sliver_urn' : String, 'geni_allocation_status' : one of the ALLOCATION_STATE_xxx, 'geni_operational_status' : one of the OPERATIONAL_STATE_xxx, 'exceptionspires' : Python-Date, 'geni_error' : optional String}, ...] Please return like so: "return slice_urn, slivers" {urns} contains a list of slice/resource identifiers (e.g. ['urn:publicid:IDN+ofelia:eict:gcf+slice+myslice']). For more information on possible {urns} see http://groups.geni.net/ geni/wiki/GAPI_AM_API_V3/CommonConcepts#urns For full description see http://groups.geni.net/geni/wiki/ GAPI_AM_API_V3#Status""" raise exceptions.GENIv3GeneralError("Method not implemented yet") def perform_operational_action(self, urns, client_cert, credentials, action, best_effort): """Overwrite by AM developer. Shall return a list of slivers of the following format or raise an GENIv3...Error: [{'geni_sliver_urn' : String, 'geni_allocation_status' : one of the ALLOCATION_STATE_xxx, 'geni_operational_status' : one of the OPERATIONAL_STATE_xxx, 'exceptionspires' : Python-Date, 'geni_error' : optional String}, ...] {urns} contains a list of slice or sliver identifiers (e.g. ['urn:publicid:IDN+ofelia:eict:gcf+slice+myslice']). {action} an arbitraty string, but the following should be possible: "geni_start", "geni_stop", "geni_restart" {best_effort} determines if the method shall fail in case that not all of the urns can be changed (best_effort=False) If the transactional behaviour of {best_effort}=False can not be provided, throw a GENIv3OperationUnsupportedError. For more information on possible {urns} see http://groups.geni.net/ geni/wiki/GAPI_AM_API_V3/CommonConcepts#urns For full description see http://groups.geni.net/geni/wiki/ GAPI_AM_API_V3#PerformOperationalAction""" raise exceptions.GENIv3GeneralError("Method not implemented yet") def delete(self, urns, client_cert, credentials, best_effort): """Overwrite by AM developer. Shall return a list of slivers of the following format or raise an GENIv3...Error: [{'geni_sliver_urn' : String, 'geni_allocation_status' : one of the ALLOCATION_STATE_xxx, 'exceptionspires' : Python-Date, 'geni_error' : optional String}, ...] {urns} contains a list of slice/resource identifiers (e.g. ['urn:publicid:IDN+ofelia:eict:gcf+slice+myslice']). {best_effort} determines if the method shall fail in case that not all of the urns can be deleted (best_effort=False) If the transactional behaviour of {best_effort}=False can not be provided, throw a GENIv3OperationUnsupportedError. For more information on possible {urns} see http://groups.geni.net/ geni/wiki/GAPI_AM_API_V3/CommonConcepts#urns For full description see http://groups.geni.net/geni/wiki/ GAPI_AM_API_V3#Delete""" raise exceptions.GENIv3GeneralError("Method not implemented yet") def shutdown(self, slice_urn, client_cert, credentials): """Overwrite by AM developer. Shall return True or False or raise an GENIv3...Error. For full description see http://groups.geni.net/geni/wiki/ GAPI_AM_API_V3#Shutdown""" raise exceptions.GENIv3GeneralError("Method not implemented yet") def auth(self, client_cert, credentials, slice_urn=None, privileges=()): """ This method authenticates and authorizes. It returns the client's urn, uuid, email (extracted from the {client_cert}). Example call: "urn, uuid, email = self.auth(...)" Be aware, the email is not required in the certificate, hence it might be empty. If the validation fails, an GENIv3ForbiddenError is thrown. The credentials are checked so the user has all the required privileges (success if any credential fits all privileges). The client certificate is not checked: this is usually done via the webserver configuration. This method only treats certificates of type 'geni_sfa'. Here a list of possible privileges (format: right_in_credential: [privilege1, privilege2, ...]): "authority" : ["register", "remove", "update", "resolve", "list", "getcredential", "*"], "refresh" : ["remove", "update"], "resolve" : ["resolve", "list", "getcredential"], "sa" : ["getticket", "redeemslice", "redeemticket", "createslice", "createsliver", "deleteslice", "deletesliver", "updateslice", "getsliceresources", "getticket", "loanresources", "stopslice", "startslice", "renewsliver", "deleteslice", "deletesliver", "resetslice", "listslices", "listnodes", "getpolicy", "sliverstatus"], "embed" : ["getticket", "redeemslice", "redeemticket", "createslice", "createsliver", "renewsliver", "deleteslice", "deletesliver", "updateslice", "sliverstatus", "getsliceresources", "shutdown"], "bind" : ["getticket", "loanresources", "redeemticket"], "control" : ["updateslice", "createslice", "createsliver", "renewsliver", "sliverstatus", "stopslice", "startslice", "deleteslice", "deletesliver", "resetslice", "getsliceresources", "getgids"], "info" : ["listslices", "listnodes", "getpolicy"], "ma" : ["setbootstate", "getbootstate", "reboot", "getgids", "gettrustedcerts"], "operator" : ["gettrustedcerts", "getgids"], "*" : ["createsliver", "deletesliver", "sliverstatus", "renewsliver", "shutdown"] When using the gcf clearinghouse implementation the credentials will have the rights: - user: "******", "resolve", "info" (which resolves to the privileges: "remove", "update", "resolve", "list", "getcredential", "listslices", "listnodes", "getpolicy"). - slice: "refresh", "embed", "bind", "control", "info" (well, do the resolving yourself...) """ # check variables if not isinstance(privileges, tuple): raise TypeError("Privileges need to be a tuple.") # collect credentials (only GENI certs, version ignored) geni_credentials = [] for c in credentials: if c["geni_type"] == "geni_sfa": geni_credentials.append(c["geni_value"]) # Get the cert_root from the configuration settings root_path = os.path.normpath(os.path.join(os.path.dirname(__file__), "../../../../")) cert_root = os.path.join(root_path, self.certificates_section.get("cert_root")) logger.debug( "client_certificate trusted, present \ at: %s" % str(cert_root) ) logger.debug("client_certificate:\n%s" % str(client_cert)) if client_cert is None: raise exceptions.GENIv3ForbiddenError( "Could not determine \ the client SSL certificate" ) # test the credential try: cred_verifier = extensions.geni.util.cred_util.CredentialVerifier(cert_root) cred_verifier.verify_from_strings(client_cert, geni_credentials, slice_urn, privileges) except Exception as e: raise exceptions.GENIv3ForbiddenError(str(e)) user_gid = extensions.sfa.trust.gid.GID(string=client_cert) user_urn = user_gid.get_urn() user_uuid = user_gid.get_uuid() user_email = user_gid.get_email() return user_urn, user_uuid, user_email # TODO document return def urn_type(self, urn): """ Returns the type of the urn (e.g. slice, sliver). For the possible types see: http://groups.geni.net/geni/wiki/ GeniApiIdentifiers#ExamplesandUsage """ return urn.split("+")[2].strip() def lxml_ad_root(self): """ Returns a xml root node with the namespace extensions specified by self.get_ad_extensions_mapping. """ return etree.Element("rspec", self.get_ad_extensions_mapping(), type="advertisement") def lxml_manifest_root(self): """ Returns a xml root node with the namespace extensions specified by self.get_manifest_extensions_mapping. """ return etree.Element("rspec", self.get_manifest_extensions_mapping(), type="manifest") def lxml_to_string(self, rspec): """ Converts lxml root node to string (for returning to the client). """ return etree.tostring(rspec, pretty_print=True) def lxml_ad_element_maker(self, prefix): """ Returns a lxml.builder.ElementMaker configured for avertisements and the namespace given by {prefix}. """ ext = self.get_ad_extensions_mapping() return ElementMaker(namespace=ext[prefix], nsmap=ext) def lxml_manifest_element_maker(self, prefix): """ Returns a lxml.builder.ElementMaker configured for manifests and the namespace given by {prefix}. """ ext = self.get_manifest_extensions_mapping() return ElementMaker(namespace=ext[prefix], nsmap=ext) def lxml_parse_rspec(self, rspec_string): """ Returns a the root element of the given {rspec_string} as lxml.Element. If the config key is set, the rspec is validated with the schemas found at the URLs specified in schemaLocation of the the given RSpec. """ # parse rspec_root = etree.fromstring(rspec_string) # validate RSpec against specified schemaLocations should_validate = ast.literal_eval(self.general_section.get("rspec_validation")) if should_validate: schema_locations = rspec_root.get("{http://www.w3.org/2001/XMLSchema-instance}schemaLocation") if schema_locations: schema_location_list = schema_locations.split(" ") # Strip whitespaces schema_location_list = map(lambda x: x.strip(), schema_location_list) for sl in schema_location_list: try: # Try to download schema xmlschema_contents = urllib2.urlopen(sl) xmlschema_doc = etree.parse(xmlschema_contents) xmlschema = etree.XMLSchema(xmlschema_doc) xmlschema.validate(rspec_root) except Exception as e: logger.warning("RSpec validation failed (%s: %s)" % (sl, str(e))) else: logger.warning("RSpec does not specify any schema locations") return rspec_root def lxml_elm_has_request_prefix(self, lxml_elm, ns_name): return str(lxml_elm.tag).startswith("{%s}" % (self.get_request_extensions_mapping()[ns_name],)) def lxml_elm_equals_request_tag(self, lxml_elm, ns_name, tagname): """Determines if the given tag by {ns_name} and {tagname} equals lxml_tag. The namespace URI is looked up via get_request_extensions_mapping()['ns_name']""" return ("{%s}%s" % (self.get_request_extensions_mapping()[ns_name], tagname)) == str(lxml_elm.tag)
class FlaskServer(object): """ Encapsules a flask server instance. It also exports/defines the rpcservice interface. When a request comes in the following chain is walked through: --http--> nginx webserver --fcgi--> WSGIServer --WSGI--> FlaskApp When using the development server: werkzeug server --WSGI--> FlaskApp """ def __init__(self): """Constructor for the server wrapper.""" #self._app = Flask(__name__) # imports the named package, in this case this file self.__load_config() self._app = Flask(__name__.split(".")[-1], template_folder=self.template_folder) self._app.mongo = db_sync_manager #PyMongo(self._app) self._app.db = "felix_mro" if self.mro_enabled else "felix_ro" # Added in order to be able to execute "before_request" method app = self._app # Setup debugging for app cDebug = self.general_section.get("debug") if cDebug: # log all actions on the XML-RPC interface def log_request(sender, **extra): logger.info(">>> REQUEST %s:\n%s" % (request.path, request.data)) request_started.connect(log_request, self._app) def log_response(sender, response, **extra): logger.info(">>> RESPONSE %s:\n%s" % (response.status, response.data)) request_finished.connect(log_response, self._app) @app.before_request def before_request(): # "Attach" objects within the "g" object. This is passed to each view method g.mongo = self._app.mongo def __load_config(self): # Imports the named module (package includes "." and this is not nice with PyMongo) self.config = FullConfParser() self.flask_category = self.config.get("flask.conf") self.general_section = self.flask_category.get("general") self.template_folder = self.general_section.get("template_folder") self.template_folder = os.path.normpath(os.path.join(os.path.dirname(__file__),\ "../../..", self.template_folder)) self.fcgi_section = self.flask_category.get("fcgi") self.certificates_flask_section = self.flask_category.get( "certificates") self.auth_category = self.config.get("auth.conf") self.certificates_auth_section = self.auth_category.get("certificates") # Verification and certificates self._verify_users =\ ast.literal_eval(self.certificates_auth_section.get("verify_users")) self.mro_section = self.config.get("ro.conf").get("master_ro") self.mro_enabled = ast.literal_eval( self.mro_section.get("mro_enabled")) @property def app(self): """Returns the flask instance (not part of the service interface, since it is specific to flask).""" return self._app def add_routes(self): """ New method. Allows to register URLs from a the views file. """ # from server.flask import views as flask_views # flask_views_custom_methods = filter(lambda x: x.startswith("view_"), dir(flask_views)) # for custom_method in flask_views_custom_methods: # # Retrieve data needed to add the URL rule to the Flask app # view_method = getattr(locals()["flask_views"], custom_method) # docstring = getattr(view_method, "__doc__") # index_start = docstring.index("@app.route") # index_end = index_start + len("@app.route") + 1 # custom_method_url = docstring[index_end:].replace(" ","").replace("\n","") # # Get: (a) method URL to bind flask app, (b), method name, (c) method object to invoke # self._app.add_url_rule(custom_method_url, custom_method, view_func=view_method(self._app.mongo)) self._app.register_blueprint(ro_flask_views) def runServer(self, services=[]): """Starts up the server. It (will) support different config options via the config plugin.""" self.add_routes() #debug = self.general_section.get("debug") host = self.general_section.get("host") use_reloader = ast.literal_eval( self.general_section.get("use_reloader")) app_port = int(self.general_section.get("port")) cFCGI = ast.literal_eval(self.fcgi_section.get("enabled")) fcgi_port = int(self.fcgi_section.get("port")) must_have_client_cert = ast.literal_eval( self.certificates_flask_section.get("force_client_certificate")) if cFCGI: logger.info("registering fcgi server at %s:%i", host, fcgi_port) from flup.server.fcgi import WSGIServer WSGIServer(self._app, bindAddress=(host, fcgi_port)).run() else: logger.info("registering app server at %s:%i", host, app_port) # this workaround makes sure that the client cert can be acquired later (even when running the development server) # copied all this stuff from the actual flask implementation, so we can intervene and adjust the ssl context # self._app.run(host=host, port=app_port, ssl_context='adhoc', debug=debug, request_handler=ClientCertHTTPRequestHandler) # the code from flask's `run...` # see https://github.com/mitsuhiko/flask/blob/master/flask/app.py #options = {} try: # now the code from werkzeug's `run_simple(host, app_port, self._app, **options)` # see https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/serving.py #from werkzeug.debug import DebuggedApplication import socket #application = DebuggedApplication(self._app, True) # Set up an SSL context context = SSL.Context(SSL.SSLv23_METHOD) certs_path = os.path.normpath( os.path.join(os.path.dirname(__file__), "../../..", "cert")) context_crt = os.path.join(certs_path, "server.crt") context_key = os.path.join(certs_path, "server.key") try: context.use_certificate_file(context_crt) context.use_privatekey_file(context_key) except Exception as e: logger.critical( "error starting flask server. Cert or key is missing under %s", certs_path) sys.exit(e) def inner(): #server = serving.make_server(host, app_port, self._app, False, 1, ClientCertHTTPRequestHandler, False, 'adhoc') server = serving.make_server(host, app_port, self._app, False, 1, ClientCertHTTPRequestHandler, False, ssl_context=context) #server = serving.make_server(host, app_port, self._app, False, 1, ClientCertHTTPRequestHandler, False, ssl_context=(context_crt, context_key)) # The following line is the reason why I copied all that code! if must_have_client_cert: # FIXME: what works with web app does not work with cli. Check this out server.ssl_context.set_verify( SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, lambda a, b, c, d, e: True) # before enter in the loop, start the supplementary services for s in services: s.start() # That's it server.serve_forever() address_family = serving.select_ip_version(host, app_port) test_socket = socket.socket(address_family, socket.SOCK_STREAM) test_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) test_socket.bind((host, app_port)) test_socket.close() # Disable reloader only by explicit config setting if use_reloader == False: serving.run_simple(host, app_port, self._app, use_reloader=False) else: serving.run_with_reloader(inner, None, 1) finally: self._app._got_first_request = False
class GENIv3DelegateBase(object): """ Please find more information about the concept of Handlers and Delegates via the wiki (e.g. https://github.com/motine/AMsoil/wiki/GENI). The GENIv3 handler (see above) assumes that this class uses RSpec version 3 when interacting with the client. For creating new a new RSpec type/extension, please see the wiki via https://github.com/motine/AMsoil/wiki/RSpec. General parameters for all following methods: {client_cert} The client's certificate. See [flaskrpcs]XMLRPCDispatcher.requestCertificate(). Also see http://groups.geni.net/geni/wiki/GeniApiCertificates {credentials} The a list of credentials in the format specified at http:// groups.geni.net/geni/wiki/GAPI_AM_API_V3/CommonConcepts#credentials Dates are converted to UTC and then made timezone-unaware (see http:// docs.python.org/2/library/datetime.html#datetime.datetime.astimezone). """ ALLOCATION_STATE_UNALLOCATED = 'geni_unallocated' """The sliver does not exist. (see http://groups.geni.net/geni/wiki/ GAPI_AM_API_V3/CommonConcepts#SliverAllocationStates)""" ALLOCATION_STATE_ALLOCATED = 'geni_allocated' """The sliver is offered/promissed, but it does not consume actual resources. This state shall time out at some point in time.""" ALLOCATION_STATE_PROVISIONED = 'geni_provisioned' """The sliver is/has been instanciated. Operational states apply here.""" OPERATIONAL_STATE_PENDING_ALLOCATION = 'geni_pending_allocation' """Required for aggregates to support. A transient state.""" OPERATIONAL_STATE_NOTREADY = 'geni_notready' """Optional. A stable state.""" OPERATIONAL_STATE_CONFIGURING = 'geni_configuring' """Optional. A transient state.""" OPERATIONAL_STATE_STOPPING = 'geni_stopping' """Optional. A transient state.""" OPERATIONAL_STATE_READY = 'geni_ready' """Optional. A stable state.""" OPERATIONAL_STATE_READY_BUSY = 'geni_ready_busy' """Optional. A transient state.""" OPERATIONAL_STATE_FAILED = 'geni_failed' """Optional. A stable state.""" OPERATIONAL_ACTION_START = 'geni_start' """Sliver shall become geni_ready. The AM developer may define more states (see http://groups.geni.net/geni/wiki/GAPI_AM_API_V3/ CommonConcepts#SliverOperationalActions)""" OPERATIONAL_ACTION_RESTART = 'geni_restart' """Sliver shall become geni_ready again.""" OPERATIONAL_ACTION_STOP = 'geni_stop' """Sliver shall become geni_notready.""" OPERATIONAL_ACTION_UPDATE_USERS = 'geni_update_users' OPERATIONAL_ACTION_UPDATING_USERS_CANCEL = 'geni_updating_users_cancel' """ Following operational actions not fully implemented. """ OPERATIONAL_ACTION_CONSOLE_URL = 'geni_console_url' OPERATIONAL_ACTION_SHARELAN = 'geni_sharelan' OPERATIONAL_ACTION_UNSHARELAN = 'geni_unsharelan' def __init__(self): super(GENIv3DelegateBase, self).__init__() self.config = FullConfParser() self.general_section = self.config.get("geniv3.conf").get("general") self.certificates_section = self.config.get("auth.conf").get("certificates") def get_request_extensions_list(self): """Not to overwrite by AM developer. Should retrun a list of request extensions (XSD schemas) to be sent back by GetVersion.""" return [uri for prefix, uri in self.get_request_extensions_mapping().items()] def get_request_extensions_mapping(self): """Overwrite by AM developer. Should return a dict of namespace names and request extensions (XSD schema's URLs as string). Format: {xml_namespace_prefix : namespace_uri, ...} """ return {} def get_manifest_extensions_mapping(self): """Overwrite by AM developer. Should return a dict of namespace names and manifest extensions (XSD schema's URLs as string). Format: {xml_namespace_prefix : namespace_uri, ...} """ return {} def get_ad_extensions_list(self): """Not to overwrite by AM developer. Should retrun a list of request extensions (XSD schemas) to be sent back by GetVersion.""" return [uri for prefix, uri in self.get_ad_extensions_mapping().items()] def get_ad_extensions_mapping(self): """Overwrite by AM developer. Should return a dict of namespace names and advertisement extensions (XSD schema URLs as string) to be sent back by GetVersion. Format: {xml_namespace_prefix : namespace_uri, ...} """ return {} def is_single_allocation(self): """Overwrite by AM developer. Shall return a True or False. When True (not default), and performing one of (Describe, Allocate, Renew, Provision, Delete), such an AM requires you to include either the slice urn or the urn of all the slivers in the same state. see http://groups.geni.net/geni/wiki/GAPI_AM_API_V3/ CommonConcepts#OperationsonIndividualSlivers""" return False def get_allocation_mode(self): """Overwrite by AM developer. Shall return a either 'geni_single', 'geni_disjoint', 'geni_many'. It defines whether this AM allows adding slivers to slices at an AM (i.e. calling Allocate multiple times, without first deleting the allocated slivers). For description of the options see http://groups.geni.net/geni/wiki/ GAPI_AM_API_V3/CommonConcepts#OperationsonIndividualSlivers""" return 'geni_single' def list_resources(self, client_cert, credentials, geni_available, inner_call=False): """Overwrite by AM developer. Shall return an RSpec version 3 (advertisement) or raise an GENIv3...Error. If {geni_available} is set, only return availabe resources. For full description see http://groups.geni.net/geni/wiki/ GAPI_AM_API_V3#ListResources""" raise exceptions.GENIv3GeneralError("Method not implemented yet") def describe(self, urns, client_cert, credentials): """Overwrite by AM developer. Shall return an RSpec version 3 (manifest) or raise an GENIv3...Error. {urns} contains a list of slice identifiers (e.g. ['urn:publicid:IDN+ofelia:eict:gcf+slice+myslice']). For more information on possible {urns} see http://groups.geni.net/ geni/wiki/GAPI_AM_API_V3/CommonConcepts#urns For full description see http://groups.geni.net/geni/wiki/ GAPI_AM_API_V3#Describe""" raise exceptions.GENIv3GeneralError("Method not implemented yet") def allocate(self, slice_urn, client_cert, credentials, rspec, end_time=None): """Overwrite by AM developer. Shall return the two following values or raise an GENIv3...Error. - a RSpec version 3 (manifest) of newly allocated slivers - a list of slivers of the format: [{'geni_sliver_urn' : String, 'exceptionspires' : Python-Date, 'geni_allocation_status' : one of the ALLOCATION_STATE_xxx}, ...] Please return like so: "return respecs, slivers" {slice_urn} contains a slice identifier (e.g. 'urn:publicid:IDN+ofelia:eict:gcf+slice+myslice'). {end_time} Optional. A python datetime object which determines the desired expiry date of this allocation (see http://groups.geni.net/ geni/wiki/GAPI_AM_API_V3/CommonConcepts#geni_end_time). >>> This is the first part of what CreateSliver used to do in previous versions of the AM API. The second part is now done by Provision, and the final part is done by PerformOperationalAction. For full description see http://groups.geni.net/geni/wiki/ GAPI_AM_API_V3#Allocate""" raise exceptions.GENIv3GeneralError("Method not implemented yet") def renew(self, urns, client_cert, credentials, expiration_time, best_effort): """Overwrite by AM developer. Shall return a list of slivers of the following format or raise an GENIv3...Error: [{'geni_sliver_urn' : String, 'geni_allocation_status' : one of the ALLOCATION_STATE_xxx, 'geni_operational_status' : one of the OPERATIONAL_STATE_xxx, 'exceptionspires' : Python-Date, 'geni_error' : optional String}, ...] {urns} contains a list of slice identifiers (e.g. ['urn:publicid:IDN+ofelia:eict:gcf+slice+myslice']). {expiration_time} is a python datetime object {best_effort} determines if the method shall fail in case that not all of the urns can be renewed (best_effort=False). If the transactional behaviour of {best_effort}=False can not be provided, throw a GENIv3OperationUnsupportedError. For more information on possible {urns} see http://groups.geni.net/ geni/wiki/GAPI_AM_API_V3/CommonConcepts#urns For full description see http://groups.geni.net/geni/wiki/ GAPI_AM_API_V3#Renew""" raise exceptions.GENIv3GeneralError("Method not implemented yet") def provision(self, urns, client_cert, credentials, best_effort, end_time, geni_users): """Overwrite by AM developer. Shall return the two following values or raise an GENIv3...Error. - a RSpec version 3 (manifest) of slivers - a list of slivers of the format: [{'geni_sliver_urn' : String, 'geni_allocation_status' : one of the ALLOCATION_STATE_xxx, 'geni_operational_status' : one of the OPERATIONAL_STATE_xxx, 'exceptionspires' : Python-Date, 'geni_error' : optional String}, ...] Please return like so: "return respecs, slivers" {urns} contains a list of slice/resource identifiers (e.g. ['urn:publicid:IDN+ofelia:eict:gcf+slice+myslice']). {best_effort} determines if the method shall fail in case that not all of the urns can be provisioned (best_effort=False) {end_time} Optional. A python datetime object which determines the desired expiry date of this provision (see http://groups.geni.net/ geni/wiki/GAPI_AM_API_V3/CommonConcepts#geni_end_time). {geni_users} is a list of the format: [ { 'urn' : ..., 'keys' : [sshkey, ...]}, ...] If the transactional behaviour of {best_effort}=False can not be provided, throw a GENIv3OperationUnsupportedError. For more information on possible {urns} see http://groups.geni.net/ geni/wiki/GAPI_AM_API_V3/CommonConcepts#urns For full description see http://groups.geni.net/geni/wiki/ GAPI_AM_API_V3#Provision""" raise exceptions.GENIv3GeneralError("Method not implemented yet") def status(self, urns, client_cert, credentials): """Overwrite by AM developer. Shall return the two following values or raise an GENIv3...Error. - a slice urn - a list of slivers of the format: [{'geni_sliver_urn' : String, 'geni_allocation_status' : one of the ALLOCATION_STATE_xxx, 'geni_operational_status' : one of the OPERATIONAL_STATE_xxx, 'exceptionspires' : Python-Date, 'geni_error' : optional String}, ...] Please return like so: "return slice_urn, slivers" {urns} contains a list of slice/resource identifiers (e.g. ['urn:publicid:IDN+ofelia:eict:gcf+slice+myslice']). For more information on possible {urns} see http://groups.geni.net/ geni/wiki/GAPI_AM_API_V3/CommonConcepts#urns For full description see http://groups.geni.net/geni/wiki/ GAPI_AM_API_V3#Status""" raise exceptions.GENIv3GeneralError("Method not implemented yet") def perform_operational_action(self, urns, client_cert, credentials, action, best_effort): """Overwrite by AM developer. Shall return a list of slivers of the following format or raise an GENIv3...Error: [{'geni_sliver_urn' : String, 'geni_allocation_status' : one of the ALLOCATION_STATE_xxx, 'geni_operational_status' : one of the OPERATIONAL_STATE_xxx, 'exceptionspires' : Python-Date, 'geni_error' : optional String}, ...] {urns} contains a list of slice or sliver identifiers (e.g. ['urn:publicid:IDN+ofelia:eict:gcf+slice+myslice']). {action} an arbitraty string, but the following should be possible: "geni_start", "geni_stop", "geni_restart" {best_effort} determines if the method shall fail in case that not all of the urns can be changed (best_effort=False) If the transactional behaviour of {best_effort}=False can not be provided, throw a GENIv3OperationUnsupportedError. For more information on possible {urns} see http://groups.geni.net/ geni/wiki/GAPI_AM_API_V3/CommonConcepts#urns For full description see http://groups.geni.net/geni/wiki/ GAPI_AM_API_V3#PerformOperationalAction""" raise exceptions.GENIv3GeneralError("Method not implemented yet") def delete(self, urns, client_cert, credentials, best_effort): """Overwrite by AM developer. Shall return a list of slivers of the following format or raise an GENIv3...Error: [{'geni_sliver_urn' : String, 'geni_allocation_status' : one of the ALLOCATION_STATE_xxx, 'exceptionspires' : Python-Date, 'geni_error' : optional String}, ...] {urns} contains a list of slice/resource identifiers (e.g. ['urn:publicid:IDN+ofelia:eict:gcf+slice+myslice']). {best_effort} determines if the method shall fail in case that not all of the urns can be deleted (best_effort=False) If the transactional behaviour of {best_effort}=False can not be provided, throw a GENIv3OperationUnsupportedError. For more information on possible {urns} see http://groups.geni.net/ geni/wiki/GAPI_AM_API_V3/CommonConcepts#urns For full description see http://groups.geni.net/geni/wiki/ GAPI_AM_API_V3#Delete""" raise exceptions.GENIv3GeneralError("Method not implemented yet") def shutdown(self, slice_urn, client_cert, credentials): """Overwrite by AM developer. Shall return True or False or raise an GENIv3...Error. For full description see http://groups.geni.net/geni/wiki/ GAPI_AM_API_V3#Shutdown""" raise exceptions.GENIv3GeneralError("Method not implemented yet") def auth(self, client_cert, credentials, slice_urn=None, privileges=()): """ This method authenticates and authorizes. It returns the client's urn, uuid, email (extracted from the {client_cert}). Example call: "urn, uuid, email = self.auth(...)" Be aware, the email is not required in the certificate, hence it might be empty. If the validation fails, an GENIv3ForbiddenError is thrown. The credentials are checked so the user has all the required privileges (success if any credential fits all privileges). The client certificate is not checked: this is usually done via the webserver configuration. This method only treats certificates of type 'geni_sfa'. Here a list of possible privileges (format: right_in_credential: [privilege1, privilege2, ...]): "authority" : ["register", "remove", "update", "resolve", "list", "getcredential", "*"], "refresh" : ["remove", "update"], "resolve" : ["resolve", "list", "getcredential"], "sa" : ["getticket", "redeemslice", "redeemticket", "createslice", "createsliver", "deleteslice", "deletesliver", "updateslice", "getsliceresources", "getticket", "loanresources", "stopslice", "startslice", "renewsliver", "deleteslice", "deletesliver", "resetslice", "listslices", "listnodes", "getpolicy", "sliverstatus"], "embed" : ["getticket", "redeemslice", "redeemticket", "createslice", "createsliver", "renewsliver", "deleteslice", "deletesliver", "updateslice", "sliverstatus", "getsliceresources", "shutdown"], "bind" : ["getticket", "loanresources", "redeemticket"], "control" : ["updateslice", "createslice", "createsliver", "renewsliver", "sliverstatus", "stopslice", "startslice", "deleteslice", "deletesliver", "resetslice", "getsliceresources", "getgids"], "info" : ["listslices", "listnodes", "getpolicy"], "ma" : ["setbootstate", "getbootstate", "reboot", "getgids", "gettrustedcerts"], "operator" : ["gettrustedcerts", "getgids"], "*" : ["createsliver", "deletesliver", "sliverstatus", "renewsliver", "shutdown"] When using the gcf clearinghouse implementation the credentials will have the rights: - user: "******", "resolve", "info" (which resolves to the privileges: "remove", "update", "resolve", "list", "getcredential", "listslices", "listnodes", "getpolicy"). - slice: "refresh", "embed", "bind", "control", "info" (well, do the resolving yourself...) """ # check variables if not isinstance(privileges, tuple): raise TypeError("Privileges need to be a tuple.") # collect credentials (only GENI certs, version ignored) geni_credentials = [] for c in credentials: if c['geni_type'] == 'geni_sfa': geni_credentials.append(c['geni_value']) # Get the cert_root from the configuration settings root_path = os.path.normpath(os.path.join(os.path.dirname(__file__), "../../../../")) cert_root = os.path.join(root_path, self.certificates_section.get("cert_root")) logger.debug("client_certificate trusted, present at: %s" % str(cert_root)) logger.debug("client_certificate:\n%s" % str(client_cert)) if client_cert is None: raise exceptions.GENIv3ForbiddenError("Could not determine the client SSL certificate") # test the credential try: cred_verifier = extensions.geni.util.cred_util.CredentialVerifier(cert_root) cred_verifier.verify_from_strings(client_cert, geni_credentials, slice_urn, privileges) except Exception as e: raise exceptions.GENIv3ForbiddenError(str(e)) user_gid = extensions.sfa.trust.gid.GID(string=client_cert) user_urn = user_gid.get_urn() user_uuid = user_gid.get_uuid() user_email = user_gid.get_email() return user_urn, user_uuid, user_email # TODO document return def urn_type(self, urn): """Returns the type of the urn (e.g. slice, sliver). For the possible types see: http://groups.geni.net/geni/wiki/ GeniApiIdentifiers#ExamplesandUsage""" return urn.split('+')[2].strip() def lxml_ad_root(self): """Returns a xml root node with the namespace extensions specified by self.get_ad_extensions_mapping.""" return etree.Element('rspec', self.get_ad_extensions_mapping(), type='advertisement') def lxml_manifest_root(self): """Returns a xml root node with the namespace extensions specified by self.get_manifest_extensions_mapping.""" return etree.Element('rspec', self.get_manifest_extensions_mapping(), type='manifest') def lxml_to_string(self, rspec): """Converts lxml root node to string (for returning to the client).""" return etree.tostring(rspec, pretty_print=True) def lxml_ad_element_maker(self, prefix): """Returns a lxml.builder.ElementMaker configured for avertisements and the namespace given by {prefix}.""" ext = self.get_ad_extensions_mapping() return ElementMaker(namespace=ext[prefix], nsmap=ext) def lxml_manifest_element_maker(self, prefix): """Returns a lxml.builder.ElementMaker configured for manifests and the namespace given by {prefix}.""" ext = self.get_manifest_extensions_mapping() return ElementMaker(namespace=ext[prefix], nsmap=ext) def lxml_parse_rspec(self, rspec_string): """Returns a the root element of the given {rspec_string} as lxml.Element. If the config key is set, the rspec is validated with the schemas found at the URLs specified in schemaLocation of the the given RSpec.""" # parse rspec_root = etree.fromstring(rspec_string) # validate RSpec against specified schemaLocations should_validate = ast.literal_eval(self.general_section.get("rspec_validation")) if should_validate: schema_locations = rspec_root.get("{http://www.w3.org/2001/XMLSchema-instance}schemaLocation") if schema_locations: schema_location_list = schema_locations.split(" ") schema_location_list = map(lambda x: x.strip(), schema_location_list) # strip whitespaces for sl in schema_location_list: try: xmlschema_contents = urllib2.urlopen(sl) # try to download the schema xmlschema_doc = etree.parse(xmlschema_contents) xmlschema = etree.XMLSchema(xmlschema_doc) xmlschema.validate(rspec_root) except Exception as e: logger.warning("RSpec validation failed (%s: %s)" % (sl, str(e),)) else: logger.warning("RSpec does not specify any schema locations") return rspec_root def lxml_elm_has_request_prefix(self, lxml_elm, ns_name): return str(lxml_elm.tag).startswith( "{%s}" % (self.get_request_extensions_mapping()[ns_name],)) def lxml_elm_equals_request_tag(self, lxml_elm, ns_name, tagname): """Determines if the given tag by {ns_name} and {tagname} equals lxml_tag. The namespace URI is looked up via get_request_extensions_mapping()['ns_name']""" return ("{%s}%s" % (self.get_request_extensions_mapping()[ns_name], tagname)) == str(lxml_elm.tag)
class FlaskServer(object): """ Encapsules a flask server instance. It also exports/defines the rpcservice interface. When a request comes in the following chain is walked through: --http--> nginx webserver --fcgi--> WSGIServer --WSGI--> FlaskApp When using the development server: werkzeug server --WSGI--> FlaskApp """ def __init__(self): """Constructor for the server wrapper.""" #self._app = Flask(__name__) # imports the named package, in this case this file self.__load_config() self._app = Flask(__name__.split(".")[-1], template_folder = self.template_folder) self._app.mongo = db_sync_manager #PyMongo(self._app) self._app.db = "felix_mro" if self.mro_enabled else "felix_ro" # Added in order to be able to execute "before_request" method app = self._app # Setup debugging for app cDebug = self.general_section.get("debug") if cDebug: # log all actions on the XML-RPC interface def log_request(sender, **extra): logger.info(">>> REQUEST %s:\n%s" % (request.path, request.data)) request_started.connect(log_request, self._app) def log_response(sender, response, **extra): logger.info(">>> RESPONSE %s:\n%s" % (response.status, response.data)) request_finished.connect(log_response, self._app) @app.before_request def before_request(): # "Attach" objects within the "g" object. This is passed to each view method g.mongo = self._app.mongo def __load_config(self): # Imports the named module (package includes "." and this is not nice with PyMongo) self.config = FullConfParser() self.flask_category = self.config.get("flask.conf") self.general_section = self.flask_category.get("general") self.template_folder = self.general_section.get("template_folder") self.template_folder = os.path.normpath(os.path.join(os.path.dirname(__file__),\ "../../..", self.template_folder)) self.fcgi_section = self.flask_category.get("fcgi") self.certificates_flask_section = self.flask_category.get("certificates") self.auth_category = self.config.get("auth.conf") self.certificates_auth_section = self.auth_category.get("certificates") # Verification and certificates self._verify_users =\ ast.literal_eval(self.certificates_auth_section.get("verify_users")) self.mro_section = self.config.get("ro.conf").get("master_ro") self.mro_enabled = ast.literal_eval(self.mro_section.get("mro_enabled")) @property def app(self): """Returns the flask instance (not part of the service interface, since it is specific to flask).""" return self._app def add_routes(self): """ New method. Allows to register URLs from a the views file. """ # from server.flask import views as flask_views # flask_views_custom_methods = filter(lambda x: x.startswith("view_"), dir(flask_views)) # for custom_method in flask_views_custom_methods: # # Retrieve data needed to add the URL rule to the Flask app # view_method = getattr(locals()["flask_views"], custom_method) # docstring = getattr(view_method, "__doc__") # index_start = docstring.index("@app.route") # index_end = index_start + len("@app.route") + 1 # custom_method_url = docstring[index_end:].replace(" ","").replace("\n","") # # Get: (a) method URL to bind flask app, (b), method name, (c) method object to invoke # self._app.add_url_rule(custom_method_url, custom_method, view_func=view_method(self._app.mongo)) self._app.register_blueprint(ro_flask_views) def runServer(self, services=[]): """Starts up the server. It (will) support different config options via the config plugin.""" self.add_routes() #debug = self.general_section.get("debug") host = self.general_section.get("host") use_reloader = ast.literal_eval(self.general_section.get("use_reloader")) app_port = int(self.general_section.get("port")) cFCGI = ast.literal_eval(self.fcgi_section.get("enabled")) fcgi_port = int(self.fcgi_section.get("port")) must_have_client_cert = ast.literal_eval(self.certificates_flask_section.get("force_client_certificate")) if cFCGI: logger.info("registering fcgi server at %s:%i", host, fcgi_port) from flup.server.fcgi import WSGIServer WSGIServer(self._app, bindAddress=(host, fcgi_port)).run() else: logger.info("registering app server at %s:%i", host, app_port) # this workaround makes sure that the client cert can be acquired later (even when running the development server) # copied all this stuff from the actual flask implementation, so we can intervene and adjust the ssl context # self._app.run(host=host, port=app_port, ssl_context='adhoc', debug=debug, request_handler=ClientCertHTTPRequestHandler) # the code from flask's `run...` # see https://github.com/mitsuhiko/flask/blob/master/flask/app.py #options = {} try: # now the code from werkzeug's `run_simple(host, app_port, self._app, **options)` # see https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/serving.py #from werkzeug.debug import DebuggedApplication import socket #application = DebuggedApplication(self._app, True) # Set up an SSL context context = SSL.Context(SSL.SSLv23_METHOD) certs_path = os.path.normpath(os.path.join(os.path.dirname(__file__), "../../..", "cert")) context_crt = os.path.join(certs_path, "server.crt") context_key = os.path.join(certs_path, "server.key") try: context.use_certificate_file(context_crt) context.use_privatekey_file(context_key) except Exception as e: logger.critical("error starting flask server. Cert or key is missing under %s", certs_path) sys.exit(e) def inner(): #server = serving.make_server(host, app_port, self._app, False, 1, ClientCertHTTPRequestHandler, False, 'adhoc') server = serving.make_server(host, app_port, self._app, False, 1, ClientCertHTTPRequestHandler, False, ssl_context=context) #server = serving.make_server(host, app_port, self._app, False, 1, ClientCertHTTPRequestHandler, False, ssl_context=(context_crt, context_key)) # The following line is the reason why I copied all that code! if must_have_client_cert: # FIXME: what works with web app does not work with cli. Check this out server.ssl_context.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, lambda a,b,c,d,e: True) # before enter in the loop, start the supplementary services for s in services: s.start() # That's it server.serve_forever() address_family = serving.select_ip_version(host, app_port) test_socket = socket.socket(address_family, socket.SOCK_STREAM) test_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) test_socket.bind((host, app_port)) test_socket.close() # Disable reloader only by explicit config setting if use_reloader == False: serving.run_simple(host, app_port, self._app, use_reloader=False) else: serving.run_with_reloader(inner, None, 1) finally: self._app._got_first_request = False