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 revert_credential_to_revision(id, to_revision): ''' Revert the provided credential to the provided revision. .. :quickref: Credential; Revert the provided credential to the provided revision **Example request**: .. sourcecode:: http PUT /v1/credentials/abcd12345bf4f1cafe8e722d3860404/1 :param id: The credential ID to revert. :type id: str :param to_revision: The revision to revert this credential 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": ["example_credential_key"], "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": { "metadata": true, "get": true, "update": true } } :resheader Content-Type: application/json :statuscode 200: Success :statuscode 400: Invalid input; the update would create conflicting credential keys in a mapped service. :statuscode 403: Client does not have access to revert the provided credential ID. ''' if not acl_module_check( resource_type='credential', action='revert', resource_id=id): msg = "{} does not have access to revert credential {}".format( authnz.get_logged_in_user(), id) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 try: current_credential = Credential.get(id) except DoesNotExist: return jsonify({'error': 'Credential not found.'}), 404 if current_credential.data_type != 'credential': msg = 'id provided is not a credential.' return jsonify({'error': msg}), 400 new_revision = credentialmanager.get_latest_credential_revision( id, current_credential.revision) try: revert_credential = Credential.get('{}-{}'.format(id, to_revision)) except DoesNotExist: logger.warning('Item with id {0} does not exist.'.format(id)) return jsonify({}), 404 if revert_credential.data_type != 'archive-credential': msg = 'id provided is not a credential.' return jsonify({'error': msg}), 400 if revert_credential.equals(current_credential): ret = {'error': 'No difference between old and new credential.'} return jsonify(ret), 400 services = servicemanager.get_services_for_credential(id) if revert_credential.credential_pairs: _credential_pairs = revert_credential.decrypted_credential_pairs _check, ret = credentialmanager.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 = servicemanager.pair_key_conflicts_for_services( id, list(_credential_pairs.keys()), services) if conflicts: ret = { 'error': 'Conflicting key pairs in mapped service.', 'conflicts': conflicts } return jsonify(ret), 400 # Try to save to the archive try: Credential( id='{0}-{1}'.format(id, new_revision), name=revert_credential.name, data_type='archive-credential', credential_pairs=revert_credential.credential_pairs, metadata=revert_credential.metadata, enabled=revert_credential.enabled, revision=new_revision, data_key=revert_credential.data_key, cipher_version=revert_credential.cipher_version, modified_by=authnz.get_logged_in_user(), documentation=revert_credential.documentation, tags=revert_credential.tags, last_rotation_date=revert_credential.last_rotation_date, ).save(id__null=True) except PutError as e: logger.error(e) return jsonify({'error': 'Failed to add credential to archive.'}), 500 try: cred = Credential( id=id, name=revert_credential.name, data_type='credential', credential_pairs=revert_credential.credential_pairs, metadata=revert_credential.metadata, enabled=revert_credential.enabled, revision=new_revision, data_key=revert_credential.data_key, cipher_version=revert_credential.cipher_version, modified_by=authnz.get_logged_in_user(), documentation=revert_credential.documentation, tags=revert_credential.tags, last_rotation_date=revert_credential.last_rotation_date, ) cred.save() except PutError as e: logger.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 credential_response_schema.dumps( CredentialResponse.from_credential(cred))
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 revert_blind_credential_to_revision(id, to_revision): try: current_credential = BlindCredential.get(id) except DoesNotExist: return jsonify({'error': 'Blind credential not found.'}), 404 if current_credential.data_type != 'blind-credential': msg = 'id provided is not a blind-credential.' return jsonify({'error': msg}), 400 new_revision = credentialmanager.get_latest_blind_credential_revision( id, current_credential.revision ) try: revert_credential = BlindCredential.get('{}-{}'.format(id, to_revision)) except DoesNotExist: return jsonify({'error': 'Blind credential not found.'}), 404 if revert_credential.data_type != 'archive-blind-credential': msg = 'id provided is not an archive-blind-credential.' return jsonify({'error': msg}), 400 if revert_credential.equals(current_credential): ret = { 'error': 'No difference between old and new blind credential.' } return jsonify(ret), 400 services = servicemanager.get_services_for_blind_credential(id) if revert_credential.credential_keys: # Ensure credential keys don't conflicts with pairs from other # services conflicts = servicemanager.pair_key_conflicts_for_services( id, revert_credential.credential_keys, services ) if conflicts: ret = { 'error': 'Conflicting key pairs in mapped service.', 'conflicts': conflicts } return jsonify(ret), 400 # Try to save to the archive try: BlindCredential( id='{}-{}'.format(id, new_revision), data_type='archive-blind-credential', name=revert_credential.name, credential_pairs=revert_credential.credential_pairs, credential_keys=revert_credential.credential_keys, metadata=revert_credential.metadata, revision=new_revision, enabled=revert_credential.enabled, data_key=revert_credential.data_key, cipher_type=revert_credential.cipher_type, cipher_version=revert_credential.cipher_version, modified_by=authnz.get_logged_in_user(), documentation=revert_credential.documentation ).save(id__null=True) except PutError as e: logger.error(e) return jsonify( {'error': 'Failed to add blind-credential to archive.'} ), 500 try: cred = BlindCredential( id=id, data_type='blind-credential', name=revert_credential.name, credential_pairs=revert_credential.credential_pairs, credential_keys=revert_credential.credential_keys, metadata=revert_credential.metadata, revision=new_revision, enabled=revert_credential.enabled, data_key=revert_credential.data_key, cipher_type=revert_credential.cipher_type, cipher_version=revert_credential.cipher_version, modified_by=authnz.get_logged_in_user(), documentation=revert_credential.documentation ) cred.save() except PutError as e: logger.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, 'documentation': cred.documentation })
def update_credential(id): ''' Update the provided credential using the data provided in the POST body. .. :quickref: Credential; Update the provided credential using the data provided in the post body. **Example request**: .. sourcecode:: http PUT /v1/credentials/abcd12345bf4f1cafe8e722d3860404 :param id: The credential ID to update. :type id: str :<json string name: The friendly name for the credential. :<json Dictionary{string: string} credential_pairs: A dictionary of arbitrary key/value pairs to be encrypted at rest. :<json Dictionary{string: string} metadata: A dictionary of arbitrary key/ value pairs for custom per-credential end-user extensions. This is not encrypted at rest. :<json boolean enabled: Whether or not this credential is enabled. :<json string documentation: End-user provided documentation for this credential. **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "id": "abcd12345bf4f1cafe8e722d3860404", "name": "Example Credential", "credential_keys": ["example_credential_key"], "credential_pairs": { "example_credential_key": "example_credential_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": { "metadata": true, "get": true, "update": true } } :resheader Content-Type: application/json :statuscode 200: Success :statuscode 400: Invalid input; either the data provided was not in the correct format, or the update would create conflicting credential keys in a mapped service. :statuscode 403: Client does not have access to update the provided credential ID. ''' if not acl_module_check( resource_type='credential', action='update', resource_id=id): msg = "{} does not have access to update credential {}".format( authnz.get_logged_in_user(), id) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 try: _cred = Credential.get(id) except 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() if not isinstance(data.get('metadata', {}), dict): return jsonify({'error': 'metadata must be a dict'}), 400 update = { 'name': data.get('name', _cred.name), 'last_rotation_date': _cred.last_rotation_date, 'credential_pairs': _cred.credential_pairs, 'enabled': _cred.enabled, 'metadata': data.get('metadata', _cred.metadata), 'documentation': data.get('documentation', _cred.documentation), 'tags': data.get('tags', _cred.tags), } # Enforce documentation, EXCEPT if we are restoring an old revision if (not update['documentation'] and settings.get('ENFORCE_DOCUMENTATION') and not data.get('revision')): return jsonify({'error': 'documentation is a required field'}), 400 if 'enabled' in data: if not isinstance(data['enabled'], bool): return jsonify({'error': 'Enabled must be a boolean.'}), 400 update['enabled'] = data['enabled'] services = servicemanager.get_services_for_credential(id) revision = credentialmanager.get_latest_credential_revision( id, _cred.revision) if 'credential_pairs' in data: # Ensure credential pair keys are lowercase credential_pairs = credentialmanager.lowercase_credential_pairs( data['credential_pairs']) _check, ret = credentialmanager.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 = servicemanager.pair_key_conflicts_for_services( id, list(credential_pairs.keys()), services) if conflicts: ret = { 'error': 'Conflicting key pairs in mapped service.', 'conflicts': conflicts } return jsonify(ret), 400 # If the credential pair passed in the update is different from the # decrypted credential pair of the most recent revision, assume that # this is a new credential pair and update last_rotation_date if credential_pairs != _cred.decrypted_credential_pairs: update['last_rotation_date'] = misc.utcnow() data_key = keymanager.create_datakey(encryption_context={'id': id}) cipher = CipherManager(data_key['plaintext'], version=2) update['credential_pairs'] = cipher.encrypt( json.dumps(credential_pairs)) # Try to save to the archive try: Credential( id='{0}-{1}'.format(id, revision), name=update['name'], data_type='archive-credential', credential_pairs=update['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(), documentation=update['documentation'], tags=update['tags'], last_rotation_date=update['last_rotation_date'], ).save(id__null=True) except PutError as e: logger.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=update['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(), documentation=update['documentation'], tags=update['tags'], last_rotation_date=update['last_rotation_date'], ) cred.save() except PutError as e: logger.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]) permissions = { 'metadata': True, 'get': True, 'update': True, } credential_response = CredentialResponse.from_credential( cred, include_credential_keys=True, include_credential_pairs=True, ) credential_response.permissions = permissions return credential_response_schema.dumps(credential_response)
def update_blind_credential(id): try: _cred = BlindCredential.get(id) except 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 = credentialmanager.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 = servicemanager.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 = servicemanager.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) update['documentation'] = data.get('documentation', _cred.documentation) # Enforce documentation, EXCEPT if we are restoring an old revision if (not update['documentation'] and settings.get('ENFORCE_DOCUMENTATION') and not data.get('revision')): return jsonify({'error': 'documentation is a required field'}), 400 # 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(), documentation=update['documentation'] ).save(id__null=True) except PutError as e: logger.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(), documentation=update['documentation'] ) cred.save() except PutError as e: logger.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, 'documentation': cred.documentation })
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)
def revert_credential_to_revision(id, to_revision): if not acl_module_check( resource_type='credential', action='revert', resource_id=id): msg = "{} does not have access to revert credential {}".format( authnz.get_logged_in_user(), id) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 try: current_credential = Credential.get(id) except DoesNotExist: return jsonify({'error': 'Credential not found.'}), 404 if current_credential.data_type != 'credential': msg = 'id provided is not a credential.' return jsonify({'error': msg}), 400 new_revision = credentialmanager.get_latest_credential_revision( id, current_credential.revision) try: revert_credential = Credential.get('{}-{}'.format(id, to_revision)) except DoesNotExist: logging.warning('Item with id {0} does not exist.'.format(id)) return jsonify({}), 404 if revert_credential.data_type != 'archive-credential': msg = 'id provided is not a credential.' return jsonify({'error': msg}), 400 if revert_credential.equals(current_credential): ret = {'error': 'No difference between old and new credential.'} return jsonify(ret), 400 services = servicemanager.get_services_for_credential(id) if revert_credential.credential_pairs: _credential_pairs = revert_credential.decrypted_credential_pairs _check, ret = credentialmanager.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 = servicemanager.pair_key_conflicts_for_services( id, list(_credential_pairs.keys()), services) if conflicts: ret = { 'error': 'Conflicting key pairs in mapped service.', 'conflicts': conflicts } return jsonify(ret), 400 # Try to save to the archive try: Credential( id='{0}-{1}'.format(id, new_revision), name=revert_credential.name, data_type='archive-credential', credential_pairs=revert_credential.credential_pairs, metadata=revert_credential.metadata, enabled=revert_credential.enabled, revision=new_revision, data_key=revert_credential.data_key, cipher_version=revert_credential.cipher_version, modified_by=authnz.get_logged_in_user(), documentation=revert_credential.documentation, ).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=revert_credential.name, data_type='credential', credential_pairs=revert_credential.credential_pairs, metadata=revert_credential.metadata, enabled=revert_credential.enabled, revision=new_revision, data_key=revert_credential.data_key, cipher_version=revert_credential.cipher_version, modified_by=authnz.get_logged_in_user(), documentation=revert_credential.documentation, ) 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 credential_response_schema.dumps( CredentialResponse.from_credential(cred))
def update_credential(id): if not acl_module_check( resource_type='credential', action='update', resource_id=id): msg = "{} does not have access to update credential {}".format( authnz.get_logged_in_user(), id) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 try: _cred = Credential.get(id) except 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 = credentialmanager.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 = servicemanager.get_services_for_credential(id) if 'credential_pairs' in data: # Ensure credential pair keys are lowercase credential_pairs = credentialmanager.lowercase_credential_pairs( data['credential_pairs']) _check, ret = credentialmanager.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 = servicemanager.pair_key_conflicts_for_services( id, list(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: update['credential_pairs'] = _cred.decrypted_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) update['documentation'] = data.get('documentation', _cred.documentation) # Enforce documentation, EXCEPT if we are restoring an old revision if (not update['documentation'] and settings.get('ENFORCE_DOCUMENTATION') and not data.get('revision')): return jsonify({'error': 'documentation is a required field'}), 400 # 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(), documentation=update['documentation']).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(), documentation=update['documentation']) 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]) permissions = { 'metadata': True, 'get': True, 'update': True, } credential_response = CredentialResponse.from_credential( cred, include_credential_keys=True, include_credential_pairs=True, ) credential_response.permissions = permissions return credential_response_schema.dumps(credential_response)