Exemple #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,
        )
    )
Exemple #2
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)
Exemple #3
0
def get_service(id):
    '''
    Get a service object from the provided service ID.

    .. :quickref: Service; Get a service object from the provided service ID.

    **Example request**:

    .. sourcecode:: http

       GET /v1/services/example-development

    :param id: The service ID to get.
    :type id: str
    :query boolean metadata_only: If true, only fetch metadata for this
      service, and do not respond with decrypted credential pairs in the
      credential responses.

    **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 403: Client does not have permissions to get the service ID
                     provided.
    '''
    permissions = {
        'metadata': False,
        'get': False,
        'update': False,
    }
    metadata_only = misc.get_boolean(request.args.get('metadata_only'))
    logged_in_user = authnz.get_logged_in_user()
    action = 'metadata' if metadata_only else 'get'
    permissions['metadata'] = acl_module_check(
        resource_type='service',
        action='metadata',
        resource_id=id,
    )
    permissions['get'] = acl_module_check(
        resource_type='service',
        action='get',
        resource_id=id,
    )
    if not permissions[action]:
        msg = "{} does not have access to get service {}".format(
            authnz.get_logged_in_user(),
            id
        )
        error_msg = {'error': msg, 'reference': id}
        return jsonify(error_msg), 403

    logger.info(
        'get_service called on id={} by user={} metadata_only={}'.format(
            id,
            logged_in_user,
            metadata_only,
        )
    )
    try:
        service = Service.get(id)
        if not authnz.service_in_account(service.account):
            logger.warning(
                'Authz failed for service {0} (wrong account).'.format(id)
            )
            msg = 'Authenticated user is not authorized.'
            return jsonify({'error': msg}), 401
    except DoesNotExist:
        return jsonify({}), 404
    if (service.data_type != 'service' and
            service.data_type != 'archive-service'):
        return jsonify({}), 404
    logger.debug('Authz succeeded for service {0}.'.format(id))
    try:
        credentials = credentialmanager.get_credentials(service.credentials)
    except KeyError:
        logger.exception('KeyError occurred in getting credentials')
        return jsonify({'error': 'Decryption error.'}), 500
    blind_credentials = credentialmanager.get_blind_credentials(
        service.blind_credentials,
    )
    # TODO: this check can be expensive, so we're gating only to user auth.
    # We should probably add an argument that opts in for permission hints,
    # rather than always checking them.
    if authnz.user_is_user_type('user'):
        combined_cred_ids = (
            list(service.credentials) + list(service.blind_credentials)
        )
        permissions['update'] = acl_module_check(
            resource_type='service',
            action='update',
            resource_id=id,
            kwargs={
                'credential_ids': combined_cred_ids,
            },
        )
    service_response = ServiceResponse.from_service_expanded(
        service,
        credentials=credentials,
        blind_credentials=blind_credentials,
        metadata_only=metadata_only,
    )
    service_response.permissions = permissions
    return service_expanded_response_schema.dumps(service_response)
Exemple #4
0
def get_service(id):
    '''
    Get service metadata and all credentials for this service. This endpoint
    allows basic authentication.
    '''
    permissions = {
        'metadata': False,
        'get': False,
        'update': False,
    }
    metadata_only = request.args.get('metadata_only', default=False, type=bool)
    logged_in_user = authnz.get_logged_in_user()
    action = 'metadata' if metadata_only else 'get'
    permissions['metadata'] = acl_module_check(
        resource_type='service',
        action='metadata',
        resource_id=id,
    )
    permissions['get'] = acl_module_check(
        resource_type='service',
        action='get',
        resource_id=id,
    )
    if not permissions[action]:
        msg = "{} does not have access to get service {}".format(
            authnz.get_logged_in_user(), id)
        error_msg = {'error': msg, 'reference': id}
        return jsonify(error_msg), 403

    logging.info(
        'get_service called on id={} by user={} metadata_only={}'.format(
            id,
            logged_in_user,
            metadata_only,
        ))
    try:
        service = Service.get(id)
        if not authnz.service_in_account(service.account):
            logging.warning(
                'Authz failed for service {0} (wrong account).'.format(id))
            msg = 'Authenticated user is not authorized.'
            return jsonify({'error': msg}), 401
    except DoesNotExist:
        return jsonify({}), 404
    if (service.data_type != 'service'
            and service.data_type != 'archive-service'):
        return jsonify({}), 404
    logging.debug('Authz succeeded for service {0}.'.format(id))
    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, )
    # TODO: this check can be expensive, so we're gating only to user auth.
    # We should probably add an argument that opts in for permission hints,
    # rather than always checking them.
    if authnz.user_is_user_type('user'):
        combined_cred_ids = (list(service.credentials) +
                             list(service.blind_credentials))
        permissions['update'] = acl_module_check(
            resource_type='service',
            action='update',
            resource_id=id,
            kwargs={
                'credential_ids': combined_cred_ids,
            },
        )
    service_response = ServiceResponse.from_service_expanded(
        service,
        credentials=credentials,
        blind_credentials=blind_credentials,
        metadata_only=metadata_only,
    )
    service_response.permissions = permissions
    return service_expanded_response_schema.dumps(service_response)
Exemple #5
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,
        ))
Exemple #6
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)