Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
 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"))
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
 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"))
Ejemplo n.º 8
0
 def __load_config(self):
     # Imports the named module (package includes "." and
     # that is not cool 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")
     self.credentials_auth_section = \
         self.auth_category.get("credentials")
     # 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"))
     try:
         self.policies_category = self.config.get("policies.conf")
         self.policies_flask_section = self.policies_category.get("flask")
         self.policies_enabled = \
             ast.literal_eval(self.policies_flask_section.get("enabled"))
         self.policies_allowed_origins = \
             ast.literal_eval(
                 self.policies_flask_section.get("allowed_origins")
             )
     except:
         # When policies conf file does not exist, there are no checks
         self.policies_enabled = False
         self.policies_allowed_origins = ["*"]
Ejemplo n.º 9
0
 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")
Ejemplo n.º 10
0
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)
Ejemplo n.º 11
0
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
Ejemplo n.º 12
0
 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")
Ejemplo n.º 13
0
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)
Ejemplo n.º 14
0
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
Ejemplo n.º 15
0
 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"))