Source code for gcip2.vault

 1import getpass
 2import sys
 3from os import getenv as os_getenv
 4
 5import hvac
 6import injector
 7
 8from gcip2.logging import logger as LOGGER
 9from gcip2.project_config import ProjectConfig
10from gcip2.project_config.vault_config import VaultConfig
11from gcip2.vault.token_store import TokenStore
12
13
[docs] 14@injector.inject 15class Vault: 16 def __init__(self, vault_config: VaultConfig, _token_store: TokenStore): 17 self.vault_config = vault_config 18 self._client = hvac.Client(url=self.vault_config.url) 19 LOGGER.debug(f"initialized client for vault instance: {self.vault_config.url}") 20 21 self._tokens = _token_store 22 self.auth() 23
[docs] 24 def auth(self) -> None: 25 LOGGER.debug(f"authorization with approle {self.vault_config.app_role_id_env_var}") 26 27 role_id = os_getenv(str(self.vault_config.app_role_id_env_var)) 28 secret_id = os_getenv(str(self.vault_config.app_role_secret_id_env_var)) 29 30 if role_id and secret_id: 31 self._client.auth.approle.login(role_id=role_id, secret_id=secret_id) # type: ignore[attr-defined] 32 return 33 self.auth_keyring()
34
[docs] 35 def auth_keyring(self) -> None: 36 token = self._tokens.load(self.vault_config.url) 37 if token: 38 self._client.token = token 39 if self._client.is_authenticated(): 40 LOGGER.debug("token from keyring is valid") 41 return 42 LOGGER.debug("token from keyring is expired") 43 44 token = self.auth_interactive() 45 self._client.token = token 46 if not self._client.is_authenticated(): 47 raise ValueError("could not authenticate with the obtained token") 48 self._tokens.save(self.vault_config.url, token)
49
[docs] 50 def auth_interactive(self) -> str: 51 if not sys.stdin.isatty(): 52 raise ValueError( 53 "cannot authenticate: no approle credentials in environment " 54 f"({self.vault_config.app_role_id_env_var}, " 55 f"{self.vault_config.app_role_secret_id_env_var}) and stdin is not a terminal" 56 ) 57 LOGGER.info(f"authenticating to {self.vault_config.url}") 58 username = input("LDAP username: ") 59 password = getpass.getpass("LDAP password: ") 60 return self._client.auth.ldap.login(username=username, password=password)["auth"]["client_token"]
61
[docs] 62 def fetch(self) -> dict[str, str]: 63 LOGGER.debug(f"fetching secret from {self.vault_config.mount_point}:{self.vault_config.path}") 64 return self._client.secrets.kv.read_secret_version( # type: ignore[attr-defined] 65 mount_point=self.vault_config.mount_point, 66 path=self.vault_config.path, 67 raise_on_deleted_version=True, 68 )["data"]["data"]
69 70
[docs] 71class SecretsHandler: 72 @injector.inject 73 def __init__(self, config: ProjectConfig, _token_store: TokenStore) -> None: 74 self._config = config 75 self._tokens = _token_store 76
[docs] 77 def normalize_secret(self, secret: str) -> VaultConfig: 78 if isinstance(secret, VaultConfig): 79 return secret 80 vault_config = self._config.extra.secrets.vault.get(secret) 81 if not vault_config: 82 raise ValueError(f"missing secret '{secret}'") 83 return vault_config
84
[docs] 85 def fetch(self, secret: str): 86 vault_config = self.normalize_secret(secret) 87 LOGGER.info(f"fetching secret: '{secret}'") 88 client = Vault(vault_config, self._tokens) 89 return client.fetch()