Skip to content

Commit 88ae4ed

Browse files
feat: add vault client
1 parent cec399d commit 88ae4ed

File tree

1 file changed

+134
-0
lines changed

1 file changed

+134
-0
lines changed

glueops/vault_client.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import requests
2+
3+
4+
class RedirectError(Exception):
5+
"""Received a redirect response when trying to read a secret from Vault.
6+
You are probably using pomerium or something went wrong in cluster and your token expired.
7+
"""
8+
pass
9+
10+
def vault_response_handler(vault_api_call):
11+
def wrapper(*args, **kwargs):
12+
response = vault_api_call(*args, **kwargs)
13+
if response.headers.get('Location'):
14+
raise RedirectError(
15+
"Received a redirect response when trying to read a secret from Vault. "
16+
"Possible reasons: using pomerium, cluster issues, or token expired."
17+
)
18+
if not response.ok:
19+
raise Exception(f"Error from Secret Store: {response.status_code}")
20+
try:
21+
response_data = response.json()
22+
return response_data
23+
except ValueError:
24+
raise Exception("Unexpected response format from Secret Store.")
25+
return wrapper
26+
27+
28+
class VaultClient:
29+
def __init__(
30+
self,
31+
vault_url: str,
32+
kubernetes_role: str,
33+
vault_token: str = None,
34+
pomerium_cookie: str = None
35+
) -> None:
36+
"""
37+
Initialize a VaultClient for reading and writing secrets to vault.
38+
39+
:param vault_url: The url of the target vault cluster
40+
:param kubernetes_role: The kubernetes_role to use when generating an client toke to access vault.
41+
:param vault_token: The vault token to use for accessing vault and can be generated in this class.
42+
:param pomerium_cookie: Pomerium's cookie to use to access vault. Retrieved from a browser session that has been authenticated to vault.
43+
"""
44+
self.vault_url = vault_url
45+
self.kubernetes_role = kubernetes_role
46+
self.vault_token = vault_token
47+
self.pomerium_cookie = pomerium_cookie
48+
49+
def _get_jwt_token(self) -> str:
50+
with open('/var/run/secrets/kubernetes.io/serviceaccount/token', 'r') as f:
51+
return f.read().strip()
52+
53+
def _get_vault_token_via_kube_auth(self) -> str:
54+
jwt_token = self._get_jwt_token()
55+
payload = {
56+
"jwt": jwt_token,
57+
"role": self.kubernetes_role
58+
}
59+
response = requests.post(
60+
f"{self.vault_url}/v1/auth/kubernetes/login",
61+
json=payload,
62+
verify=False
63+
)
64+
response.raise_for_status()
65+
66+
return response.json()["auth"]["client_token"]
67+
68+
def _adjust_path(self, path: str) -> str:
69+
if path.startswith("secret/") and not path.startswith("secret/data/"):
70+
return path.replace("secret/", "secret/data/")
71+
return path
72+
73+
def _vault_auth(self):
74+
if not self.vault_token:
75+
self.vault_token = self._get_vault_token_via_kube_auth()
76+
self.headers = {
77+
'X-Vault-Token': self.vault_token
78+
}
79+
if self.pomerium_cookie:
80+
self.headers["cookie"] = f"_pomerium={self.pomerium_cookie}"
81+
82+
@vault_response_handler
83+
def _query_vault(
84+
self,
85+
secret_path: str
86+
):
87+
self._vault_auth()
88+
89+
secret_path = self._adjust_path(secret_path)
90+
91+
return requests.get(
92+
f"{self.vault_url}/v1/{secret_path}",
93+
headers=self.headers,
94+
verify=False,
95+
allow_redirects=False
96+
)
97+
98+
def get_data_from_vault(
99+
self,
100+
secret_path: str
101+
):
102+
self._vault_auth()
103+
response_data = self._query_vault(
104+
secret_path=secret_path
105+
)
106+
if 'data' not in response_data:
107+
raise Exception("Missing data.")
108+
109+
actual_data = response_data['data'].get('data')
110+
if not actual_data:
111+
raise Exception("Failed to get secret from secret store")
112+
113+
return actual_data
114+
115+
116+
@vault_response_handler
117+
def write_data_to_vault(
118+
self,
119+
secret_path: str,
120+
data: dict
121+
):
122+
self._vault_auth()
123+
124+
secret_path = self._adjust_path(secret_path)
125+
126+
write_vault_path=f"{self.vault_url}/v1/{secret_path}"
127+
response = requests.post(
128+
write_vault_path,
129+
headers=self.headers,
130+
verify=False,
131+
allow_redirects=False,
132+
json={"data":data}
133+
)
134+
return response

0 commit comments

Comments
 (0)