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
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
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)))
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
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
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
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
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
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
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
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()
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)
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