예제 #1
0
def get_service_private_certificate(fingerprint=None):
    """This function returns the private signing certificate
       for this service
    """
    s = get_this_service(need_private_access=True)
    s = refresh_service_keys_and_certs(s)
    cert = s.private_certificate()

    if fingerprint:
        if cert.fingerprint() != fingerprint:
            cert = s.last_certificate()

        if cert.fingerprint() != fingerprint:
            try:
                return load_service_key_from_objstore(fingerprint)
            except Exception as e:
                from Acquire.Service import ServiceAccountError
                raise ServiceAccountError(
                    "Cannot find a private certificate for '%s' that matches "
                    "the fingerprint %s. This is either because you are "
                    "using a certificate that is too old or "
                    "you are requesting a wrong certificate. Please call "
                    "refresh_keys on your Service object and try again: %s" %
                    (str(s), fingerprint, str(e)))

    return cert
예제 #2
0
def get_service_user_account_uid(accounting_service_uid):
    """Return the UID of the financial Acquire.Accounting.Account
       that is held on the accounting service with UID
       'accounting_service_uid' for the service user on this
       service. This is the account to which payment for this
       service should be sent
    """
    assert_running_service()

    from Acquire.Service import get_service_account_bucket as \
        _get_service_account_bucket
    from Acquire.ObjectStore import ObjectStore as _ObjectStore

    bucket = _get_service_account_bucket()

    key = "%s/account/%s" % (_service_key, accounting_service_uid)

    try:
        account_uid = _ObjectStore.get_string_object(bucket, key)
    except:
        account_uid = None

    if account_uid is None:
        from Acquire.Service import ServiceAccountError
        raise ServiceAccountError(
            "This service does not have a valid financial account on "
            "the accounting service at '%s'" % accounting_service_uid)

    return account_uid
예제 #3
0
def create_service_user_account(service, accounting_service_url):
    """Call this function to create the financial service account
       for this service on the accounting service at 'accounting_service_url'

       This does nothing if the account already exists
    """
    assert_running_service()

    accounting_service = service.get_trusted_service(
        service_url=accounting_service_url)
    accounting_service_uid = accounting_service.uid()

    key = "%s/account/%s" % (_service_key, accounting_service_uid)
    bucket = service.bucket()

    from Acquire.ObjectStore import ObjectStore as _ObjectStore

    try:
        account_uid = _ObjectStore.get_string_object(bucket, key)
    except:
        account_uid = None

    if account_uid:
        # we already have an account...
        return

    service_user = service.login_service_user()

    try:
        from Acquire.Client import create_account as _create_account
        from Acquire.Client import deposit as _deposit

        account = _create_account(
            service_user,
            "main",
            "Main account to receive payment for all use on service "
            "%s (%s)" % (service.canonical_url(), service.uid()),
            accounting_service=accounting_service)

        _deposit(user=service_user,
                 value=100.0,
                 account_name="main",
                 accounting_service=accounting_service)

        account_uid = account.uid()

        _ObjectStore.set_string_object(bucket, key, account_uid)
    except Exception as e:
        from Acquire.Service import exception_to_string
        from Acquire.Service import ServiceAccountError
        raise ServiceAccountError(
            "Unable to create a financial account for the service "
            "principal for '%s' on accounting service '%s'\n\nERROR\n%s" %
            (str(service), str(accounting_service), exception_to_string(e)))
예제 #4
0
def _get_service_password():
    """Function used to get the primary password that locks the
       skeleton key that secures the entire tree of trust from the
       Service object
    """
    service_password = _os.getenv("SERVICE_PASSWORD")

    if service_password is None:
        from Acquire.Service import ServiceAccountError
        raise ServiceAccountError("You must supply a $SERVICE_PASSWORD")

    return service_password
예제 #5
0
def get_service_public_key(fingerprint=None):
    """This function returns the public key for this service"""
    s = get_this_service(need_private_access=False)
    key = s.public_key()

    if fingerprint:
        if key.fingerprint() != fingerprint:
            from Acquire.Service import ServiceAccountError
            raise ServiceAccountError(
                "Cannot find a public key for '%s' that matches "
                "the fingerprint %s" % (str(s), fingerprint))

    return key
예제 #6
0
    def connect_to_bucket(login_details, bucket_name):
        """Connect to the object store compartment 'compartment'
           using the passed 'login_details', returning a handle to the
           bucket associated with 'bucket
        '"""
        try:
            from google.oauth2 import service_account as _service_account
            from google.cloud import storage as _storage
        except:
            raise ImportError(
                "Cannot import GCP. Please install GCP, e.g. via "
                "'pip install google-cloud-storage' so that you can "
                "connect to the Google Cloud Platform")

        login = GCPAccount.get_login(login_details)
        bucket = {}
        client = None

        creds = _service_account.Credentials.from_service_account_info(
            login["credentials"])

        client = _storage.Client(credentials=creds, project=login["project"])

        try:
            b = client.get_bucket(bucket_name)
        except Exception as e:
            from Acquire.Service import ServiceAccountError
            raise ServiceAccountError(
                "Cannot connect to GCP - invalid credentials for bucket %s" %
                bucket_name, e)

        bucket["client"] = client
        bucket["credentials"] = creds
        bucket["bucket"] = b
        bucket["bucket_name"] = bucket_name
        bucket["unique_suffix"] = login["unique_suffix"]

        return bucket
예제 #7
0
def _get_this_service_data():
    """Internal function that loads up the service info data from
       the object store.
    """
    assert_running_service()

    from Acquire.Service import ServiceAccountError

    # get the bucket again - can't pass as an argument as this is a cached
    # function - luckily _get_service_account_bucket is also a cached function
    try:
        from Acquire.Service import get_service_account_bucket as \
            _get_service_account_bucket
        bucket = _get_service_account_bucket()
    except ServiceAccountError as e:
        raise e
    except Exception as e:
        raise ServiceAccountError("Cannot log into the service account: %s" %
                                  str(e))

    # find the service info from the object store
    try:
        from Acquire.ObjectStore import ObjectStore as _ObjectStore
        service = _ObjectStore.get_object_from_json(bucket, _service_key)
    except Exception as e:
        from Acquire.Service import MissingServiceAccountError
        raise MissingServiceAccountError(
            "Unable to load the service account for this service. An "
            "error occured while loading the data from the object "
            "store: %s" % str(e))

    if not service:
        from Acquire.Service import MissingServiceAccountError
        raise MissingServiceAccountError(
            "You haven't yet created the service account "
            "for this service. Please create an account first.")

    return service
예제 #8
0
def get_admin_users():
    """This function returns all of the admin_users data. This is a
       dictionary of the UIDs of all of the admin users
    """
    assert_running_service()

    from Acquire.Service import ServiceAccountError

    try:
        from Acquire.Service import get_service_account_bucket as \
            _get_service_account_bucket
        bucket = _get_service_account_bucket()
    except ServiceAccountError as e:
        raise e
    except Exception as e:
        raise ServiceAccountError("Cannot log into the service account: %s" %
                                  str(e))

    # find the admin accounts info from the object store
    try:
        key = "%s/admin_users" % _service_key
        from Acquire.ObjectStore import ObjectStore as _ObjectStore
        admin_users = _ObjectStore.get_object_from_json(bucket, key)
    except Exception as e:
        from Acquire.Service import MissingServiceAccountError
        raise MissingServiceAccountError(
            "Unable to load the Admin User data for this service. An "
            "error occured while loading the data from the object "
            "store: %s" % str(e))

    if not admin_users:
        from Acquire.Service import MissingServiceAccountError
        raise MissingServiceAccountError(
            "You haven't yet created any Admin Users for the service account "
            "for this service. Please create an Admin User first.")

    return admin_users
예제 #9
0
def run(args):
    """Call this function to trust the passed accounting service,
       specifically to trust that we can move money using that service.

       Args:
            args(dict): containing data on the service we want
            to trust

       Returns:
            dict: containing status, status message and passed in args
    """
    service_url = args["service_url"]

    authorisation = Authorisation.from_data(args["authorisation"])

    accounting_service = get_trusted_service(service_url=service_url)

    if not accounting_service.is_accounting_service():
        raise ServiceAccountError(
            "%s is not an accounting service, so should not be "
            "trusted as one" % str(accounting_service))

    service = get_this_service(need_private_access=True)
    service.assert_admin_authorised(
        authorisation,
        "trust_accounting_service %s" % accounting_service.uid())

    url = accounting_service.canonical_url()

    create_service_user_account(service=service, accounting_service_url=url)

    return_value = {}

    return_value["args"] = args

    return return_value
예제 #10
0
def get_trusted_service(service_url=None,
                        service_uid=None,
                        service_type=None,
                        autofetch=True):
    """Return the trusted service info for the service with specified
       service_url or service_uid"""
    if service_url is not None:
        from Acquire.Service import Service as _Service
        service_url = _Service.get_canonical_url(service_url,
                                                 service_type=service_type)

    from Acquire.Service import is_running_service as _is_running_service

    if _is_running_service():
        from Acquire.Service import get_this_service as _get_this_service
        from Acquire.Service import Service as _Service
        from Acquire.Service import get_service_account_bucket as \
            _get_service_account_bucket
        from Acquire.ObjectStore import ObjectStore as _ObjectStore
        from Acquire.ObjectStore import url_to_encoded as \
            _url_to_encoded

        service = _get_this_service()

        if service_url is not None and service.canonical_url() == service_url:
            # we trust ourselves :-)
            return service

        if service_uid is not None and service.uid() == service_uid:
            # we trust ourselves :-)
            return service

        bucket = _get_service_account_bucket()
        uidkey = None
        data = None

        if service_uid is not None:
            uidkey = "_trusted/uid/%s" % service_uid
            try:
                data = _ObjectStore.get_object_from_json(bucket, uidkey)
            except:
                pass
        elif service_url is not None:
            urlkey = "_trusted/url/%s" % _url_to_encoded(service_url)
            try:
                uidkey = _ObjectStore.get_string_object(bucket, urlkey)
                if uidkey is not None:
                    data = _ObjectStore.get_object_from_json(bucket, uidkey)
            except:
                pass

        if data is not None:
            remote_service = _Service.from_data(data)

            if remote_service.should_refresh_keys():
                # need to update the keys in our copy of the service
                remote_service.refresh_keys()

                if uidkey is not None:
                    _ObjectStore.set_object_from_json(bucket, uidkey,
                                                      remote_service.to_data())

            return remote_service

        if not autofetch:
            from Acquire.Service import ServiceAccountError
            if service_uid is not None:
                raise ServiceAccountError(
                    "We do not trust the service with UID '%s'" % service_uid)
            else:
                raise ServiceAccountError(
                    "We do not trust the service at URL '%s'" % service_url)

        # we can try to fetch this data - we will ask our own
        # registry
        from Acquire.Registry import get_trusted_registry_service \
            as _get_trusted_registry_service
        registry = _get_trusted_registry_service(service_uid=service.uid())
        service = registry.get_service(service_uid=service_uid,
                                       service_url=service_url)

        from Acquire.Service import trust_service as _trust_service
        _trust_service(service)
        return service
    else:
        # this is running on the client
        from Acquire.Client import Wallet as _Wallet
        wallet = _Wallet()
        service = wallet.get_service(service_uid=service_uid,
                                     service_url=service_url,
                                     service_type=service_type,
                                     autofetch=autofetch)
        return service
예제 #11
0
def add_admin_user(service, account_uid, authorisation=None):
    """Function that is called to add a new user account as a service
       administrator. If this is the first account then authorisation
       is not needed. If this is the second or subsequent admin account,
       then you need to provide an authorisation signed by one of the
       existing admin users. If you need to reset the admin users then
       delete the user accounts from the service.
    """
    assert_running_service()

    from Acquire.Service import get_service_account_bucket as \
        _get_service_account_bucket
    from Acquire.ObjectStore import Mutex as _Mutex
    from Acquire.ObjectStore import ObjectStore as _ObjectStore
    from Acquire.ObjectStore import get_datetime_now_to_string as \
        _get_datetime_now_to_string

    bucket = _get_service_account_bucket()

    # see if the admin account details exists...
    admin_key = "%s/admin_users" % _service_key

    # ensure that we have exclusive access to this service
    mutex = _Mutex(key=admin_key, bucket=bucket)

    try:
        admin_users = _ObjectStore.get_object_from_json(bucket, admin_key)
    except:
        admin_users = None

    if admin_users is None:
        # this is the first admin user - automatically accept
        admin_users = {}
        authorised_by = "first admin"
    else:
        # validate that the new user has been authorised by an existing
        # admin...
        if authorisation is None:
            from Acquire.Service import ServiceAccountError
            raise ServiceAccountError(
                "You must supply a valid authorisation from an existing admin "
                "user if you want to add a new admin user.")

        if authorisation.user_uid() not in admin_users:
            from Acquire.Service import ServiceAccountError
            raise ServiceAccountError(
                "The authorisation for the new admin account is not valid "
                "because the user who signed it is not a valid admin on "
                "this service.")

        authorisation.verify(account_uid)
        authorised_by = authorisation.user_uid()

    # everything is ok - add this admin user to the admin_users
    # dictionary - save the date and time they became an admin,
    # and how they achieved this status (first admin, or whoever
    # authorised them)
    admin_users[account_uid] = {
        "datetime": _get_datetime_now_to_string(),
        "authorised_by": authorised_by
    }

    # everything is done, so now write this data to the object store
    _ObjectStore.set_object_from_json(bucket, admin_key,
                                      _json.dumps(admin_users))

    # we can (finally!) release the mutex, as everyone else should now
    # be able to see the account
    mutex.unlock()

    _cache_adminusers.clear()
예제 #12
0
def setup_this_service(service_type, canonical_url, registry_uid, username,
                       password):
    """Call this function to setup a new
       service that will serve at 'canonical_url', will be of
       the specified service_type. This will be registered at the
       registry at UID registry_uid

        (1) Delete the object store value "_service" if you want to reset
            the actual Service. This will assign a new UID for the service
            which would reset the certificates and keys. This new service
            will need to be re-introduced to other services that need
            to trust it
    """
    assert_running_service()

    from Acquire.Service import get_service_account_bucket as \
        _get_service_account_bucket

    from Acquire.ObjectStore import Mutex as _Mutex
    from Acquire.ObjectStore import ObjectStore as _ObjectStore
    from Acquire.Service import Service as _Service

    bucket = _get_service_account_bucket()

    # ensure that this is the only time the service is set up
    mutex = _Mutex(key=_service_key, bucket=bucket, lease_time=120)

    try:
        service_info = _ObjectStore.get_object_from_json(bucket, _service_key)
    except:
        service_info = None

    service = None
    service_password = _get_service_password()

    user_uid = None
    otp = None

    if service_info:
        try:
            service = _Service.from_data(service_info, service_password)
        except Exception as e:
            from Acquire.Service import ServiceAccountError
            raise ServiceAccountError(
                "Something went wrong reading the Service data. You should "
                "either debug the error or delete the data at key '%s' "
                "to allow the service to be reset and constructed again. "
                "The error was %s" % (_service_key, str(e)))

        if service.uid().startswith("STAGE1"):
            from Acquire.Service import ServiceAccountError
            raise ServiceAccountError(
                "The service is currently under construction. Please "
                "try again later...")

    if service is None:
        # we need to create the new service
        if (service_type is None) or (canonical_url is None):
            from Acquire.Service import ServiceAccountError
            raise ServiceAccountError(
                "You need to supply both the service_type and canonical_url "
                "in order to initialise a new Service")

        # we need to build the service account - first stage 1
        service = _Service.create(service_url=canonical_url,
                                  service_type=service_type)

        # write the stage1 service data, encrypted using the service password.
        # This will be needed to answer the challenge from the registry
        service_data = service.to_data(service_password)
        _ObjectStore.set_object_from_json(bucket, _service_key, service_data)

        # now we can register the service with a registry - this
        # will return the stage2-constructed service
        from Acquire.Registry import register_service as _register_service
        service = _register_service(service=service, registry_uid=registry_uid)

    canonical_url = _Service.get_canonical_url(canonical_url)

    if service.service_type() != service_type or \
            service.canonical_url() != canonical_url:
        from Acquire.Service import ServiceAccountError
        raise ServiceAccountError(
            "The existing service has a different type or URL to that "
            "requested at setup. The request type and URL are %s and %s, "
            "while the actual service type and URL are %s and %s." %
            (service_type, canonical_url, service.service_type(),
             service.canonical_url()))

    # we can add the first admin user
    service_uid = service.uid()
    skelkey = service.skeleton_key().public_key()

    # now register the new admin user account - remembering to
    # encode the password
    from Acquire.Client import Credentials as _Credentials
    password = _Credentials.encode_password(password=password,
                                            identity_uid=service_uid)

    from Acquire.Identity import UserAccount as _UserAccount
    (user_uid, otp) = _UserAccount.create(username=username,
                                          password=password,
                                          _service_uid=service_uid,
                                          _service_public_key=skelkey)

    add_admin_user(service, user_uid)

    # write the service data, encrypted using the service password
    service_data = service.to_data(service_password)
    # reload the data to check it is ok, and also to set the right class
    service = _Service.from_data(service_data, service_password)
    # now it is ok, save this data to the object store
    _ObjectStore.set_object_from_json(bucket, _service_key, service_data)

    mutex.unlock()

    from Acquire.Service import clear_service_cache as _clear_service_cache
    _clear_service_cache()

    return (service, user_uid, otp)
예제 #13
0
def get_service_account_bucket(testing_dir=None):
    """This function logs into the object store account of the service account.
       Accessing the object store means being able to access
       all resources and which can authorise the creation
       of access all resources on the object store. Obviously this is
       a powerful account, so only log into it if you need it!!!

       The login information should not be put into a public
       repository or stored in plain text. In this case,
       the login information is held in an environment variable
       (which should be encrypted or hidden in some way...)
    """
    from Acquire.Service import assert_running_service as \
        _assert_running_service

    _assert_running_service()

    # read the password for the secret key from the filesystem
    try:
        with open("secret_key", "r") as FILE:
            password = FILE.readline()[0:-1]
    except:
        password = None

        # we must be in testing mode...
        from Acquire.ObjectStore import use_testing_object_store_backend as \
            _use_testing_object_store_backend

        # see if this is running in testing mode...
        global _current_testing_objstore
        if testing_dir:
            _current_testing_objstore = testing_dir
            return _use_testing_object_store_backend(testing_dir)
        elif _current_testing_objstore:
            return _use_testing_object_store_backend(_current_testing_objstore)

    if password is None:
        from Acquire.Service import ServiceAccountError
        raise ServiceAccountError(
            "You need to supply login credentials via the 'secret_key' "
            "file, and 'SECRET_KEY' and 'SECRET_CONFIG' environment "
            "variables! %s" % testing_dir)

    secret_key = _os.getenv("SECRET_KEY")

    if secret_key is None:
        from Acquire.Service import ServiceAccountError
        raise ServiceAccountError(
            "You must supply the password used to unlock the configuration "
            "key in the 'SECRET_KEY' environment variable")

    try:
        secret_key = _json.loads(secret_key)
    except Exception as e:
        from Acquire.Service import ServiceAccountError
        raise ServiceAccountError(
            "Unable to decode valid JSON from the secret key: %s" % str(e))

    # use the password to decrypt the SECRET_KEY in the config
    try:
        from Acquire.Crypto import PrivateKey as _PrivateKey
        secret_key = _PrivateKey.from_data(secret_key, password)
    except Exception as e:
        from Acquire.Service import ServiceAccountError
        raise ServiceAccountError(
            "Unable to open the private SECRET_KEY using the password "
            "supplied in the 'secret_key' file: %s" % str(e))

    config = _os.getenv("SECRET_CONFIG")

    if config is None:
        from Acquire.Service import ServiceAccountError
        raise ServiceAccountError(
            "You must supply the encrypted config in teh 'SECRET_CONFIG' "
            "environment variable!")

    try:
        from Acquire.ObjectStore import string_to_bytes as _string_to_bytes
        config = secret_key.decrypt(_string_to_bytes(config))
    except Exception as e:
        from Acquire.Service import ServiceAccountError
        raise ServiceAccountError(
            "Cannot decrypt the 'SECRET_CONFIG' with the 'SECRET_KEY'. Are "
            "you sure that the configuration has been set up correctly? %s "
            % str(e))

    # use the secret_key to decrypt the config in SECRET_CONFIG
    try:
        config = _json.loads(config)
    except Exception as e:
        from Acquire.Service import ServiceAccountError
        raise ServiceAccountError(
            "Unable to decode valid JSON from the config: %s" % str(e))

    # get info from this config
    access_data = config["LOGIN"]
    bucket_data = config["BUCKET"]

    # save the service password to the environment
    _os.environ["SERVICE_PASSWORD"] = config["PASSWORD"]

    # save any other decrypted config data to environment variables
    for key in config.keys():
        if key not in ["LOGIN", "BUCKET", "PASSWORD"]:
            _os.environ[key] = config[key]

    # we have OCI login details, so make sure that we are using
    # the OCI object store backend
    from Acquire.ObjectStore import use_oci_object_store_backend as \
        _use_oci_object_store_backend

    _use_oci_object_store_backend()

    # now login and create/load the bucket for this account
    try:
        from ._oci_account import OCIAccount as _OCIAccount

        account_bucket = _OCIAccount.create_and_connect_to_bucket(
                                    access_data,
                                    bucket_data["compartment"],
                                    bucket_data["bucket"])
    except Exception as e:
        from Acquire.Service import ServiceAccountError
        raise ServiceAccountError(
             "Error connecting to the service account: %s" % str(e))

    return account_bucket