예제 #1
0
    def test_get_config_by_keypath2(self):
        cfg = config.get_config_by_keypath('LOG_LEVEL')
        assert cfg == 'info'

        try:
            cfg = config.get_config_by_keypath('NONEXISTING_KEY')
        except KeyError as e:
            assert isinstance(e, KeyError)
예제 #2
0
파일: jwe.py 프로젝트: p15r/distributey
def get_wrapped_key_as_jwe(priv_dek: bytearray,
                           tenant: str,
                           jwe_kid: str,
                           nonce: str = '') -> str:
    """Creates a JWE."""
    trace_enter(inspect.currentframe())

    logger.info('Creating JWE token for request with kid "%s"...', jwe_kid)

    # Generate a 256 bit AES content encryption key (32 bytes * 8).
    try:
        cek = bytearray(get_random_bytes(32))
    except Exception as exc:
        ret = ''
        logger.error('Failed to get random bytes: %s', exc)
        trace_exit(inspect.currentframe(), ret)
        return ret

    if config.get_config_by_keypath('DEV_MODE'):
        logger.debug('Generated cek (BYOK AES key): %s (hex)', cek.hex())

    if not (b64_cek_ciphertext := _encrypt_cek_with_key_consumer_key(
            tenant, jwe_kid, cek)):

        logger.error(
            'Cannot encrypt content encryption key with key consumer '
            'key of %s/%s.', tenant, jwe_kid)

        trace_exit(inspect.currentframe(), '')
        return ''
예제 #3
0
def __get_vault_token(client: hvac.Client, tenant: str, priv_jwt_token: str,
                      vault_auth_jwt_path: str) -> str:

    trace_enter(inspect.currentframe())

    default_role = config.get_vault_default_role(tenant)

    if not default_role:
        logger.error('Failed to load Vault default role for tenant "%s"',
                     tenant)
        return ''

    try:
        response = client.auth.jwt.jwt_login(role=default_role,
                                             jwt=priv_jwt_token,
                                             path=vault_auth_jwt_path)
    except Exception as exc:
        ret = ''
        logger.error('Failed to authenticate against Vault: %s', exc)
        trace_exit(inspect.currentframe(), ret)
        return ret

    if config.get_config_by_keypath('DEV_MODE'):
        logger.debug('Vault login response: %s', response)

    try:
        vault_token = response['auth']['client_token']
    except KeyError as exc:
        ret = ''

        logger.error(
            'Failed to access the Vault token from auth response: '
            'KeyError on key %s. '
            'This is most likely a permission issue.', exc)

        trace_exit(inspect.currentframe(), ret)
        return ret

    if config.get_config_by_keypath('DEV_MODE'):
        logger.debug('Vault client token returned: %s', vault_token)

    logger.debug('Retrieved new Vault token.')

    trace_exit(inspect.currentframe(), CAMOUFLAGE_SIGN)
    return vault_token
예제 #4
0
    def test_get_config_by_keypath(self):
        # test if cfg is not accessible
        os.chmod(self.cfg, 0o000)

        cfg = config.get_config_by_keypath('LOG_LEVEL')

        assert cfg is False

        os.chmod(self.cfg, 0o664)
예제 #5
0
def __authenticate_vault_client(client: hvac.Client, tenant: str,
                                priv_jwt_token: str) -> hvac.Client:

    trace_enter(inspect.currentframe())

    vault_auth_jwt_path = config.get_vault_auth_jwt_path(tenant)

    if not vault_auth_jwt_path:
        logger.error('Failed to load auth jwt path for tenant "%s"', tenant)
        return None

    if config.get_config_by_keypath('DEV_MODE'):
        logger.debug('Attempting to authenticate against Vault using JWT: %s',
                     priv_jwt_token)

    cache_id = utils.get_vault_token_cache_id(tenant, priv_jwt_token)

    if cache_id in __VAULT_TOKEN_CACHE:
        logger.debug('Cache hit: Found token for "%s".', cache_id)
        client.token = __VAULT_TOKEN_CACHE[cache_id]
    else:
        logger.debug('Cache miss: Token for "%s" not found.', cache_id)

        token = __get_vault_token(client, tenant, priv_jwt_token,
                                  vault_auth_jwt_path)

        if not token:
            ret = None
            logger.error('Failed to get Vault token.')
            trace_exit(inspect.currentframe(), ret)
            return ret

        client.token = token
        __VAULT_TOKEN_CACHE[cache_id] = token

    if not client.is_authenticated():
        # token might be invalid/has expired
        del __VAULT_TOKEN_CACHE[cache_id]

        ret = None

        logger.error('Failed to validate Vault client. '
                     'Review configuration (config/config.json). '
                     'Retry as token might have expired.')

        trace_exit(inspect.currentframe(), ret)
        return ret

    logger.debug('Successfully authenticated Vault client '
                 'for tenant "%s"', tenant)

    trace_exit(inspect.currentframe(), client)
    return client
예제 #6
0
def _dev_mode_warning_banner() -> None:
    trace_enter(inspect.currentframe())

    dev_mode = config.get_config_by_keypath('DEV_MODE')
    log_level = config.get_config_by_keypath('LOG_LEVEL')

    banner = r"""

      _____  ________      __  __  __  ____  _____  ______
     |  __ \|  ____\ \    / / |  \/  |/ __ \|  __ \|  ____|
     | |  | | |__   \ \  / /  | \  / | |  | | |  | | |__
     | |  | |  __|   \ \/ /   | |\/| | |  | | |  | |  __|
     | |__| | |____   \  /    | |  | | |__| | |__| | |____
     |_____/|______|   \/     |_|  |_|\____/|_____/|______|

     Sensitive data, such as data encryption keys are logged in plain-text.

     """

    if dev_mode and log_level == 'debug':
        app.logger.info(banner)

    trace_exit(inspect.currentframe(), None)
예제 #7
0
파일: jwe.py 프로젝트: p15r/distributey
def _encrypt_dek_with_cek(priv_cek: bytearray, initialization_vector: bytes,
                          priv_dek: bytearray,
                          ascii_b64_protected_header: bytes) \
        -> Tuple[bytes, bytes]:
    """
    Wrap dek with cek:
    - Perform authenticated encryption on dek with the AES GCM algorithm.
    - Use cek as encryption key, the initialization vector,
      and the protected header as Additional Authenticated Data value.
    - Request a 128-bit Authentication Tag output.
    """
    trace_enter(inspect.currentframe())

    try:
        # mac_len=16 bytes: 128 bit authentication tag
        dek_cipher = AES.new(priv_cek,
                             AES.MODE_GCM,
                             nonce=initialization_vector,
                             mac_len=16)

        # add additional authenticated data (aad)
        dek_cipher.update(ascii_b64_protected_header)

        # TODO: Autom. padding helpful? Might replace pycryptodome anyway.
        # from Cryptodome.Util.Padding import pad
        # encrypted_dek, tag = \
        #   dek_cipher.encrypt_and_digest(pad(dek, AES.block_size))
        encrypted_dek, tag = dek_cipher.encrypt_and_digest(priv_dek)

        # Remove sensitive data from memory
        del priv_dek[:]
        del priv_cek[:]

        b64_encrypted_dek = base64.urlsafe_b64encode(encrypted_dek)
        b64_tag = base64.urlsafe_b64encode(tag)
    except Exception as exc:
        ret = (b'', b'')
        logger.error('Failed to encrypt dek: %s', exc)
        trace_exit(inspect.currentframe(), ret)
        return ret

    if config.get_config_by_keypath('DEV_MODE'):
        logger.debug('Additional authenticated data (aad): '
                     '%s', ascii_b64_protected_header.decode())
        logger.debug('Encrypted dek: "%s" (hex), '
                     'tag :"%s" (hex).', encrypted_dek.hex(), tag.hex())

    trace_exit(inspect.currentframe(), (b64_encrypted_dek, b64_tag))
    return b64_encrypted_dek, b64_tag
예제 #8
0
def get_healthz():
    """
    This healthz implementation adheres to:
        https://tools.ietf.org/html/draft-inadarei-api-health-check-04

    TODO: add user-agent & x-real-ip to input validation &
          flask.session in order to log request properly.
    """
    trace_enter(inspect.currentframe())

    if not config.get_config_by_keypath('LOG_LEVEL'):
        ret = '{"status": "fail", "output": "Config not loaded"}'
        trace_exit(inspect.currentframe(), ret)
        _http_error(500, ret)

    ret = _http_20x(200, '{"status": "pass"}')

    trace_exit(inspect.currentframe(), ret)
    return ret
예제 #9
0
                     validation_cert)

    # pyjwt[crypto] requires PEM format & only the public key.
    # TODO: Extract key from cert programmatically:
    #       https://pyjwt.readthedocs.io/en/latest/faq.html
    #           how-can-i-extract-a-public-private-key-from-a-x509-certificate
    try:
        with open(validation_cert) as file:
            cert = file.read()
    except Exception as exc:
        ret = ''
        app.logger.error('Failed to read validation cert: %s', exc)
        trace_exit(inspect.currentframe(), ret)
        _http_error(500, '{"error": "internal error"}')

    if config.get_config_by_keypath('DEV_MODE'):
        app.logger.debug('Received JWT: %s', token)

    token_sub, token_iss = _decode_jwt(tenant, token, cert)

    if not (cfg_sub := config.get_jwt_subject_by_tenant(tenant)):
        ret = ''
        app.logger.error('Cannot get JWT subject for tenant "%s" from config.',
                         tenant)
        trace_exit(inspect.currentframe(), ret)
        _http_error(500, '{"error": "internal error"}')

    if not (cfg_iss := config.get_jwt_issuer_by_tenant(tenant)):
        ret = ''
        app.logger.error('Cannot get JWT issuer for tenant "%s" from config.',
                         tenant)
예제 #10
0
"""Provides formatted logging handler for distributey."""

import logging
import sys
from flask import has_request_context, session
import config
from splunk_handler import SplunkHandler

log_level = config.get_config_by_keypath('LOG_LEVEL')
splunk_enabled = config.get_config_by_keypath('SPLUNK_ENABLED')

if log_level == 'debug':
    __LOGLVL = logging.DEBUG
else:
    __LOGLVL = logging.INFO


class __RequestFormatter(logging.Formatter):
    def format(self, record):
        if has_request_context():
            try:
                record.user_agent = session['header_args']['user-agent']
                record.tenant = session['view_args']['tenant']
                record.x_real_ip = session['header_args']['x-real-ip']
            except KeyError:
                # input validation failed
                record.user_agent = 'N/A'
                record.tenant = 'N/A'
                record.x_real_ip = 'N/A'
        else:
            record.tenant = 'system'