Exemple #1
0
 def delete(self, deletes, force=False):
     _deletes = ', '.join([delete.id for delete in deletes])
     if not force:
         logger.info(
             'Would have deleted credential and revisions: {}'.format(
                 _deletes, ))
         return
     logger.info('Deleting credential and revisions: {}'.format(_deletes, ))
     with Credential.batch_write() as batch:
         for delete in deletes:
             batch.delete(delete)
     stats.incr('archive.delete.success')
Exemple #2
0
 def save(self, saves, force=False):
     _saves = ', '.join([save.id for save in saves])
     if not force:
         logger.info(
             'Would have archived credential and revisions: {}'.format(
                 _saves, ))
         return
     logger.info('Archiving credential and revisions: {}'.format(_saves, ))
     with CredentialArchive.batch_write() as batch:
         for save in saves:
             batch.save(save)
     stats.incr('archive.save.success')
Exemple #3
0
def create_datakey(encryption_context):
    '''
    Create a datakey from KMS.
    '''
    # Disabled encryption is dangerous, so we don't use falsiness here.
    if app.config['USE_ENCRYPTION'] is False:
        logging.warning('Creating a mock datakey in keymanager.create_datakey.'
                        ' If you are not running in a development or test'
                        ' environment, this should not be happening!')
        return cryptolib.create_mock_datakey()
    # underlying lib does generate random and encrypt, so increment by 2
    stats.incr('at_rest_action', 2)
    return cryptolib.create_datakey(
        encryption_context,
        'alias/{0}'.format(app.config.get('KMS_MASTER_KEY')))
Exemple #4
0
def create_datakey(encryption_context):
    '''
    Create a datakey from KMS.
    '''
    # Disabled encryption is dangerous, so we don't use falsiness here.
    if app.config['USE_ENCRYPTION'] is False:
        logging.warning('Creating a mock datakey in keymanager.create_datakey.'
                        ' If you are not running in a development or test'
                        ' environment, this should not be happening!')
        return cryptolib.create_mock_datakey()
    # underlying lib does generate random and encrypt, so increment by 2
    stats.incr('at_rest_action', 2)
    return cryptolib.create_datakey(
        encryption_context,
        'alias/{0}'.format(app.config.get('KMS_MASTER_KEY'))
    )
Exemple #5
0
def decrypt_datakey(data_key, encryption_context=None):
    '''
    Decrypt a datakey.
    '''
    # Disabled encryption is dangerous, so we don't use falsiness here.
    if app.config['USE_ENCRYPTION'] is False:
        logging.warning('Decypting a mock data key in'
                        ' keymanager.decrypt_datakey. If you are not running'
                        ' in a development or test environment, this should'
                        ' not be happening!')
        return cryptolib.decrypt_mock_datakey(data_key)
    sha = hashlib.sha256(data_key).hexdigest()
    if sha not in DATAKEYS:
        stats.incr('at_rest_action')
        plaintext = cryptolib.decrypt_datakey(data_key, encryption_context)
        DATAKEYS[sha] = plaintext
    return DATAKEYS[sha]
Exemple #6
0
def decrypt_datakey(data_key, encryption_context=None):
    '''
    Decrypt a datakey.
    '''
    # Disabled encryption is dangerous, so we don't use falsiness here.
    if app.config['USE_ENCRYPTION'] is False:
        logging.warning('Decypting a mock data key in'
                        ' keymanager.decrypt_datakey. If you are not running'
                        ' in a development or test environment, this should'
                        ' not be happening!')
        return cryptolib.decrypt_mock_datakey(data_key)
    sha = hashlib.sha256(data_key).hexdigest()
    if sha not in DATAKEYS:
        stats.incr('at_rest_action')
        plaintext = cryptolib.decrypt_datakey(data_key, encryption_context)
        DATAKEYS[sha] = plaintext
    return DATAKEYS[sha]
Exemple #7
0
def create_datakey(encryption_context):
    '''
    Create a datakey from KMS.
    '''
    at_rest_kms_client = _get_at_rest_kms_client()
    # Disabled encryption is dangerous, so we don't use falsiness here.
    if settings.USE_ENCRYPTION is False:
        logger.warning(
            'Creating a mock datakey in keymanager.create_datakey. If you are'
            ' not running in a development or test environment, this should not'
            ' be happening!')
        return cryptolib.create_mock_datakey()
    # underlying lib does generate random and encrypt, so increment by 2
    stats.incr('at_rest_action', 2)
    return cryptolib.create_datakey(encryption_context,
                                    settings.KMS_MASTER_KEY,
                                    client=at_rest_kms_client)
 def issue_certificate_with_key(self, cn, validity, san=None):
     """
     Given the string common name, the validity length of the certificate (in
     number of days), and a list of subject alternative names, return a dict
     with the PEM encoded certificate, certificate chain, and private RSA
     key.
     """
     with stats.timer('issue_certificate_with_key'):
         cache_id = self.cache.get_cache_id(cn, validity, san)
         cached_response = self._get_cached_certificate_with_key(cache_id)
         if cached_response:
             stats.incr('get_cached_certificate_with_key.hit')
             logging.debug('Used cached response for {}'.format(cache_id))
             return cached_response
         stats.incr('get_cached_certificate_with_key.miss')
         key = self.generate_key()
         encoded_key = self.encode_key(key)
         if self.settings['self_sign']:
             cert = self.encode_certificate(
                 self.generate_self_signed_certificate(
                     key,
                     cn,
                     validity,
                     san,
                 ))
             return {
                 'certificate': cert,
                 'certificate_chain': cert,
                 'key': encoded_key,
             }
         csr = self.generate_csr(key, cn, san)
         try:
             # set a lock
             self.cache.lock(cache_id)
             arn = self.issue_certificate(self.encode_csr(csr), validity)
             response = self.get_certificate_from_arn(arn)
             response['key'] = encoded_key
             self.cache.set_response(cache_id, response)
         finally:
             # release the lock
             self.cache.release(cache_id)
         return response
Exemple #9
0
def decrypt_datakey(data_key, encryption_context=None):
    '''
    Decrypt a datakey.
    '''
    at_rest_kms_client = _get_at_rest_kms_client()
    # Disabled encryption is dangerous, so we don't use falsiness here.
    if settings.USE_ENCRYPTION is False:
        logger.warning(
            'Decrypting a mock data key in keymanager.decrypt_datakey. If you'
            ' are not running in a development or test environment, this should'
            ' not be happening!')
        return cryptolib.decrypt_mock_datakey(data_key)
    sha = hashlib.sha256(data_key).hexdigest()
    if sha not in _DATAKEYS:
        stats.incr('at_rest_action')
        plaintext = cryptolib.decrypt_datakey(data_key,
                                              encryption_context,
                                              client=at_rest_kms_client)
        _DATAKEYS[sha] = plaintext
    return _DATAKEYS[sha]
Exemple #10
0
 def archive(self, credentials, force):
     services = list(Service.data_type_date_index.query('service'))
     for credential in credentials:
         if self.credential_in_service(credential.id, services):
             msg = ('Skipping archival of disabled credential {}, as it'
                    ' is still mapped to a service.')
             logger.warning(msg.format(credential.id))
             continue
         saves = []
         deletes = []
         # save the current record.
         archive_credential = CredentialArchive.from_credential(
             credential, )
         saves.append(archive_credential)
         # fetch and save every revision
         revisions = Credential.batch_get(
             credentialmanager.get_revision_ids_for_credential(credential))
         for revision in revisions:
             archive_revision = CredentialArchive.from_credential(
                 revision, )
             saves.append(archive_revision)
             deletes.append(revision)
         deletes.append(credential)
         try:
             self.save(saves, force=force)
         except Exception:
             logger.exception(
                 'Failed to batch save {}, skipping deletion.'.format(
                     credential.id))
             stats.incr('archive.save.failure')
             continue
         try:
             self.delete(deletes, force=force)
         except Exception:
             logger.exception('Failed to batch delete {}'.format(
                 credential.id))
             stats.incr('archive.delete.failure')
Exemple #11
0
 def restore(self, archive_credentials, force):
     for archive_credential in archive_credentials:
         saves = []
         # restore the current record
         credential = Credential.from_archive_credential(
             archive_credential, )
         saves.append(credential)
         # fetch and restore every revision
         _range = range(1, credential.revision + 1)
         ids = []
         for i in _range:
             ids.append("{0}-{1}".format(credential.id, i))
         archive_revisions = CredentialArchive.batch_get(ids)
         for archive_revision in archive_revisions:
             revision = Credential.from_archive_credential(
                 archive_revision, )
             saves.append(revision)
         try:
             self.save(saves, force=force)
         except Exception:
             logger.exception('Failed to batch save {}.'.format(
                 credential.id))
             stats.incr('restore.save.failure')
             continue
Exemple #12
0
 def save(self, saves, force=False):
     # Do not restore a credential if it exists in the primary table.
     # We do this check at the point of all saves so that we can
     # restore revisions, if one of them failed to restore for some
     # reason.
     _saves = []
     for save in saves:
         if self.credential_exists(save.id):
             continue
         _saves.append(save)
     if not _saves:
         return
     save_msg = ', '.join([save.id for save in _saves])
     if not force:
         logger.info(
             'Would have restored credential and revisions: {}'.format(
                 save_msg, ))
         return
     logger.info('Restoring credential and revisions: {}'.format(
         save_msg, ))
     with Credential.batch_write() as batch:
         for save in _saves:
             batch.save(save)
     stats.incr('restore.save.success')
Exemple #13
0
def decrypt_token(version, user_type, _from, token):
    '''
    Decrypt a token.
    '''
    if (version > app.config['KMS_MAXIMUM_TOKEN_VERSION']
            or version < app.config['KMS_MINIMUM_TOKEN_VERSION']):
        raise TokenDecryptionError('Unacceptable token version.')
    stats.incr('token_version_{0}'.format(version))
    try:
        token_key = '{0}{1}'.format(hashlib.sha256(token).hexdigest(), _from)
    except Exception:
        raise TokenDecryptionError('Authentication error.')
    if token_key not in TOKENS:
        try:
            token = base64.b64decode(token)
            context = {
                # This key is sent to us.
                'to': app.config['AUTH_CONTEXT'],
                # From a service.
                'from': _from
            }
            if version > 1:
                context['user_type'] = user_type
            with stats.timer('kms_decrypt_token'):
                data = kms_client.decrypt(CiphertextBlob=token,
                                          EncryptionContext=context)
            # Decrypt doesn't take KeyId as an argument. We need to verify the
            # correct key was used to do the decryption.
            # Annoyingly, the KeyId from the data is actually an arn.
            key_arn = data['KeyId']
            if user_type == 'service':
                if not valid_service_auth_key(key_arn):
                    raise TokenDecryptionError(
                        'Authentication error (wrong KMS key).')
            elif user_type == 'user':
                if key_arn != get_key_arn(app.config['USER_AUTH_KEY']):
                    raise TokenDecryptionError(
                        'Authentication error (wrong KMS key).')
            else:
                raise TokenDecryptionError(
                    'Authentication error. Unsupported user_type.')
            plaintext = data['Plaintext']
            payload = json.loads(plaintext)
            key_alias = get_key_alias_from_cache(key_arn)
            ret = {'payload': payload, 'key_alias': key_alias}
        except TokenDecryptionError:
            raise
        # We don't care what exception is thrown. For paranoia's sake, fail
        # here.
        except Exception:
            logging.exception('Failed to validate token.')
            raise TokenDecryptionError('Authentication error. General error.')
    else:
        ret = TOKENS[token_key]
    time_format = "%Y%m%dT%H%M%SZ"
    now = datetime.datetime.utcnow()
    try:
        not_before = datetime.datetime.strptime(ret['payload']['not_before'],
                                                time_format)
        not_after = datetime.datetime.strptime(ret['payload']['not_after'],
                                               time_format)
    except Exception:
        logging.exception(
            'Failed to get not_before and not_after from token payload.')
        raise TokenDecryptionError('Authentication error. Missing validity.')
    delta = (not_after - not_before).seconds / 60
    if delta > app.config['AUTH_TOKEN_MAX_LIFETIME']:
        logging.warning('Token used which exceeds max token lifetime.')
        raise TokenDecryptionError(
            'Authentication error. Token lifetime exceeded.')
    if (now < not_before) or (now > not_after):
        logging.warning('Invalid time validity for token.')
        raise TokenDecryptionError(
            'Authentication error. Invalid time validity for token.')
    TOKENS[token_key] = ret
    return TOKENS[token_key]
Exemple #14
0
def decrypt_token(version, user_type, _from, token):
    '''
    Decrypt a token.
    '''
    if (version > app.config['KMS_MAXIMUM_TOKEN_VERSION'] or
            version < app.config['KMS_MINIMUM_TOKEN_VERSION']):
        raise TokenDecryptionError('Unacceptable token version.')
    stats.incr('token_version_{0}'.format(version))
    try:
        token_key = '{0}{1}'.format(
            hashlib.sha256(token).hexdigest(),
            _from
        )
    except Exception:
        raise TokenDecryptionError('Authentication error.')
    if token_key not in TOKENS:
        try:
            token = base64.b64decode(token)
            context = {
                # This key is sent to us.
                'to': app.config['AUTH_CONTEXT'],
                # From a service.
                'from': _from
            }
            if version > 1:
                context['user_type'] = user_type
            with stats.timer('kms_decrypt_token'):
                data = kms_client.decrypt(
                    CiphertextBlob=token,
                    EncryptionContext=context
                )
            # Decrypt doesn't take KeyId as an argument. We need to verify the
            # correct key was used to do the decryption.
            # Annoyingly, the KeyId from the data is actually an arn.
            key_arn = data['KeyId']
            if user_type == 'service':
                if not valid_service_auth_key(key_arn):
                    raise TokenDecryptionError(
                        'Authentication error (wrong KMS key).'
                    )
            elif user_type == 'user':
                if key_arn != get_key_arn(app.config['USER_AUTH_KEY']):
                    raise TokenDecryptionError(
                        'Authentication error (wrong KMS key).'
                    )
            else:
                raise TokenDecryptionError(
                    'Authentication error. Unsupported user_type.'
                )
            plaintext = data['Plaintext']
            payload = json.loads(plaintext)
            key_alias = get_key_alias_from_cache(key_arn)
            ret = {'payload': payload, 'key_alias': key_alias}
        except TokenDecryptionError:
            raise
        # We don't care what exception is thrown. For paranoia's sake, fail
        # here.
        except Exception:
            logging.exception('Failed to validate token.')
            raise TokenDecryptionError('Authentication error. General error.')
    else:
        ret = TOKENS[token_key]
    time_format = "%Y%m%dT%H%M%SZ"
    now = datetime.datetime.utcnow()
    try:
        not_before = datetime.datetime.strptime(
            ret['payload']['not_before'],
            time_format
        )
        not_after = datetime.datetime.strptime(
            ret['payload']['not_after'],
            time_format
        )
    except Exception:
        logging.exception(
            'Failed to get not_before and not_after from token payload.'
        )
        raise TokenDecryptionError('Authentication error. Missing validity.')
    delta = (not_after - not_before).seconds / 60
    if delta > app.config['AUTH_TOKEN_MAX_LIFETIME']:
        logging.warning('Token used which exceeds max token lifetime.')
        raise TokenDecryptionError(
            'Authentication error. Token lifetime exceeded.'
        )
    if (now < not_before) or (now > not_after):
        logging.warning('Invalid time validity for token.')
        raise TokenDecryptionError(
            'Authentication error. Invalid time validity for token.'
        )
    TOKENS[token_key] = ret
    return TOKENS[token_key]