def test_equals(mocker): service1 = Service( id='test', credentials=['abc', 'def'], blind_credentials=['123', '456'], account='test-account', ) service2 = Service( id='test', credentials=['abc', 'def'], blind_credentials=['123', '456'], account='test-account', ) assert service1.equals(service2) is True
def test_diff(mocker): modified_by = '*****@*****.**' modified_date_old = datetime.now modified_date_new = datetime.now old = Service( id='test', revision=1, credentials=['abc', 'def'], blind_credentials=['123', '456'], account='test-account', modified_by=modified_by, modified_date=modified_date_old, ) new = Service( id='test', revision=1, credentials=['def'], blind_credentials=['123', '456', '789'], account='test-account2', modified_by=modified_by, modified_date=modified_date_new, ) # If the revisions are the same we short-circuit, so even if the contents # differ, there should be no diff. assert old.diff(new) == {} new.revision = 2 expected_diff = { 'credentials': { 'removed': ['abc'], }, 'blind_credentials': { 'added': ['789'], }, 'account': { 'removed': 'test-account', 'added': 'test-account2', }, 'modified_by': { 'removed': '*****@*****.**', 'added': '*****@*****.**', }, 'modified_date': { 'removed': modified_date_old, 'added': modified_date_new, }, } assert old.diff(new) == expected_diff
def test_valid_kms_auth_token_good_account(self, keymanager_mock): app.debug = True app.config['USE_AUTH'] = True service = Service( id='confidant-development', data_type='service', account='sandbox', credentials=[], blind_credentials=[], enabled=True, revision=1, modified_by='test-user' ) service.save() keymanager_mock.return_value = { "payload": {"fake": "payload"}, "key_alias": "sandbox-auth-key" } auth = base64.b64encode( '{0}:{1}'.format('confidant-development', 'faketoken') ) ret = self.test_client.open( '/v1/services/confidant-development', 'GET', headers={'Authorization': 'Basic {0}'.format(auth)}, follow_redirects=False ) service.delete() self.assertEquals(ret.status_code, 200)
def mapped_service(mocker): mocked = mocker.patch.object(Service, 'data_type_date_index') mocked.query = mocker.Mock( return_value=[Service(id='test-service', revision=1, enabled=True)], ) mocker.patch( 'confidant.scripts.archive.ArchiveCredentials.credential_in_service', return_value=True, )
def revert_service_to_revision(id, to_revision): ''' Revert the provided service to the provided revision. .. :quickref: Service; Revert the provided service to the provided revision **Example request**: .. sourcecode:: http PUT /v1/services/example-development/1 :param id: The service ID to revert. :type id: str :param to_revision: The revision to revert this service to. :type to_revision: int **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "id": "abcd12345bf4f1cafe8e722d3860404", "name": "Example Credential", "credential_keys": ["test_key"], "credential_pairs": { "test_key": "test_value" }, "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": {} } :resheader Content-Type: application/json :statuscode 200: Success :statuscode 400: Invalid input; the update would create conflicting credential keys in the service mapping. :statuscode 403: Client does not have access to revert the provided service ID. ''' if not acl_module_check(resource_type='service', action='revert', resource_id=id): msg = "{} does not have access to revert service {}".format( authnz.get_logged_in_user(), id ) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 try: current_service = Service.get(id) except DoesNotExist: logger.warning( 'Item with id {0} does not exist.'.format(id) ) return jsonify({}), 404 if current_service.data_type != 'service': msg = 'id provided is not a service.' return jsonify({'error': msg}), 400 new_revision = servicemanager.get_latest_service_revision( id, current_service.revision ) try: revert_service = Service.get('{}-{}'.format(id, to_revision)) except DoesNotExist: logger.warning( 'Item with id {0} does not exist.'.format(id) ) return jsonify({}), 404 if revert_service.data_type != 'archive-service': msg = 'id provided is not a service.' return jsonify({'error': msg}), 400 if revert_service.credentials or revert_service.blind_credentials: conflicts = credentialmanager.pair_key_conflicts_for_credentials( revert_service.credentials, revert_service.blind_credentials, ) if conflicts: ret = { 'error': 'Conflicting key pairs in mapped service.', 'conflicts': conflicts } return jsonify(ret), 400 credentials = credentialmanager.get_credentials( revert_service.credentials, ) blind_credentials = credentialmanager.get_blind_credentials( revert_service.blind_credentials, ) # Use the IDs from the fetched IDs, to ensure we filter any archived # credential IDs. revert_service.credentials = [cred.id for cred in credentials] if revert_service.equals(current_service): ret = { 'error': 'No difference between old and new service.' } return jsonify(ret), 400 # Try to save to the archive try: Service( id='{0}-{1}'.format(id, new_revision), data_type='archive-service', credentials=revert_service.credentials, blind_credentials=revert_service.blind_credentials, account=revert_service.account, enabled=revert_service.enabled, revision=new_revision, modified_by=authnz.get_logged_in_user() ).save(id__null=True) except PutError as e: logger.error(e) return jsonify({'error': 'Failed to add service to archive.'}), 500 try: service = Service( id=id, data_type='service', credentials=revert_service.credentials, blind_credentials=revert_service.blind_credentials, account=revert_service.account, enabled=revert_service.enabled, revision=new_revision, modified_by=authnz.get_logged_in_user() ) service.save() except PutError as e: logger.error(e) return jsonify({'error': 'Failed to update active service.'}), 500 servicemanager.send_service_mapping_graphite_event(service, current_service) webhook.send_event( 'service_update', [service.id], service.credentials, ) return service_expanded_response_schema.dumps( ServiceResponse.from_service_expanded( service, credentials=credentials, blind_credentials=blind_credentials, ) )
def map_service_credentials(id): """ Create or update a service to credential mapping. .. :quickref: Service; Create or update a service to credential mapping with the data provided in the PUT body. **Example request**: .. sourcecode:: http PUT /v1/services/example-development :param id: The service ID to create or update. :type id: str :<json List[string] credentials: A list of credential IDs to map to this service. :<json List[string] blind_credentials: A list of blind_credential IDs to map to this service. :<json boolean enabled: Whether or not this service is enabled. (default: true) :<json string account: An AWS account to scope this service to. **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "id": "example-development", "revision": 1, "enabled": true, "modified_date": "2019-12-16T23:16:11.413299+00:00", "modified_by": "*****@*****.**", "account": null, "credentials": [ { "id": "abcd12345bf4f1cafe8e722d3860404", "name": "Example Credential", "credential_keys": ["test_key"], "credential_pairs": { "test_key": "test_value" }, "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": {} }, ... ], "blind_credentials": [], "permissions": {} "metadata": True, "get": True, "update": True } } :resheader Content-Type: application/json :statuscode 200: Success :statuscode 400: Invalid input; Either required fields were not provided or credentials being mapped would result in credential key conflicts. :statuscode 403: Client does not have permissions to create or update the specified service ID. """ try: _service = Service.get(id) if _service.data_type != 'service': msg = 'id provided is not a service.' return jsonify({'error': msg}), 400 revision = servicemanager.get_latest_service_revision( id, _service.revision ) except DoesNotExist: revision = 1 _service = None if revision == 1 and not acl_module_check( resource_type='service', action='create', resource_id=id, ): msg = "{} does not have access to create service {}".format( authnz.get_logged_in_user(), id ) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 data = request.get_json() credentials = data.get('credentials', []) blind_credentials = data.get('blind_credentials', []) combined_credentials = credentials + blind_credentials if not acl_module_check( resource_type='service', action='update', resource_id=id, kwargs={ 'credential_ids': combined_credentials, } ): msg = ("{} does not have access to map the credentials " "because they do not own the credentials being added") msg = msg.format(authnz.get_logged_in_user()) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 conflicts = credentialmanager.pair_key_conflicts_for_credentials( credentials, blind_credentials, ) if conflicts: ret = { 'error': 'Conflicting key pairs in mapped service.', 'conflicts': conflicts } return jsonify(ret), 400 accounts = list(settings.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) logger.error(msg) credentials = credentialmanager.get_credentials(data.get('credentials')) blind_credentials = credentialmanager.get_blind_credentials( data.get('blind_credentials'), ) # Use the IDs from the fetched IDs, to ensure we filter any archived # credential IDs. filtered_credential_ids = [cred.id for cred in credentials] # Try to save to the archive try: Service( id='{0}-{1}'.format(id, revision), data_type='archive-service', credentials=filtered_credential_ids, 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: logger.error(e) return jsonify({'error': 'Failed to add service to archive.'}), 500 try: service = Service( id=id, data_type='service', credentials=filtered_credential_ids, 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: logger.error(e) return jsonify({'error': 'Failed to update active service.'}), 500 servicemanager.send_service_mapping_graphite_event(service, _service) webhook.send_event('service_update', [service.id], service.credentials) permissions = { 'create': True, 'metadata': True, 'get': True, 'update': True, } service_response = ServiceResponse.from_service_expanded( service, credentials=credentials, blind_credentials=blind_credentials, ) service_response.permissions = permissions return service_expanded_response_schema.dumps(service_response)
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 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 = list(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 revert_service_to_revision(id, to_revision): if not acl_module_check( resource_type='service', action='revert', resource_id=id): msg = "{} does not have access to revert service {}".format( authnz.get_logged_in_user(), id) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 try: current_service = Service.get(id) except DoesNotExist: logging.warning('Item with id {0} does not exist.'.format(id)) return jsonify({}), 404 if current_service.data_type != 'service': msg = 'id provided is not a service.' return jsonify({'error': msg}), 400 new_revision = servicemanager.get_latest_service_revision( id, current_service.revision) try: revert_service = Service.get('{}-{}'.format(id, to_revision)) except DoesNotExist: logging.warning('Item with id {0} does not exist.'.format(id)) return jsonify({}), 404 if revert_service.data_type != 'archive-service': msg = 'id provided is not a service.' return jsonify({'error': msg}), 400 if revert_service.equals(current_service): ret = {'error': 'No difference between old and new service.'} return jsonify(ret), 400 if revert_service.credentials or revert_service.blind_credentials: conflicts = credentialmanager.pair_key_conflicts_for_credentials( revert_service.credentials, revert_service.blind_credentials, ) if conflicts: ret = { 'error': 'Conflicting key pairs in mapped service.', 'conflicts': conflicts } return jsonify(ret), 400 # Try to save to the archive try: Service(id='{0}-{1}'.format(id, new_revision), data_type='archive-service', credentials=revert_service.credentials, blind_credentials=revert_service.blind_credentials, account=revert_service.account, enabled=revert_service.enabled, revision=new_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=revert_service.credentials, blind_credentials=revert_service.blind_credentials, account=revert_service.account, enabled=revert_service.enabled, revision=new_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 servicemanager.send_service_mapping_graphite_event(service, current_service) webhook.send_event( 'service_update', [service.id], service.credentials, ) try: credentials = credentialmanager.get_credentials(service.credentials, ) except KeyError: logging.exception('KeyError occurred in getting credentials') return jsonify({'error': 'Decryption error.'}), 500 blind_credentials = credentialmanager.get_blind_credentials( service.blind_credentials, ) return service_expanded_response_schema.dumps( ServiceResponse.from_service_expanded( service, credentials=credentials, blind_credentials=blind_credentials, ))
def map_service_credentials(id): try: _service = Service.get(id) if _service.data_type != 'service': msg = 'id provided is not a service.' return jsonify({'error': msg}), 400 revision = servicemanager.get_latest_service_revision( id, _service.revision) except DoesNotExist: revision = 1 _service = None if revision == 1 and not acl_module_check( resource_type='service', action='create', resource_id=id, ): msg = "{} does not have access to create service {}".format( authnz.get_logged_in_user(), id) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 data = request.get_json() credentials = data.get('credentials', []) blind_credentials = data.get('blind_credentials', []) combined_credentials = credentials + blind_credentials if not acl_module_check(resource_type='service', action='update', resource_id=id, kwargs={ 'credential_ids': combined_credentials, }): msg = ("{} does not have access to map the credentials " "because they do not own the credentials being added") msg = msg.format(authnz.get_logged_in_user()) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 conflicts = credentialmanager.pair_key_conflicts_for_credentials( credentials, blind_credentials, ) if conflicts: ret = { 'error': 'Conflicting key pairs in mapped service.', 'conflicts': conflicts } return jsonify(ret), 400 accounts = list(settings.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 servicemanager.send_service_mapping_graphite_event(service, _service) webhook.send_event('service_update', [service.id], service.credentials) try: credentials = credentialmanager.get_credentials(service.credentials, ) except KeyError: logging.exception('KeyError occurred in getting credentials') return jsonify({'error': 'Decryption error.'}), 500 blind_credentials = credentialmanager.get_blind_credentials( service.blind_credentials, ) permissions = { 'create': True, 'metadata': True, 'get': True, 'update': True, } service_response = ServiceResponse.from_service_expanded( service, credentials=credentials, blind_credentials=blind_credentials, ) service_response.permissions = permissions return service_expanded_response_schema.dumps(service_response)