예제 #1
0
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,
        )
    )
예제 #2
0
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))
예제 #3
0
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)
예제 #4
0
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
    })
예제 #5
0
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)
예제 #6
0
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
    })
예제 #7
0
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,
        ))
예제 #8
0
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)
예제 #9
0
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))
예제 #10
0
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)