Page Menu
Home
DevCentral
Search
Configure Global Search
Log In
Files
F11758042
D3697.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
12 KB
Referenced Files
None
Subscribers
None
D3697.id.diff
View Options
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -22,6 +22,10 @@
allows to run the MariaDB or MySQL query,
and format the result as expected, e.g. as MediaWiki table
+* **[secretsmith](tools/secretsmith/README.md)**:
+ wrapper around the hvac library to get secrets from Vault or OpenBao,
+ allow to authenticate with token or AppRole
+
### Contribute
This repository is intended to behave as a monorepo for reporting.
diff --git a/tools/nasqueron-reports/conf/reports.yaml b/tools/nasqueron-reports/conf/reports.yaml
--- a/tools/nasqueron-reports/conf/reports.yaml
+++ b/tools/nasqueron-reports/conf/reports.yaml
@@ -46,3 +46,12 @@
date: Date
userName: Author
repository: Repository
+
+# -------------------------------------------------------------
+# Secretsmith configuration
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+vault:
+ server:
+ url: https://172.27.27.7:8200
+ verify: /usr/local/share/certs/nasqueron-vault-ca.crt
diff --git a/tools/rhyne-wyse/src/rhyne_wyse/credentials/vault.py b/tools/rhyne-wyse/src/rhyne_wyse/credentials/vault.py
--- a/tools/rhyne-wyse/src/rhyne_wyse/credentials/vault.py
+++ b/tools/rhyne-wyse/src/rhyne_wyse/credentials/vault.py
@@ -11,27 +11,13 @@
from typing import Dict
-import hvac
-
-
-VAULT_CA_CERTIFICATE = "/usr/local/share/certs/nasqueron-vault-ca.crt"
+import secretsmith
+from secretsmith.vault.secrets import read_secret
def connect_to_vault():
- return hvac.Client(
- verify=VAULT_CA_CERTIFICATE,
- )
-
-
-def read_secret(
- vault_client, mount_point: str, prefix: str, key: str
-) -> Dict[str, str]:
- secret = vault_client.secrets.kv.read_secret_version(
- mount_point=mount_point,
- path=prefix + "/" + key,
- )
- return secret["data"]["data"]
+ return secretsmith.login()
def read_app_secret(vault_client, key: str) -> Dict[str, str]:
- return read_secret(vault_client, "apps", "rhyne-wyse", key)
+ return read_secret(vault_client, "apps", "rhyne-wyse/" + key)
diff --git a/tools/secretsmith/README.md b/tools/secretsmith/README.md
new file mode 100644
--- /dev/null
+++ b/tools/secretsmith/README.md
@@ -0,0 +1,120 @@
+## secretsmith
+
+The secretsmith Python package allows **connecting to Vault or OpenBao**,
+with support for several authentication methods, including using a token
+or AppRole.
+
+It also provides a simple wrapper to **query secrets from a kv2 store**.
+
+This is a high-level wrapper around [hvac](https://python-hvac.org/).
+
+At Nasqueron, we use this package to avoid writing boilerplate code in each
+application that needs to interact with Vault or OpenBao to:
+ - read a configuration file to determine login parameters
+ - query a simple password from kv2 store from a path
+
+When more and more applications need to interact with Vault or OpenBao,
+and use the same authentication methods, the same patterns to query secrets,
+to maintain this wrapper high-level library becomes useful.
+
+### Login
+
+Secretsmith uses the `hvac` library to connect to Vault or OpenBao.
+
+If nothing is specified, it will try to connect to Vault using the environment
+variables `VAULT_ADDR` and `VAULT_TOKEN`, or reading a token file at the
+default path. Especially convenient during the development workflow.
+
+When it's ready to be deployed, write a configuration file explaining how to
+connect to Vault or OpenBao.
+
+#### How to use in code?
+
+Call secretsmith.login() with the path to the configuration file:
+
+```python
+import secretsmith
+
+VAULT_CONFIG_PATH = '/path/to/config.yaml'
+
+vault_client = secretsmith.login(config_path=VAULT_CONFIG_PATH)
+```
+
+Then, you can use the client as a hvac library Vault client.
+
+We provide helper methods for common tasks, but you can also directly use hvac.
+
+#### Configuration file
+
+Secretsmith uses a YAML configuration file to determine the login parameters:
+
+```
+vault:
+ server:
+ url: https://127.0.0.1:8200
+ auth:
+ token: hvs.000000000000000000000000
+```
+
+When using AppRole, the configuration file will look like:
+
+```
+vault:
+ server:
+ url: https://127.0.0.1:8200
+ verify: /path/to/ca.pem
+ auth:
+ method: approle
+ role_id: e5a7b66e-5d08-da9c-7075-71984634b882
+ secret_id: 841771dc-11c9-bbc7-bcac-6a3945a69cd9
+```
+
+The format is based on the Vault execution module for SaltStack.
+
+The following parameters are supported:
+ - `server` - a block to specify the Vault or OpenBao server parameters
+ - `url` - the URL
+ - `verify` - the path to a CA certificate to verify the server's certificate
+ - `namespace` - the namespace to use (by default, will follow environment)
+ - `auth` - a block to specify the authentication method and parameters
+ - `method` - what authentication backend to use, by default 'token'
+
+Additional parameters are supported in the `auth` block depending
+on the authentication method.
+
+When the method is `token`:
+ - `token` - the token to use
+ - `token_file` - alternatively, the path to a file containing the token
+
+When the method is `approle`:
+ - `role_id` - the AppRole role ID (required)
+ - `secret_id` - the AppRole secret ID (optional)
+
+### Querying secrets
+
+For kv2, we also provide helper methods for more common use cases.
+
+If you store a password in the password field of the 'secret/app/db' path:
+
+```python
+import secretsmith
+from secretsmith.vault import secrets
+
+vault_client = secretsmith.login()
+password = secrets.get_password(vault_client, "secret", "app/db")
+```
+
+To get the full k/v store at the 'secret/app/db' path:
+
+```python
+secret = secrets.read_secret(vault_client, "secret", "app/db")
+```
+
+If you also store custom metadata, you can use:
+
+```python
+secret, metadata = secrets.read_secret_with_custom_metadata(vault_client, "secret", "app/db")
+```
+
+In all those examples, you need to replace "secret" by your kv2 mount point.
+The "secret" mount point is the default one if you didn't configure Vault.
diff --git a/tools/secretsmith/src/secretsmith/__init__.py b/tools/secretsmith/src/secretsmith/__init__.py
new file mode 100644
--- /dev/null
+++ b/tools/secretsmith/src/secretsmith/__init__.py
@@ -0,0 +1,9 @@
+# -------------------------------------------------------------
+# Secretsmith
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# License: Trivial work, not eligible to copyright
+# -------------------------------------------------------------
+
+
+from .vault.client import login
diff --git a/tools/secretsmith/src/secretsmith/vault/client.py b/tools/secretsmith/src/secretsmith/vault/client.py
new file mode 100644
--- /dev/null
+++ b/tools/secretsmith/src/secretsmith/vault/client.py
@@ -0,0 +1,68 @@
+# -------------------------------------------------------------
+# Secretsmith :: Vault :: client
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# License: BSD-2-Clause
+# -------------------------------------------------------------
+
+
+import os
+
+from hvac import Client
+
+from secretsmith.vault.config import load_config
+
+
+# -------------------------------------------------------------
+# General login
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+def login(config_path: str | None = None) -> Client:
+ if config_path is None:
+ config = {}
+ else:
+ config = load_config(config_path).get("vault", {})
+
+ config_server = config.get("server", {})
+ url = config_server.get("url", None)
+ verify = config_server.get("verify", None)
+ namespace = resolve_namespace(config_server)
+
+ config_auth = config.get("auth", {})
+ auth_method = config_auth.get("method", "token")
+ if auth_method == "token":
+ token = config_auth.get("token", None)
+ else:
+ token = None
+
+ client = Client(url=url, token=token, verify=verify, namespace=namespace)
+
+ if auth_method == "approle":
+ login_with_approle(client, config_auth)
+ elif auth_method != "token":
+ raise ValueError(f"Unknown auth method: {auth_method}")
+
+ return client
+
+
+def resolve_namespace(config: dict) -> str | None:
+ try:
+ return config["namespace"]
+ except KeyError:
+ return os.environ.get("VAULT_NAMESPACE", None)
+
+
+# -------------------------------------------------------------
+# Additional authentication backends
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+def login_with_approle(client: Client, config_auth: dict):
+ if "role_id" not in config_auth:
+ raise ValueError("Missing role_id in auth configuration")
+
+ client.auth.approle.login(
+ role_id=config_auth["role_id"],
+ secret_id=config_auth.get("secret_id", None),
+ )
diff --git a/tools/secretsmith/src/secretsmith/vault/config.py b/tools/secretsmith/src/secretsmith/vault/config.py
new file mode 100644
--- /dev/null
+++ b/tools/secretsmith/src/secretsmith/vault/config.py
@@ -0,0 +1,14 @@
+# -------------------------------------------------------------
+# Secretsmith :: Vault :: configuration
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# License: BSD-2-Clause
+# -------------------------------------------------------------
+
+
+import yaml
+
+
+def load_config(path: str) -> dict:
+ with open(path) as fd:
+ return yaml.safe_load(fd)
diff --git a/tools/secretsmith/src/secretsmith/vault/secrets.py b/tools/secretsmith/src/secretsmith/vault/secrets.py
new file mode 100644
--- /dev/null
+++ b/tools/secretsmith/src/secretsmith/vault/secrets.py
@@ -0,0 +1,79 @@
+# -------------------------------------------------------------
+# Secretsmith :: Vault :: KV secrets engine - version 2
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# License: BSD-2-Clause
+# -------------------------------------------------------------
+
+
+from typing import Any, Dict, Tuple
+
+from hvac import Client
+
+
+# -------------------------------------------------------------
+# Fetch secret from kv engine
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+def read_secret(client: Client, mount_point: str, secret_path: str) -> Dict[str, str]:
+ secret = client.secrets.kv.read_secret_version(
+ mount_point=mount_point,
+ path=secret_path,
+ raise_on_deleted_version=True,
+ )
+ return secret["data"]["data"]
+
+
+def read_secret_with_metadata(
+ client: Client, mount_point: str, secret_path: str
+) -> Tuple[dict[str, str], dict[str, Any]]:
+ """
+ Read a secret and return the data and the metadata dictionaries.
+ """
+ secret = client.secrets.kv.read_secret_version(
+ mount_point=mount_point,
+ path=secret_path,
+ raise_on_deleted_version=True,
+ )
+
+ return secret["data"]["data"], secret["data"]["metadata"]
+
+
+def read_secret_with_custom_metadata(
+ client: Client, mount_point: str, secret_path: str
+) -> Tuple[dict[str, str], dict[str, Any]]:
+ """
+ Read a secret and return the data and the metadata dictionaries.
+
+ The custom metadata keys are directly merged into the metadata dictionary.
+ """
+ data, metadata = read_secret_with_metadata(client, mount_point, secret_path)
+
+ if "custom_metadata" in metadata:
+ metadata.update(metadata["custom_metadata"])
+ del metadata["custom_metadata"]
+
+ return data, metadata
+
+
+# -------------------------------------------------------------
+# Helpers to select common fields
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+def get_username(client: Client, mount_point: str, secret_path: str) -> str:
+ return get_field(client, mount_point, secret_path, "username")
+
+
+def get_password(client: Client, mount_point: str, secret_path: str) -> str:
+ return get_field(client, mount_point, secret_path, "password")
+
+
+def get_field(client: Client, mount_point: str, secret_path: str, field: str) -> str:
+ secret = read_secret(client, mount_point, secret_path)
+
+ try:
+ return secret[field]
+ except KeyError:
+ raise ValueError(f"Missing {field} field in {mount_point}/{secret_path}")
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Sep 21, 19:25 (13 h, 15 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3002749
Default Alt Text
D3697.id.diff (12 KB)
Attached To
Mode
D3697: Configure Vault connection with secretsmith
Attached
Detach File
Event Timeline
Log In to Comment