Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2Hashicorp Vault class 

3""" 

4 

5import boto3 

6import hvac 

7import logging 

8import os 

9from sdc_etl_libs.hashicorp_helpers.hashicorp_enums import VaultEnvironments 

10 

11 

12class Vault: 

13 

14 def __init__(self, vault_env_: str = None, role_: str = None, token_: str = None): 

15 """ 

16 In this init the class picks client auth to proceed with. 

17 :param vault_env_: points to a Vault environment and between 

18 2 available 

19 Acceptable values: 

20 dev - https://vault-nonprod.smileco.cloud 

21 prod - https://vault-prod.smileco.cloud 

22 :param role_: aws role to be used for vault authentication 

23 (must be provisioned by Platform team first) 

24 :param token_: access token to vault environment. 

25 The token can be obtained via vault login -method=okta username=<user>. 

26 Can only be used for development. 

27 :raise ValueError: 

28 """ 

29 

30 # If a vault_env_ is given, we assume that either "token_" or "role_" is also passed in 

31 # and we will try to connect Vault with one or the other. 

32 if vault_env_: 

33 if role_ is None and token_ is None: 

34 raise Exception("To use Vault with vault_env_, either role_ or token_ must also be passed in.") 

35 env = vault_env_.lower() 

36 if env in VaultEnvironments.__members__: 

37 # The Vault URL is grabbed from the Enums based on the vault_env_ given 

38 url = VaultEnvironments.__members__[env].value["url"] 

39 # If token_ is given, we will try to connect to Vault via that token 

40 if token_ is not None: 

41 self.client = self.__vault_token_client(url, token_) 

42 # If role_ is given, we will try to connect to Vault via an AWS role 

43 elif role_ is not None: 

44 self.client = self.__aws_secret_client(url, role_) 

45 else: 

46 raise Exception(f"Could not connect Vault with the provided environment of {vault_env_}.") 

47 

48 # If vault_env_ is not given, then we will try and infer the proper way to connect 

49 # to Vault from the environment 

50 else: 

51 # First, we check to see if "VAULT_TOKEN" var exists in the environment 

52 token = self.__grab_token_from_environment() 

53 if token: 

54 # If we get a token back, we try to connect via the token using the "dev" environment 

55 # as that is the only environment setup for a token use. 

56 try: 

57 url = VaultEnvironments.__members__["dev"].value["url"] 

58 self.client = self.__vault_token_client(url, token) 

59 except Exception as e: 

60 logging.info("Could not connect to Vault with the token grabbed from the environment.") 

61 raise 

62 # If there is no token, then we will try to infer the URL and AWS role needed to connect 

63 # from the environment's AWS_PROFILE. 

64 else: 

65 url, role_from_aws = self.__infer_access_setup_from_aws() 

66 # If a role is passed in the function, we will go with that one before using the role we infer 

67 role = role_ or role_from_aws 

68 self.client = self.__aws_secret_client(url, role) 

69 

70 def __grab_token_from_environment(self): 

71 """ 

72 Attempts to grab the Vault token from the os environment. 

73 :return: Str. Vault token if available, else, None. 

74 """ 

75 

76 logging.info("Attempting to grab Vault token from environment...") 

77 try: 

78 token = os.environ.get("VAULT_TOKEN") 

79 except: 

80 return None 

81 return token 

82 

83 def __infer_access_setup_from_aws(self): 

84 """ 

85 Infers appropriate Vault URL and Role from the AWS account currently connected to via boto. 

86 :return: 

87 - url: Str. URL for Vault UI. 

88 - role: Str. Role for access to Vault. 

89 """ 

90 logging.info("Attempting to infer Vault URL and role from AWS environment...") 

91 account_name = boto3.client('iam').list_account_aliases()['AccountAliases'][0] 

92 for k, v in VaultEnvironments.__members__.items(): 

93 if v.value["aws_account_name"] == account_name: 

94 url = v.value["url"] 

95 role = v.value["role"] 

96 logging.info(f"The {account_name} AWS account is setup for Vault with a role of '{role}' and a url of '{url}'") 

97 return url, role 

98 

99 def __vault_token_client(self, url_: str, token_: str): 

100 """ 

101 This private method returns client based on supplied token 

102 :param url_: url address of the vault environment. 

103 Now only 2 Vault envs (https://vault-nonprod.smileco.cloud, 

104 https://vault-prod.smileco.cloud) 

105 :param token_: access token to vault environment. 

106 The token can be obtained via vault login -method=okta username=<user>. 

107 Only must be used in development. 

108 :return: Vault Client object 

109 :rtype: Object 

110 """ 

111 client = hvac.Client(url=url_, token=token_) 

112 logging.info(f"Connected to {url_} Vault via token.") 

113 return client 

114 

115 def __aws_secret_client(self, url_: str, role_: str): 

116 """ 

117 This private method returns client based on supplied AWS role 

118 :param url_: url address of the vault environment. 

119 Now only 2 Vault envs (https://vault-nonprod.smileco.cloud, 

120 https://vault-prod.smileco.cloud) 

121 :param role_: AWS role to access to vault environment with 

122 :return: Vault Client object 

123 :rtype: Object 

124 """ 

125 session = boto3.Session() 

126 credentials = session.get_credentials() 

127 client = hvac.Client(url=url_) 

128 client.auth.aws.iam_login(credentials.access_key, credentials.secret_key, 

129 credentials.token, 

130 role=role_) 

131 logging.info(f"Connected to {url_} Vault via AWS role.") 

132 return client 

133 

134 def get_secrets(self, secrets_engine_: str, domain_: str, secret_path_: str): 

135 """ 

136 This method retrieves secrets stored in Vault 

137 :param secrets_engine_: set the engine the secret is mounted to 

138 :param domain_: set Vault domain name 

139 :param secret_path_: path to the secret within Vaults env/domain 

140 """ 

141 full_path = f'{domain_}/{secret_path_}' 

142 response = self.client.secrets.kv.v2.read_secret_version(mount_point=secrets_engine_, 

143 path=full_path) 

144 return response['data']['data'] 

145 

146 def post_secrets(self, secrets_engine_: str, domain_: str, secret_path_: str, secret_value_: dict): 

147 """ 

148 This method stores secrets in Vault 

149 :param secrets_engine_: set the engine the secret is mounted to 

150 :param domain_: set Vault domain name 

151 :param secret_path_: path to the secret within Vaults env/domain 

152 :param secret_value_: the secret to store 

153 """ 

154 full_path = f'{domain_}/{secret_path_}' 

155 response = self.client.secrets.kv.v2.create_or_update_secret(mount_point=secrets_engine_, 

156 path=full_path, 

157 secret=secret_value_) 

158 return response.get('data')