def post(self): """ Generate a keypair for user **Example:** .. code-block:: http POST /credentials/cdis/?expires_in=3600 HTTP/1.1 Content-Type: application/json Accept: application/json (JSON key in Google Credentials File format) .. code-block:: JavaScript { "type": "service_account", "project_id": "project-id", "private_key_id": "some_number", "private_key": "-----BEGIN PRIVATE KEY-----\n.... =\n-----END PRIVATE KEY-----\n", "client_email": "<api-name>[email protected]", "client_id": "...", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://accounts.google.com/o/oauth2/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "https://www.googleapis.com/...<api-name>api%40project-id.iam.gserviceaccount.com" } """ user_id = current_token["sub"] client_id = current_token.get("azp") or None proxy_group_id = get_or_create_proxy_group_id() username = current_token.get("context", {}).get("user", {}).get("name") r_pays_project = flask.request.args.get("userProject", None) key, service_account = create_google_access_key( client_id, user_id, username, proxy_group_id ) if config["ENABLE_AUTOMATIC_BILLING_PERMISSION_SA_CREDS"]: give_service_account_billing_access_if_necessary( key, r_pays_project, default_billing_project=config["BILLING_PROJECT_FOR_SA_CREDS"], ) if client_id is None: self.handle_user_service_account_creds(key, service_account) return flask.jsonify(key)
def get(self): """ List access keys for user **Example:** .. code-block:: http POST /credentials/apis/ HTTP/1.1 Content-Type: application/json Accept: application/json Info from Google API /serviceAccounts/<account>/keys endpoint TODO: In the future we should probably add in our expiration time, when we start monitoring and deleting after x amount of time .. code-block:: JavaScript { "access_keys": [ { "keyAlgorithm": enum(ServiceAccountKeyAlgorithm), "validBeforeTime": string, "name": string, "validAfterTime": string, }, ... ] } """ client_id = current_token.get("azp") or None user_id = current_token["sub"] username = current_token.get("context", {}).get("user", {}).get("name") with GoogleCloudManager() as g_cloud_manager: proxy_group_id = get_or_create_proxy_group_id() service_account = get_or_create_service_account( client_id=client_id, user_id=user_id, username=username, proxy_group_id=proxy_group_id, ) keys = g_cloud_manager.get_service_account_keys_info( service_account.google_unique_id) result = {"access_keys": keys} return flask.jsonify(result)
def delete(self): """ .. http:get: /google/ Delete keypair(s) for user ?all=true must be specified True values are y, yes, t, true, on and 1; false values are n, no, f, false, off and 0 :statuscode 204 Success :statuscode 403 Forbidden to delete access key :statuscode 405 Method Not Allowed if ?all=true is not included """ user_id = current_token["sub"] username = current_token.get("context", {}).get("user", {}).get("name") try: all_arg = strtobool(flask.request.args.get("all", "false").lower()) except ValueError: all_arg = False if not all_arg: flask.abort( 405, "Please include ?all=true to confirm deletion of ALL Google Service account keys.", ) with GoogleCloudManager() as g_cloud: client_id = current_token.get("azp") or None service_account = get_service_account(client_id, user_id, username=username) if service_account: keys_for_account = g_cloud.get_service_account_keys_info( service_account.email ) # Only delete the key if is owned by current client's SA all_client_keys = [ key["name"].split("/")[-1] for key in keys_for_account ] for key in all_client_keys: _delete_service_account_key(g_cloud, service_account.email, key) else: flask.abort(404, "Could not find service account for current user.") return "", 204
def delete(self, access_key): """ .. http:get: /google/(string: access_key) Delete keypair(s) for user :param access_key: existing access key that belongs to this user :statuscode 204 Success :statuscode 403 Forbidden to delete access key :statuscode 404 Access key doesn't exist """ user_id = current_token["sub"] username = current_token.get("context", {}).get("user", {}).get("name") with GoogleCloudManager() as g_cloud: client_id = current_token.get("azp") or None service_account = get_service_account(client_id, user_id, username=username) if service_account: keys_for_account = g_cloud.get_service_account_keys_info( service_account.email ) # Only delete the key if is owned by current client's SA all_client_keys = [ key["name"].split("/")[-1] for key in keys_for_account ] if access_key in all_client_keys: _delete_service_account_key( g_cloud, service_account.email, access_key ) else: flask.abort( 404, "Could not delete key " + access_key + ". Not found for current user.", ) else: flask.abort(404, "Could not find service account for current user.") return "", 204
def post(self): """ Generate a key for user **Example:** .. code-block:: http POST /credentials/api/?expires_in=3600 HTTP/1.1 Content-Type: application/json Accept: application/json .. code-block:: JavaScript { "key_id": result, "api_key": result } """ client_id = current_token.get("azp") or None user_id = current_token["sub"] # fence identifies access_token endpoint, openid is the default # scope for service endpoints default_scope = ["fence", "openid"] content_type = flask.request.headers.get("Content-Type") if content_type == "application/x-www-form-urlencoded": scope = flask.request.form.getlist("scope") else: try: scope = (json.loads(flask.request.data).get("scope")) or [] except ValueError: scope = [] if not isinstance(scope, list): scope = scope.split(",") scope.extend(default_scope) for s in scope: if s not in config["USER_ALLOWED_SCOPES"]: flask.abort(400, "Scope {} is not supported".format(s)) # add all scopes from the user's access token; # remove any scopes that have been removed from USER_ALLOWED_SCOPES scope.extend( [s for s in current_token["scope"] if s in config["USER_ALLOWED_SCOPES"]] ) # a token created using an API key cannot be used to create a new API key scope = [s for s in set(scope) if s != "credentials"] max_ttl = config.get("MAX_API_KEY_TTL", 2592000) expires_in = min(int(flask.request.args.get("expires_in", max_ttl)), max_ttl) api_key, claims = create_api_key( user_id, flask.current_app.keypairs[0], expires_in, scope, client_id ) return flask.jsonify(dict(key_id=claims["jti"], api_key=api_key))
def get_users_proxy_group_from_token(): """ Return a user's proxy group ID by parsing the JWT token in the header. Returns: str: proxy group ID or None """ return (current_token.get("context", {}).get("user", {}).get("google", {}).get("proxy_group", None))
def get_users_linked_google_email_from_token(): """ Return a user's linked Google Account's email address by parsing the JWT token in the header. Returns: str: email address of account or None """ if current_token: return (current_token.get("context", {}).get("user", {}).get( "google", {}).get("linked_google_account", None)) return None
def delete(self, access_key): """ .. http:get: /google/(string: access_key) Delete a keypair for user :param access_key: existing access key that belongs to this user :statuscode 204 Success :statuscode 403 Forbidden to delete access key :statuscode 404 Access key doesn't exist """ user_id = current_token["sub"] with GoogleCloudManager() as g_cloud: client_id = current_token.get("azp") or None service_account = get_service_account(client_id, user_id) if service_account: keys_for_account = g_cloud.get_service_account_keys_info( service_account.google_unique_id) # Only delete the key if is owned by current client's SA all_client_keys = [ key["name"].split("/")[-1] for key in keys_for_account ] if access_key in all_client_keys: g_cloud.delete_service_account_key( service_account.google_unique_id, access_key) db_entry = (current_session.query(GoogleServiceAccountKey). filter_by(key_id=access_key).first()) if db_entry: current_session.delete(db_entry) current_session.commit() else: flask.abort( 404, "Could not delete key " + access_key + ". Not found for current user.", ) else: flask.abort( 404, "Could not find service account for current user.") return "", 204
def post(self): """ Force the creation of the User's Primary Google Service Account instead of relying on lazy creation at first time of Google Data Access. """ user_id = current_token["sub"] proxy_group_id = get_or_create_proxy_group_id() username = current_token.get("context", {}).get("user", {}).get("name") service_account_email = None # do the same thing signed URL creation is doing, but don't use the resulting # key, just extract the service account email sa_private_key, _ = get_or_create_primary_service_account_key( user_id=user_id, username=username, proxy_group_id=proxy_group_id ) service_account_email = sa_private_key.get("client_email") # NOTE: service_account_from_db.email is what gets populated in the UserInfo endpoint's # "primary_google_service_account" as well, so this remains consistent return flask.jsonify({"primary_google_service_account": service_account_email})
def get_or_create_proxy_group_id(): """ If no username returned from token or database, create a new proxy group for the give user. Also, add the access privileges. Returns: int: id of (possibly newly created) proxy group associated with user """ proxy_group_id = _get_proxy_group_id() if not proxy_group_id: user_id = current_token["sub"] username = current_token.get("context", {}).get("user", {}).get("name", "") proxy_group_id = _create_proxy_group(user_id, username).id privileges = current_session.query(AccessPrivilege).filter( AccessPrivilege.user_id == user_id) for p in privileges: storage_accesses = p.project.storage_access for sa in storage_accesses: if sa.provider.name == STORAGE_ACCESS_PROVIDER_NAME: flask.current_app.storage_manager.logger.info( "grant {} access {} to {} in {}".format( username, p.privilege, p.project_id, p.auth_provider)) flask.current_app.storage_manager.grant_access( provider=(sa.provider.name), username=username, project=p.project, access=p.privilege, session=current_session, ) return proxy_group_id
def _generate_google_storage_signed_url(self, http_verb, resource_path, expiration_time): set_current_token(validate_request(aud={"user"})) user_id = current_token["sub"] proxy_group_id = get_or_create_proxy_group_id() username = current_token.get("context", {}).get("user", {}).get("name") private_key, key_db_entry = get_or_create_primary_service_account_key( user_id=user_id, username=username, proxy_group_id=proxy_group_id) # Make sure the service account key expiration is later # than the expiration for the signed url. If it's not, we need to # provision a new service account key. # # NOTE: This should occur very rarely: only when the service account key # already exists and is very close to expiring. # # If our scheduled maintainence script removes the url-signing key # before the expiration of the url then the url will NOT work # (even though the url itself isn't expired) if key_db_entry and key_db_entry.expires < expiration_time: private_key = create_primary_service_account_key( user_id=user_id, username=username, proxy_group_id=proxy_group_id) final_url = cirrus.google_cloud.utils.get_signed_url( resource_path, http_verb, expiration_time, extension_headers=None, content_type="", md5_value="", service_account_creds=private_key, ) return final_url
def get(self): """ List access keys for user **Example:** .. code-block:: http POST /credentials/apis/ HTTP/1.1 Content-Type: application/json Accept: application/json Info from Google API /serviceAccounts/<account>/keys endpoint but get the expiration time from our DB .. code-block:: JavaScript { "access_keys": [ { "keyAlgorithm": enum(ServiceAccountKeyAlgorithm), "validBeforeTime": string, "name": string, "validAfterTime": string, }, ... ] } """ client_id = current_token.get("azp") or None user_id = current_token["sub"] username = current_token.get("context", {}).get("user", {}).get("name") with GoogleCloudManager() as g_cloud_manager: proxy_group_id = get_or_create_proxy_group_id() service_account = get_or_create_service_account( client_id=client_id, user_id=user_id, username=username, proxy_group_id=proxy_group_id, ) keys = g_cloud_manager.get_service_account_keys_info( service_account.email) # replace Google's expiration date by the one in our DB reg = re.compile( ".+\/keys\/(.+)") # get key_id from xx/keys/key_id for i, key in enumerate(keys): key_id = reg.findall(key["name"])[0] db_entry = ( current_session.query(GoogleServiceAccountKey).filter_by( service_account_id=service_account.id).filter_by( key_id=key_id).first()) if db_entry: # convert timestamp to date - use the same format as Google API exp_date = datetime.utcfromtimestamp( db_entry.expires).strftime("%Y-%m-%dT%H:%M:%SZ") key["validBeforeTime"] = exp_date # the key exists in Google but not in our DB. This should not # happen! Delete the key from Google else: keys.pop(i) logger.warning( "No GoogleServiceAccountKey entry was found in the fence database for service account name {} for key_id {}, which exists in Google. It will now be deleted from Google." .format(username, key_id)) with GoogleCloudManager() as g_cloud: g_cloud.delete_service_account_key( service_account.email, key_id) result = {"access_keys": keys} return flask.jsonify(result)
def get_or_create_proxy_group_id(expires=None, user_id=None, username=None): """ If no username returned from token or database, create a new proxy group for the given user. Also, add the access privileges. Returns: int: id of (possibly newly created) proxy group associated with user """ proxy_group_id = _get_proxy_group_id(user_id=user_id, username=username) if not proxy_group_id: try: user_by_id = query_for_user_by_id(current_session, user_id) user_by_username = query_for_user( session=current_session, username=username ) except Exception: user_by_id = None user_by_username = None if user_by_id: user_id = user_id username = user_by_id.username elif user_by_username: user_id = user_by_username.id username = username elif current_token: user_id = current_token["sub"] username = current_token.get("context", {}).get("user", {}).get("name", "") else: raise Exception( f"could not find user given input user_id={user_id} or " f"username={username}, nor was there a current_token" ) proxy_group_id = _create_proxy_group(user_id, username).id privileges = current_session.query(AccessPrivilege).filter( AccessPrivilege.user_id == user_id ) for p in privileges: storage_accesses = p.project.storage_access for sa in storage_accesses: if sa.provider.name == STORAGE_ACCESS_PROVIDER_NAME: flask.current_app.storage_manager.logger.info( "grant {} access {} to {} in {}".format( username, p.privilege, p.project_id, p.auth_provider ) ) flask.current_app.storage_manager.grant_access( provider=(sa.provider.name), username=username, project=p.project, access=p.privilege, session=current_session, expires=expires, ) return proxy_group_id