def get_ca(ca): ''' Get the CA information for the provided ca. ''' try: ca_object = certificatemanager.get_ca(ca) except certificatemanager.CertificateAuthorityNotFoundError: return jsonify({'error': 'Provided CA not found.'}), 404 logged_in_user = authnz.get_logged_in_user() if not acl_module_check( resource_type='ca', action='get', resource_id=ca, ): msg = '{} does not have access to get ca {}'.format( authnz.get_logged_in_user(), ca, ) error_msg = {'error': msg, 'reference': ca} return jsonify(error_msg), 403 logging.info('get_ca called on id={} by user={}'.format( ca, logged_in_user, )) _ca = ca_object.get_certificate_authority_certificate() ca_response = CertificateAuthorityResponse( ca=_ca['ca'], certificate=_ca['certificate'], certificate_chain=_ca['certificate_chain'], tags=_ca['tags'], ) return certificate_authority_response_schema.dumps(ca_response)
def create_credential(): data = request.get_json() if not data.get('credential_pairs'): return jsonify({'error': 'credential_pairs is a required field'}), 400 if not isinstance(data.get('metadata', {}), dict): return jsonify({'error': 'metadata must be a dict'}), 400 # Ensure credential pair keys are lowercase credential_pairs = _lowercase_credential_pairs(data['credential_pairs']) _check, ret = _check_credential_pair_values(credential_pairs) if not _check: return jsonify(ret), 400 for cred in Credential.data_type_date_index.query( 'credential', name__eq=data['name']): # Conflict, the name already exists msg = 'Name already exists. See id: {0}'.format(cred.id) return jsonify({'error': msg, 'reference': cred.id}), 409 # Generate an initial stable ID to allow name changes id = str(uuid.uuid4()).replace('-', '') # Try to save to the archive revision = 1 credential_pairs = json.dumps(credential_pairs) data_key = keymanager.create_datakey(encryption_context={'id': id}) cipher = CipherManager(data_key['plaintext'], version=2) credential_pairs = cipher.encrypt(credential_pairs) cred = Credential( id='{0}-{1}'.format(id, revision), data_type='archive-credential', name=data['name'], credential_pairs=credential_pairs, metadata=data.get('metadata'), revision=revision, enabled=data.get('enabled'), data_key=data_key['ciphertext'], cipher_version=2, modified_by=authnz.get_logged_in_user() ).save(id__null=True) # Make this the current revision cred = Credential( id=id, data_type='credential', name=data['name'], credential_pairs=credential_pairs, metadata=data.get('metadata'), revision=revision, enabled=data.get('enabled'), data_key=data_key['ciphertext'], cipher_version=2, modified_by=authnz.get_logged_in_user() ) cred.save() return jsonify({ 'id': cred.id, 'name': cred.name, 'credential_pairs': json.loads(cipher.decrypt(cred.credential_pairs)), 'metadata': cred.metadata, 'revision': cred.revision, 'enabled': cred.enabled, 'modified_date': cred.modified_date, 'modified_by': cred.modified_by })
def create_credential(): data = request.get_json() if not data.get('credential_pairs'): return jsonify({'error': 'credential_pairs is a required field'}), 400 if not isinstance(data.get('metadata', {}), dict): return jsonify({'error': 'metadata must be a dict'}), 400 # Ensure credential pair keys are lowercase credential_pairs = _lowercase_credential_pairs(data['credential_pairs']) _check, ret = _check_credential_pair_values(credential_pairs) if not _check: return jsonify(ret), 400 for cred in Credential.data_type_date_index.query( 'credential', name__eq=data['name']): # Conflict, the name already exists msg = 'Name already exists. See id: {0}'.format(cred.id) return jsonify({'error': msg, 'reference': cred.id}), 409 # Generate an initial stable ID to allow name changes id = str(uuid.uuid4()).replace('-', '') # Try to save to the archive revision = 1 credential_pairs = json.dumps(credential_pairs) data_key = keymanager.create_datakey(encryption_context={'id': id}) cipher = CipherManager(data_key['plaintext'], version=2) credential_pairs = cipher.encrypt(credential_pairs) cred = Credential( id='{0}-{1}'.format(id, revision), data_type='archive-credential', name=data['name'], credential_pairs=credential_pairs, metadata=data.get('metadata'), revision=revision, enabled=data.get('enabled'), data_key=data_key['ciphertext'], cipher_version=2, modified_by=authnz.get_logged_in_user() ).save(id__null=True) # Make this the current revision cred = Credential( id=id, data_type='credential', name=data['name'], credential_pairs=credential_pairs, metadata=data.get('metadata'), revision=revision, enabled=data.get('enabled'), data_key=data_key['ciphertext'], cipher_version=2, modified_by=authnz.get_logged_in_user() ) cred.save() return jsonify({ 'id': cred.id, 'name': cred.name, 'credential_pairs': json.loads(cipher.decrypt(cred.credential_pairs)), 'metadata': cred.metadata, 'revision': cred.revision, 'enabled': cred.enabled, 'modified_date': cred.modified_date, 'modified_by': cred.modified_by })
def get_certificate_from_csr(ca): ''' Get a certificate from the ca provided in the url, using the CSR, validity and san provided in the POST body. ''' try: ca_object = certificatemanager.get_ca(ca) except certificatemanager.CertificateAuthorityNotFoundError: return jsonify({'error': 'Provided CA not found.'}), 404 data = request.get_json() if not data or not data.get('csr'): return jsonify( {'error': 'csr must be provided in the POST body.'}, ), 400 validity = data.get( 'validity', ca_object.settings['max_validity_days'], ) try: csr = ca_object.decode_csr(data['csr']) except Exception: logging.exception('Failed to decode PEM csr') return jsonify({'error': 'csr could not be decoded'}, ), 400 # Get the cn and san values from the csr object, so that we can use them # for the ACL check. cn = ca_object.get_csr_common_name(csr) san = ca_object.get_csr_san(csr) logged_in_user = authnz.get_logged_in_user() if not acl_module_check( resource_type='certificate', action='get', resource_id=cn, kwargs={ 'ca': ca, 'san': san, }, ): msg = ('{} does not have access to get certificate cn {} against' ' ca {}').format( authnz.get_logged_in_user(), cn, ca, ) error_msg = {'error': msg, 'reference': cn} return jsonify(error_msg), 403 logging.info('get_certificate called on id={} for ca={} by user={}'.format( cn, ca, logged_in_user, )) arn = ca_object.issue_certificate(data['csr'], validity) certificate = ca_object.get_certificate_from_arn(arn) certificate_response = CertificateResponse( certificate=certificate['certificate'], certificate_chain=certificate['certificate_chain'], ) return certificate_response_schema.dumps(certificate_response)
def get_certificate(ca, cn): ''' Get a certificate for the provided cn, using the provided CA. ''' try: ca_object = certificatemanager.get_ca(ca) except certificatemanager.CertificateAuthorityNotFoundError: return jsonify({'error': 'Provided CA not found.'}), 404 san = request.args.getlist('san') logged_in_user = authnz.get_logged_in_user() if not acl_module_check( resource_type='certificate', action='get', resource_id=cn, kwargs={ 'ca': ca, 'san': san, }, ): msg = ('{} does not have access to get certificate cn {} against' ' ca {}').format( authnz.get_logged_in_user(), cn, ca, ) error_msg = {'error': msg, 'reference': cn} return jsonify(error_msg), 403 logging.info('get_certificate called on id={} for ca={} by user={}'.format( cn, ca, logged_in_user, )) validity = request.args.get( 'validity', default=ca_object.settings['max_validity_days'], type=int, ) try: certificate = ca_object.issue_certificate_with_key( cn, validity, san, ) except certificatemanager.CertificateNotReadyError: # Ratelimit response for a locked certificate in the cache error_msg = 'Certificate being requested, please wait and try again.' response = jsonify(error_msg) response.retry_after = 2 return response, 429 certificate_response = CertificateResponse( certificate=certificate['certificate'], certificate_chain=certificate['certificate_chain'], key=certificate['key'], ) return certificate_expanded_response_schema.dumps(certificate_response)
def test_get_logged_in_user(mocker): mocker.patch('confidant.authnz.settings.USER_EMAIL_SUFFIX', 'example.com') app = create_app() with app.test_request_context('/v1/user/email'): with pytest.raises(authnz.UserUnknownError): authnz.get_logged_in_user() g_mock = mocker.patch('confidant.authnz.g') g_mock.username = '******' assert authnz.get_logged_in_user() == 'unittestuser'
def test_get_logged_in_user(self): with app.test_request_context('/v1/user/email'): with self.assertRaises(authnz.UserUnknownError): authnz.get_logged_in_user() with patch('confidant.authnz.g') as g_mock: g_mock.username = '******' self.assertEqual(authnz.get_logged_in_user(), 'unittestuser') session_data = {'user': {'email': '*****@*****.**'}} with patch('confidant.authnz.userauth.session', session_data): self.assertEqual(authnz.get_logged_in_user(), '*****@*****.**')
def list_cas(): ''' List the configured CAs. .. :quickref: Certificate Authorities; Get a list of the detailed certificate authorities configured on the server. **Example request**: .. sourcecode:: http GET /v1/cas **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "cas": [ "example-ca": { "certificate": "---...BEGIN...", "certificate_chain": "---...BEGIN...", "tags": { "hello": "world" }, ... ] } :resheader Content-Type: application/json :statuscode 200: success :statuscode 403: client does not have access to list CAs ''' logged_in_user = authnz.get_logged_in_user() if not acl_module_check( resource_type='ca', action='list', ): msg = '{} does not have access to list cas'.format( authnz.get_logged_in_user(), ) error_msg = {'error': msg} return jsonify(error_msg), 403 cas = certificatemanager.list_cas() logger.info('list_cas called by user={}'.format(logged_in_user)) cas_response = CertificateAuthoritiesResponse.from_cas(cas) return certificate_authorities_response_schema.dumps(cas_response)
def test_will_extract_from_request(self): with app.test_request_context('/fake'): # No headers given: an error with self.assertRaises(authnz.UserUnknownError): authnz.get_logged_in_user() # Both headers given: success with patch('confidant.authnz.userauth.request') as request_mock: request_mock.headers = { app.config['HEADER_AUTH_USERNAME_HEADER']: 'unittestuser', app.config['HEADER_AUTH_EMAIL_HEADER']: '*****@*****.**', } self.assertEqual(authnz.get_logged_in_user(), '*****@*****.**')
def test_header_auth_will_extract_from_request(mocker, mock_header_auth): app = create_app() with app.test_request_context('/fake'): # No headers given: an error with pytest.raises(authnz.UserUnknownError): authnz.get_logged_in_user() # Both headers given: success request_mock = mocker.patch('confidant.authnz.userauth.request') request_mock.headers = { 'X-Confidant-Username': '******', 'X-Confidant-Email': '*****@*****.**', # noqa:E501 } assert authnz.get_logged_in_user() == '*****@*****.**'
def test_get_logged_in_user(self): with app.test_request_context('/v1/user/email'): with self.assertRaises(authnz.UserUnknownError): authnz.get_logged_in_user() with patch('confidant.authnz.g') as g_mock: g_mock.username = '******' self.assertEqual(authnz.get_logged_in_user(), 'unittestuser') session_data = { 'user': {'email': '*****@*****.**'} } with patch('confidant.authnz.userauth.session', session_data): self.assertEqual( authnz.get_logged_in_user(), '*****@*****.**' )
def ensure_grants(id): # we pass [] in for the credential IDs, because this action isn't related # to adding or removing credentials, but just a generic update of a # service. if not acl_module_check(resource_type='service', action='update', resource_id=id, kwargs={ 'credential_ids': [], }): msg = "{} does not have access to ensure grants for service {}" msg = msg.format(authnz.get_logged_in_user(), id) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 try: _service = Service.get(id) if _service.data_type != 'service': msg = 'id provided is not a service.' return jsonify({'error': msg}), 400 except DoesNotExist: msg = 'id provided does not exist.' return jsonify({'error': msg}), 400 try: keymanager.ensure_grants(id) except keymanager.ServiceCreateGrantError: msg = 'Failed to add grants for service.' logging.error(msg) return jsonify({'error': msg}), 400 try: grants = keymanager.grants_exist(id) except keymanager.ServiceGetGrantError: msg = 'Failed to get grants.' return jsonify({'error': msg}), 500 return jsonify({'id': id, 'grants': grants})
def get_archive_service_revisions(id): if not acl_module_check(resource_type='service', action='metadata', resource_id=id): msg = "{} does not have access to service {} revisions".format( authnz.get_logged_in_user(), id ) error_msg = {'error': msg} return jsonify(error_msg), 403 try: service = Service.get(id) except DoesNotExist: logging.warning( 'Item with id {0} does not exist.'.format(id) ) return jsonify({}), 404 if (service.data_type != 'service' and service.data_type != 'archive-service'): return jsonify({}), 404 _range = range(1, service.revision + 1) ids = [] for i in _range: ids.append("{0}-{1}".format(id, i)) revisions_response = RevisionsResponse.from_services( Service.batch_get(ids) ) return revisions_response_schema.dumps(revisions_response)
def get_archive_service_list(): if not acl_module_check(resource_type='service', action='list'): msg = "{} does not have access to list services".format( authnz.get_logged_in_user()) error_msg = {'error': msg} return jsonify(error_msg), 403 limit = request.args.get( 'limit', default=settings.HISTORY_PAGE_LIMIT, type=int, ) page = request.args.get('page', default=None, type=str) if page: try: page = decode_last_evaluated_key(page) except Exception: logging.exception('Failed to parse provided page') return jsonify({'error': 'Failed to parse page'}), 400 results = Service.data_type_date_index.query( 'archive-service', scan_index_forward=False, limit=limit, last_evaluated_key=page, ) services_response = ServicesResponse.from_services( [service for service in results], next_page=results.last_evaluated_key, ) return services_response_schema.dumps(services_response)
def get_credential_list(): """ Returns a list of the metadata of all the current revision of credentials. .. :quickref: Credentials; Get a list of the metadata for all current credential revisions. **Example request**: .. sourcecode:: http GET /v1/credentials :query string next_page: If paged results were returned in a call, this query string can be used to fetch the next page. **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "credentials": [ { "id": "abcd12345bf4f1cafe8e722d3860404", "name": "Example Credential", "credential_keys": [], "credential_pairs": {}, "metadata": { "example_metadata_key": "example_value" }, "revision": 1, "enabled": true, "documentation": "Example documentation", "modified_date": "2019-12-16T23:16:11.413299+00:00", "modified_by": "*****@*****.**", "permissions": {} }, ... ], next_page: null } :resheader Content-Type: application/json :statuscode 200: Success :statuscode 403: Client does not have permissions to list credentials. """ if not acl_module_check(resource_type='credential', action='list'): msg = "{} does not have access to list credentials".format( authnz.get_logged_in_user()) error_msg = {'error': msg} return jsonify(error_msg), 403 credentials_response = CredentialsResponse.from_credentials([ credential for credential in Credential.data_type_date_index.query('credential') ]) return credentials_response_schema.dumps(credentials_response)
def get_user_info(): ''' Get the email associated with the currently authenticated user. .. :quickref: Email Address; Get the email address associated with the currently authenticated user. **Example request**: .. sourcecode:: http GET /v1/user/email **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "email": "*****@*****.**" } :resheader Content-Type: application/json :statuscode 200: Success ''' try: response = jsonify({'email': authnz.get_logged_in_user()}) except authnz.UserUnknownError: response = jsonify({'email': None}) return response
def get_grants(id): if not acl_module_check( resource_type='service', action='metadata', resource_id=id, ): msg = "{} does not have access to get grants for service {}" msg = msg.format(authnz.get_logged_in_user(), id) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 try: _service = Service.get(id) if _service.data_type != 'service': msg = 'id provided is not a service.' return jsonify({'error': msg}), 400 except DoesNotExist: msg = 'id provided does not exist.' return jsonify({'error': msg}), 400 try: grants = keymanager.grants_exist(id) except keymanager.ServiceGetGrantError: msg = 'Failed to get grants.' return jsonify({'error': msg}), 500 return jsonify({ 'id': id, 'grants': grants })
def diff_service(id, old_revision, new_revision): if not acl_module_check(resource_type='service', action='metadata', resource_id=id): msg = "{} does not have access to diff service {}".format( authnz.get_logged_in_user(), id ) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 try: old_service = Service.get('{}-{}'.format(id, old_revision)) except DoesNotExist: return jsonify({'error': 'Service not found.'}), 404 if old_service.data_type != 'archive-service': msg = 'id provided is not a service.' return jsonify({'error': msg}), 400 try: new_service = Service.get('{}-{}'.format(id, new_revision)) except DoesNotExist: logging.warning( 'Item with id {0} does not exist.'.format(id) ) return jsonify({}), 404 if new_service.data_type != 'archive-service': msg = 'id provided is not a service.' return jsonify({'error': msg}), 400 return jsonify(old_service.diff(new_service))
def test_get_logged_in_user_from_session(mocker): mocker.patch('confidant.authnz.settings.USER_EMAIL_SUFFIX', 'example.com') app = create_app() with app.test_request_context('/v1/user/email'): session_data = {'user': {'email': '*****@*****.**'}} mocker.patch('confidant.authnz.userauth.session', session_data) assert authnz.get_logged_in_user() == '*****@*****.**'
def test_will_extract_from_request(self): with app.test_request_context('/fake'): # No headers given: an error with self.assertRaises(authnz.UserUnknownError): authnz.get_logged_in_user() # Both headers given: success with patch('confidant.authnz.userauth.request') as request_mock: request_mock.headers = { app.config['HEADER_AUTH_USERNAME_HEADER']: 'unittestuser', app.config['HEADER_AUTH_EMAIL_HEADER']: '*****@*****.**', } self.assertEqual(authnz.get_logged_in_user(), '*****@*****.**')
def get_user_info(): ''' Get the email address of the currently logged-in user. ''' try: response = jsonify({'email': authnz.get_logged_in_user()}) except authnz.UserUnknownError: response = jsonify({'email': None}) return response
def get_user_info(): ''' Get the email address of the currently logged-in user. ''' try: response = jsonify({'email': authnz.get_logged_in_user()}) except authnz.UserUnknownError: response = jsonify({'email': None}) return response
def get_iam_roles_list(): if not acl_module_check(resource_type='service', action='list'): msg = "{} does not have access to list services".format( authnz.get_logged_in_user()) error_msg = {'error': msg} return jsonify(error_msg), 403 roles = iamrolemanager.get_iam_roles() return jsonify({'roles': roles})
def get_service_list(): if not acl_module_check(resource_type='service', action='list'): msg = "{} does not have access to list services".format( authnz.get_logged_in_user()) error_msg = {'error': msg} return jsonify(error_msg), 403 services_response = ServicesResponse.from_services( Service.data_type_date_index.query('service')) return services_response_schema.dumps(services_response)
def get_credential(id): if not acl_module_check( resource_type='credential', action='metadata', resource_id=id): msg = "{} does not have access to credential {}".format( authnz.get_logged_in_user(), id) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 try: credential = Credential.get(id) except DoesNotExist: logging.warning('Item with id {0} does not exist.'.format(id)) return jsonify({}), 404 if (credential.data_type != 'credential' and credential.data_type != 'archive-credential'): return jsonify({}), 404 permissions = { 'metadata': True, 'get': False, 'update': acl_module_check(resource_type='credential', action='update', resource_id=id), } include_credential_pairs = False if acl_module_check(resource_type='credential', action='get', resource_id=id): permissions['get'] = True include_credential_pairs = True log_line = "{0} get credential {1}".format(authnz.get_logged_in_user(), id) logging.info(log_line) credential_response = CredentialResponse.from_credential( credential, include_credential_keys=True, include_credential_pairs=include_credential_pairs, ) credential_response.permissions = permissions return credential_response_schema.dumps(credential_response)
def get_service_list(): """ Get a list of current service revisions. .. :quickref: Services; Get a list of current service revisions. **Example request**: .. sourcecode:: http GET /v1/services :query string next_page: If paged results were returned in a call, this query string can be used to fetch the next page. **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "services": [ { "id": "example-development", "revision": 1, "enabled": true, "modified_date": "2019-12-16T23:16:11.413299+00:00", "modified_by": "*****@*****.**", "account": null, "credentials": [], "blind_credentials": [], "permissions": {} }, ... ], "next_page": null } :resheader Content-Type: application/json :statuscode 200: Success :statuscode 403: Client does not have permissions to list services. """ if not acl_module_check(resource_type='service', action='list'): msg = "{} does not have access to list services".format( authnz.get_logged_in_user() ) error_msg = {'error': msg} return jsonify(error_msg), 403 services_response = ServicesResponse.from_services( Service.data_type_date_index.query('service') ) return services_response_schema.dumps(services_response)
def get_credential_dependencies(id): if not acl_module_check( resource_type='credential', action='metadata', resource_id=id): msg = "{} does not have access to get dependencies for credential {}" msg = msg.format(authnz.get_logged_in_user(), id) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 services = servicemanager.get_services_for_credential(id) _services = [{'id': x.id, 'enabled': x.enabled} for x in services] return jsonify({'services': _services})
def get_user_info(): ''' Get the email address of the currently logged-in user. ''' try: #email에 대한 JSON타입의 http response 생성된 값을 response변수에 대입 response = jsonify({'email': authnz.get_logged_in_user()}) except authnz.UserUnknownError: #UserUnknownError가 뜰 시 none을 response변수에 대입 response = jsonify({'email': None}) #response 변수 리턴 return response
def get_credential_list(): if not acl_module_check(resource_type='credential', action='list'): msg = "{} does not have access to list credentials".format( authnz.get_logged_in_user()) error_msg = {'error': msg} return jsonify(error_msg), 403 credentials_response = CredentialsResponse.from_credentials([ credential for credential in Credential.data_type_date_index.query('credential') ]) return credentials_response_schema.dumps(credentials_response)
def list_cas(): ''' List the configured CAs. ''' logged_in_user = authnz.get_logged_in_user() if not acl_module_check( resource_type='ca', action='list', ): msg = '{} does not have access to list cas'.format( authnz.get_logged_in_user(), ) error_msg = {'error': msg} return jsonify(error_msg), 403 cas = certificatemanager.list_cas() logging.info('list_cas called by user={}'.format(logged_in_user)) cas_response = CertificateAuthoritiesResponse.from_cas(cas) return certificate_authorities_response_schema.dumps(cas_response)
def get_iam_roles_list(): """ Get a list of IAM roles from the configured AWS account. .. :quickref: IAM Roles; Get a list of IAM roles from the configured AWS account. **Example request**: .. sourcecode:: http GET /v1/roles **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "roles": [ 'example-development', 'example2-development', ... ] } :resheader Content-Type: application/json :statuscode 200: Success :statuscode 403: Client does not have permissions to list services. """ if not acl_module_check(resource_type='service', action='list'): msg = "{} does not have access to list services".format( authnz.get_logged_in_user() ) error_msg = {'error': msg} return jsonify(error_msg), 403 roles = iamrolemanager.get_iam_roles() return jsonify({'roles': roles})
def get_credential_dependencies(id): """ Returns a list of services that this credential is mapped to. .. :quickref: Credential Mappings; Get a list of services that this credential is mapped to. **Example request**: .. sourcecode:: http GET /v1/credentials/abcd12345bf4f1cafe8e722d3860404/services **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "services": ["example-development", "example2-development"] } :resheader Content-Type: application/json :statuscode 200: Success :statuscode 403: Client does not have permissions to get metadata for the provided credential. """ if not acl_module_check( resource_type='credential', action='metadata', resource_id=id): msg = "{} does not have access to get dependencies for credential {}" msg = msg.format(authnz.get_logged_in_user(), id) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 services = servicemanager.get_services_for_credential(id) _services = [{'id': x.id, 'enabled': x.enabled} for x in services] return jsonify({'services': _services})
def get_grants(id): """ Get grants for the provided service ID. .. :quickref: KMS Grants; Get grants for the provided service ID. **Example request**: .. sourcecode:: http GET /v1/grants/example-development :param id: The service ID to ensure grants for. :type id: str **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "id": "example-development", "grants": { "encrypt_grant": true, "decrypt_grant": true } } :resheader Content-Type: application/json :statuscode 200: Success :statuscode 400: Invalid input. The service provided does not exist. :statuscode 403: Client does not have permissions to get service metadata for the specified service ID. """ if not acl_module_check( resource_type='service', action='metadata', resource_id=id, ): msg = "{} does not have access to get grants for service {}" msg = msg.format(authnz.get_logged_in_user(), id) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 try: _service = Service.get(id) if _service.data_type != 'service': msg = 'id provided is not a service.' return jsonify({'error': msg}), 400 except DoesNotExist: msg = 'id provided does not exist.' return jsonify({'error': msg}), 400 try: grants = keymanager.grants_exist(id) except keymanager.ServiceGetGrantError: msg = 'Failed to get grants.' return jsonify({'error': msg}), 500 return jsonify({ 'id': id, 'grants': grants })
def ensure_grants(id): """ Ensure grants are set for the provided service ID. .. :quickref: KMS Grants; Ensure grants are set for the provided service ID. **Example request**: .. sourcecode:: http PUT /v1/grants/example-development :param id: The service ID to ensure grants for. :type id: str **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "id": "example-development", "grants": { "encrypt_grant": true, "decrypt_grant": true } } :resheader Content-Type: application/json :statuscode 200: Success :statuscode 400: Invalid input. The service provided does not exist. :statuscode 403: Client does not have permissions to create or update the specified service ID. """ # we pass [] in for the credential IDs, because this action isn't related # to adding or removing credentials, but just a generic update of a # service. if not acl_module_check( resource_type='service', action='update', resource_id=id, kwargs={ 'credential_ids': [], } ): msg = "{} does not have access to ensure grants for service {}" msg = msg.format(authnz.get_logged_in_user(), id) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 try: _service = Service.get(id) if _service.data_type != 'service': msg = 'id provided is not a service.' return jsonify({'error': msg}), 400 except DoesNotExist: msg = 'id provided does not exist.' return jsonify({'error': msg}), 400 try: keymanager.ensure_grants(id) except keymanager.ServiceCreateGrantError: msg = 'Failed to add grants for service.' logger.error(msg) return jsonify({'error': msg}), 400 try: grants = keymanager.grants_exist(id) except keymanager.ServiceGetGrantError: msg = 'Failed to get grants.' return jsonify({'error': msg}), 500 return jsonify({ 'id': id, 'grants': grants })
def update_blind_credential(id): try: _cred = BlindCredential.get(id) except Credential.DoesNotExist: return jsonify({'error': 'Blind credential not found.'}), 404 if _cred.data_type != 'blind-credential': msg = 'id provided is not a blind-credential.' return jsonify({'error': msg}), 400 data = request.get_json() update = {} revision = _get_latest_blind_credential_revision(id, _cred.revision) update['name'] = data.get('name', _cred.name) if 'enabled' in data: if not isinstance(data['enabled'], bool): return jsonify({'error': 'Enabled must be a boolean.'}), 400 update['enabled'] = data['enabled'] else: update['enabled'] = _cred.enabled if not isinstance(data.get('metadata', {}), dict): return jsonify({'error': 'metadata must be a dict'}), 400 services = _get_services_for_blind_credential(id) if 'credential_pairs' in data: for key in ['data_key', 'cipher_type', 'cipher_version']: if key not in data: msg = '{0} required when updating credential_pairs.' msg = msg.format(key) return jsonify({'error': msg}), 400 update['credential_pairs'] = data['credential_pairs'] update['credential_keys'] = data.get('credential_keys', []) if not isinstance(update['credential_keys'], list): return jsonify({ 'error': 'credential_keys must be a list.' }), 400 # Ensure credential keys don't conflicts with pairs from other # services conflicts = _pair_key_conflicts_for_services( id, data['credential_keys'], services ) if conflicts: ret = { 'error': 'Conflicting key pairs in mapped service.', 'conflicts': conflicts } return jsonify(ret), 400 if not isinstance(data['data_key'], dict): return jsonify({ 'error': 'data_key must be a dict with a region/key mapping.' }), 400 update['data_key'] = data['data_key'] update['cipher_type'] = data['cipher_type'] update['cipher_version'] = data['cipher_version'] else: update['credential_pairs'] = _cred.credential_pairs update['credential_keys'] = _cred.credential_keys update['data_key'] = _cred.data_key update['cipher_type'] = _cred.cipher_type update['cipher_version'] = _cred.cipher_version update['metadata'] = data.get('metadata', _cred.metadata) # Try to save to the archive try: BlindCredential( id='{0}-{1}'.format(id, revision), data_type='archive-blind-credential', name=update['name'], credential_pairs=update['credential_pairs'], credential_keys=update['credential_keys'], metadata=update['metadata'], revision=revision, enabled=update['enabled'], data_key=update['data_key'], cipher_type=update['cipher_type'], cipher_version=update['cipher_version'], modified_by=authnz.get_logged_in_user() ).save(id__null=True) except PutError as e: logging.error(e) return jsonify( {'error': 'Failed to add blind-credential to archive.'} ), 500 try: cred = BlindCredential( id=id, data_type='blind-credential', name=update['name'], credential_pairs=update['credential_pairs'], credential_keys=update['credential_keys'], metadata=update['metadata'], revision=revision, enabled=update['enabled'], data_key=update['data_key'], cipher_type=update['cipher_type'], cipher_version=update['cipher_version'], modified_by=authnz.get_logged_in_user() ) cred.save() except PutError as e: logging.error(e) return jsonify( {'error': 'Failed to update active blind-credential.'} ), 500 if services: service_names = [x.id for x in services] msg = 'Updated credential "{0}" ({1}); Revision {2}' msg = msg.format(cred.name, cred.id, cred.revision) graphite.send_event(service_names, msg) webhook.send_event('blind_credential_update', service_names, [cred.id]) return jsonify({ 'id': cred.id, 'name': cred.name, 'credential_pairs': cred.credential_pairs, 'credential_keys': list(cred.credential_keys), 'cipher_type': cred.cipher_type, 'cipher_version': cred.cipher_version, 'metadata': cred.metadata, 'revision': cred.revision, 'enabled': cred.enabled, 'data_key': cred.data_key, 'modified_date': cred.modified_date, 'modified_by': cred.modified_by })
def map_service_credentials(id): data = request.get_json() try: _service = Service.get(id) if _service.data_type != 'service': msg = 'id provided is not a service.' return jsonify({'error': msg}), 400 revision = _service.revision + 1 _service_credential_ids = _service.credentials except Service.DoesNotExist: revision = 1 _service_credential_ids = [] if data.get('credentials') or data.get('blind_credentials'): conflicts = _pair_key_conflicts_for_credentials( data.get('credentials', []), data.get('blind_credentials', []), ) if conflicts: ret = { 'error': 'Conflicting key pairs in mapped service.', 'conflicts': conflicts } return jsonify(ret), 400 accounts = app.config['SCOPED_AUTH_KEYS'].values() if data.get('account') and data['account'] not in accounts: ret = {'error': '{0} is not a valid account.'} return jsonify(ret), 400 # If this is the first revision, we should attempt to create a grant for # this service. if revision == 1: try: keymanager.ensure_grants(id) except keymanager.ServiceCreateGrantError: msg = 'Failed to add grants for {0}.'.format(id) logging.error(msg) # Try to save to the archive try: Service( id='{0}-{1}'.format(id, revision), data_type='archive-service', credentials=data.get('credentials'), blind_credentials=data.get('blind_credentials'), account=data.get('account'), enabled=data.get('enabled'), revision=revision, modified_by=authnz.get_logged_in_user() ).save(id__null=True) except PutError as e: logging.error(e) return jsonify({'error': 'Failed to add service to archive.'}), 500 try: service = Service( id=id, data_type='service', credentials=data.get('credentials'), blind_credentials=data.get('blind_credentials'), account=data.get('account'), enabled=data.get('enabled'), revision=revision, modified_by=authnz.get_logged_in_user() ) service.save() except PutError as e: logging.error(e) return jsonify({'error': 'Failed to update active service.'}), 500 added = list(set(service.credentials) - set(_service_credential_ids)) removed = list(set(_service_credential_ids) - set(service.credentials)) msg = 'Added credentials: {0}; Removed credentials {1}; Revision {2}' msg = msg.format(added, removed, service.revision) graphite.send_event([id], msg) webhook.send_event('service_update', [service.id], service.credentials) try: credentials = _get_credentials(service.credentials) except KeyError: return jsonify({'error': 'Decryption error.'}), 500 blind_credentials = _get_blind_credentials(service.blind_credentials) return jsonify({ 'id': service.id, 'account': service.account, 'credentials': credentials, 'blind_credentials': blind_credentials, 'revision': service.revision, 'enabled': service.enabled, 'modified_date': service.modified_date, 'modified_by': service.modified_by })
def create_blind_credential(): data = request.get_json() missing = [] for arg in ['cipher_version', 'cipher_type', 'credential_pairs', 'data_key']: if not data.get(arg): missing.append(arg) if missing: return jsonify({ 'error': 'The following fields are required: {0}'.format(missing) }), 400 if not isinstance(data['data_key'], dict): return jsonify({ 'error': 'data_key must be a dict with a region/key mapping.' }), 400 if not isinstance(data.get('credential_keys', []), list): return jsonify({ 'error': 'credential_keys must be a list.' }), 400 if not isinstance(data.get('metadata', {}), dict): return jsonify({'error': 'metadata must be a dict'}), 400 for cred in BlindCredential.data_type_date_index.query( 'blind-credential', name__eq=data['name']): # Conflict, the name already exists msg = 'Name already exists. See id: {0}'.format(cred.id) return jsonify({'error': msg, 'reference': cred.id}), 409 # Generate an initial stable ID to allow name changes id = str(uuid.uuid4()).replace('-', '') # Try to save to the archive revision = 1 cred = BlindCredential( id='{0}-{1}'.format(id, revision), data_type='archive-blind-credential', name=data['name'], credential_pairs=data['credential_pairs'], credential_keys=data.get('credential_keys'), metadata=data.get('metadata'), revision=revision, enabled=data.get('enabled'), data_key=data['data_key'], cipher_type=data['cipher_type'], cipher_version=data['cipher_version'], modified_by=authnz.get_logged_in_user() ).save(id__null=True) # Make this the current revision cred = BlindCredential( id=id, data_type='blind-credential', name=data['name'], credential_pairs=data['credential_pairs'], credential_keys=data.get('credential_keys'), metadata=data.get('metadata'), revision=revision, enabled=data.get('enabled'), data_key=data['data_key'], cipher_type=data['cipher_type'], cipher_version=data['cipher_version'], modified_by=authnz.get_logged_in_user() ) cred.save() return jsonify({ 'id': cred.id, 'name': cred.name, 'credential_pairs': cred.credential_pairs, 'credential_keys': list(cred.credential_keys), 'cipher_type': cred.cipher_type, 'cipher_version': cred.cipher_version, 'metadata': cred.metadata, 'revision': cred.revision, 'enabled': cred.enabled, 'data_key': cred.data_key, 'modified_date': cred.modified_date, 'modified_by': cred.modified_by })
def update_credential(id): try: _cred = Credential.get(id) except Credential.DoesNotExist: return jsonify({'error': 'Credential not found.'}), 404 if _cred.data_type != 'credential': msg = 'id provided is not a credential.' return jsonify({'error': msg}), 400 data = request.get_json() update = {} revision = _get_latest_credential_revision(id, _cred.revision) update['name'] = data.get('name', _cred.name) if 'enabled' in data: if not isinstance(data['enabled'], bool): return jsonify({'error': 'Enabled must be a boolean.'}), 400 update['enabled'] = data['enabled'] else: update['enabled'] = _cred.enabled if not isinstance(data.get('metadata', {}), dict): return jsonify({'error': 'metadata must be a dict'}), 400 services = _get_services_for_credential(id) if 'credential_pairs' in data: # Ensure credential pair keys are lowercase credential_pairs = _lowercase_credential_pairs( data['credential_pairs'] ) _check, ret = _check_credential_pair_values(credential_pairs) if not _check: return jsonify(ret), 400 # Ensure credential pairs don't conflicts with pairs from other # services conflicts = _pair_key_conflicts_for_services( id, credential_pairs.keys(), services ) if conflicts: ret = { 'error': 'Conflicting key pairs in mapped service.', 'conflicts': conflicts } return jsonify(ret), 400 update['credential_pairs'] = json.dumps(credential_pairs) else: data_key = keymanager.decrypt_datakey( _cred.data_key, encryption_context={'id': id} ) cipher_version = _cred.cipher_version cipher = CipherManager(data_key, cipher_version) update['credential_pairs'] = cipher.decrypt(_cred.credential_pairs) data_key = keymanager.create_datakey(encryption_context={'id': id}) cipher = CipherManager(data_key['plaintext'], version=2) credential_pairs = cipher.encrypt(update['credential_pairs']) update['metadata'] = data.get('metadata', _cred.metadata) # Try to save to the archive try: Credential( id='{0}-{1}'.format(id, revision), name=update['name'], data_type='archive-credential', credential_pairs=credential_pairs, metadata=update['metadata'], enabled=update['enabled'], revision=revision, data_key=data_key['ciphertext'], cipher_version=2, modified_by=authnz.get_logged_in_user() ).save(id__null=True) except PutError as e: logging.error(e) return jsonify({'error': 'Failed to add credential to archive.'}), 500 try: cred = Credential( id=id, name=update['name'], data_type='credential', credential_pairs=credential_pairs, metadata=update['metadata'], enabled=update['enabled'], revision=revision, data_key=data_key['ciphertext'], cipher_version=2, modified_by=authnz.get_logged_in_user() ) cred.save() except PutError as e: logging.error(e) return jsonify({'error': 'Failed to update active credential.'}), 500 if services: service_names = [x.id for x in services] msg = 'Updated credential "{0}" ({1}); Revision {2}' msg = msg.format(cred.name, cred.id, cred.revision) graphite.send_event(service_names, msg) webhook.send_event('credential_update', service_names, [cred.id]) return jsonify({ 'id': cred.id, 'name': cred.name, 'credential_pairs': json.loads(cipher.decrypt(cred.credential_pairs)), 'metadata': cred.metadata, 'revision': cred.revision, 'enabled': cred.enabled, 'modified_date': cred.modified_date, 'modified_by': cred.modified_by })