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)
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 ''
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
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)
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
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)
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
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
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)
"""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'