Skip to content

Commit

Permalink
Exception Handling for Vault (#16)
Browse files Browse the repository at this point in the history
* Updated README

* Updated README

* Added exception handling for vault

* Added more tests for vault

* Updated the setup version
  • Loading branch information
Vishesh-Gupta authored Jun 15, 2021
1 parent ebc1dff commit b35324e
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 34 deletions.
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
Gestalt is an opinionated, strongly typed, configuration library for Python 3.6+. Gestalt aims to simplify configuration loading, getting/setting, and management by letting the user focus on writing programs, and letting Gestalt check configuration types, location, and environment variable overrides.



## Install

```python
Expand Down Expand Up @@ -191,11 +189,13 @@ To set a default configuration value programmatically:
```python
g.set_defualt_string('some.default.k', 'default value')
```

Defined by the function signature

```python
set_default_string(key: str, value: str) -> None
```

The same applies to the remaining types that Gestalt supports:

```python
Expand Down Expand Up @@ -244,8 +244,16 @@ g.add_vault_config_provider(vault_config, authentication_config)
1. The `vault_config` is a dictionary requiring url, and token
2. The `authentication_config` is a dictionary with Kubernetes `role` and `jwt`

Next, add a path to the secret for Vault to access the cluster which loads it into the running
internal data structure configuration
Raises RuntimeError if the VAULT_ADDR is not set or set incorrectly, or TypeError when client_config is empty.

Second, add a path to the secret for Vault to access the cluster which loads it into the running
internal data structure configuration. The function `add_vault_secret_path` can consume a
mount point which, if not provided is set to defualt value.

```python
g.add_vault_secret_path('path')
g.add_vault_secret_path(path="your-secret-pat", mount_path="mount-point")
```

Lastly, use the `fetch_vault_secret` function to fetch the secrets from your vault cluster.

Raises a RuntimeError, if the path provided in `add_vault_secret_path` is invalid or the mount is invalid
58 changes: 33 additions & 25 deletions gestalt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import glob
import sys
import json
import requests
import hvac # type: ignore
import collections.abc as collections
from typing import Dict, List, Tuple, Type, Union, Optional, MutableMapping, Text, Any, NamedTuple
Expand All @@ -18,7 +19,7 @@ class CACertClient(NamedTuple):


class HVAC_ClientConfig(TypedDict):
url: str
url: Optional[str]
token: str
cert: Union[None, CACertClient]
verify: Union[None, bool]
Expand Down Expand Up @@ -94,19 +95,6 @@ def add_config_path(self, path: str) -> None:
f'Given directory path of {tmp} is not a directory')
self.__conf_file_paths.append(tmp)

def __init_vault_client(self, url: str, token: str,
cert: Union[Tuple[str, str], None],
verify: Union[bool, str]) -> None:
self.vault_client: hvac.Client = hvac.Client(
url=url,
token=token,
cert=cert,
verify=verify,
)

def __authenticate_vault_client(self, role: str, jwt: str) -> None:
self.vault_client.auth_kubernetes(role=role, jwt=jwt)

def add_config_file(self, path: str) -> None:
"""Adds a path to a single file to read configs from
Expand Down Expand Up @@ -568,22 +556,34 @@ def add_vault_config_provider(
auth_config (HVAC_ClientAuthentication): authenticates the initialized vault client
with role and jwt string from kubernetes
"""
if client_config['url'] == "":
client_config['url'] = os.environ["VAULT_ADDR"]
if bool(client_config) == False:
raise TypeError("Gestalt Error: client config is empty for Vault")
client_config['url'] = os.environ.get("VAULT_ADDR")
print(client_config)
if not client_config['url']:
raise RuntimeError('Gestalt Error: VAULT_ADDR is missing')
if client_config['token'] == "":
client_config['token'] == os.environ.get("VAULT_TOKEN", "")
if client_config['verify']:
verify = True
else:
verify = False
self.__init_vault_client(client_config['url'],
client_config['token'],
client_config['cert'],
verify=verify)

self.vault_client: hvac.Client = hvac.Client(client_config['url'],
client_config['token'],
client_config['cert'],
verify=verify)

if auth_config:
self.__authenticate_vault_client(auth_config['role'],
auth_config['jwt'])
try:
self.vault_client.auth_kubernetes(\
role=auth_config['role'],
jwt=auth_config['jwt']
)
except requests.exceptions.ConnectionError as err:
print(
"Gestalt Error: Couldn't connect to Vault. Maybe missing VAULT_ADDR?"
)

def add_vault_secret_path(self,
path: str,
Expand All @@ -608,6 +608,14 @@ def fetch_vault_secrets(self) -> None:
for vault_path in self.__vault_paths:
mount_path = str(vault_path[0])
secret_path = vault_path[1]
secret_token = self.vault_client.secrets.kv.v2.read_secret_version(
mount_point=str(mount_path), path=secret_path)
self.__conf_data.update(secret_token['data']['data'])
try:
secret_token = self.vault_client.secrets.kv.v2.read_secret_version(
mount_point=str(mount_path), path=secret_path)
self.__conf_data.update(secret_token['data']['data'])
except hvac.exceptions.InvalidPath as err:
raise RuntimeError(
"Gestalt Error: The secret path or mount is set incorrectly"
)
except requests.exceptions.ConnectionError as err:
raise RuntimeError(
"Gestalt Error: Gestalt couldn't connect to Vault")
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def readme():


setup(name='gestalt-cfg',
version='1.0.8',
version='1.0.9',
description='A sensible configuration library for Python',
long_description=readme(),
long_description_content_type="text/markdown",
Expand Down
54 changes: 51 additions & 3 deletions tests/test_gestalt.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,13 @@ def test_set_default_bad_type_set_config():
assert 'Set config has' in terr


def test_vault_setup():
## Vault testing
@pytest.fixture
def user_setup():
os.environ['VAULT_ADDR'] = "http://localhost:8200"


def test_vault_setup(user_setup):
g = gestalt.Gestalt()
client_config = gestalt.HVAC_ClientConfig()
client_config['url'] = ""
Expand All @@ -442,8 +448,15 @@ def test_vault_fail_setup():
client_config['cert'] = None
client_config['verify'] = True
g.add_vault_config_provider(client_config, auth_config=None)
with pytest.raises(requests.exceptions.MissingSchema):
g.vault_client.is_authenticated()
assert g.vault_client.is_authenticated() == False


def test_vault_connection_error():
g = gestalt.Gestalt()
client_config = gestalt.HVAC_ClientConfig()
with pytest.raises(TypeError):
g.add_vault_config_provider(client_config=client_config,
auth_config=None)


def test_vault_fail_kubernetes_auth():
Expand All @@ -461,6 +474,7 @@ def test_vault_fail_kubernetes_auth():


def test_vault_get():
print("If this test fails, please makes sure Vault is running locally")
g = gestalt.Gestalt()
g.build_config()
client_config = gestalt.HVAC_ClientConfig()
Expand All @@ -478,6 +492,23 @@ def test_vault_get():
assert secret == client_password


def test_vault_incorrect_path():
g = gestalt.Gestalt()
g.build_config()
client_config = gestalt.HVAC_ClientConfig()
client_config['url'] = ""
client_config['token'] = "myroot"
client_config['cert'] = None
client_config['verify'] = True
g.add_vault_config_provider(client_config, auth_config=None)
print("Requires the user to set a token in the client")
client_id = "random_client"
client_password = "random_password"
g.add_vault_secret_path(path="random_path")
with pytest.raises(RuntimeError):
g.fetch_vault_secrets()


def test_vault_mount_path():
g = gestalt.Gestalt()
g.build_config()
Expand All @@ -494,3 +525,20 @@ def test_vault_mount_path():
g.fetch_vault_secrets()
secret = g.get_string(client_id_mount_path)
assert secret == client_password_mount_path


def test_vault_incorrect_mount_path():
g = gestalt.Gestalt()
g.build_config()
client_config = gestalt.HVAC_ClientConfig()
client_config['url'] = ""
client_config['token'] = "myroot"
client_config['cert'] = None
client_config['verify'] = True
g.add_vault_config_provider(client_config, auth_config=None)
print("Requires the user to set a token in the client")
client_id_mount_path = "random_test_mount"
client_password_mount_path = "random_test_mount_password"
g.add_vault_secret_path("test", mount_path="incorrect-mount-point")
with pytest.raises(RuntimeError):
g.fetch_vault_secrets()

0 comments on commit b35324e

Please sign in to comment.