Add Vault secrets provider with hash-separated keys and automatic JWT generation#109
Add Vault secrets provider with hash-separated keys and automatic JWT generation#109
Conversation
Co-authored-by: NiklasRosenstein <1318438+NiklasRosenstein@users.noreply.github.com>
Co-authored-by: NiklasRosenstein <1318438+NiklasRosenstein@users.noreply.github.com>
…sistency Co-authored-by: NiklasRosenstein <1318438+NiklasRosenstein@users.noreply.github.com>
…cking Co-authored-by: NiklasRosenstein <1318438+NiklasRosenstein@users.noreply.github.com>
| - "database.credentials.username" retrieves nested fields | ||
|
|
||
| Args: | ||
| key: The key of the secret to retrieve, with optional dot notation for nested access. |
There was a problem hiding this comment.
@copilot Using dot notation for nested access makes it impossible to use secrets with dots in the path. We should separate the secret path with fields for nested access with a hash character followed by dot notation, e.g. path/to/secret#database.password.
There was a problem hiding this comment.
Done in c2eea33. Changed key format to use hash separator (#) between path and field access. Now supports secrets with dots in paths: "db.prod#password" or "my-app.v2#field". Updated all tests and documentation.
|
|
||
| return result | ||
|
|
||
| def set(self, key: str, value: SecretValue, /) -> None: |
There was a problem hiding this comment.
@copilot The Vault implementation should not support setting/unsetting. Let's simply raise a NotImplementedError and document this behavior. Make sure to cleanup unused code after removing these implementations.
There was a problem hiding this comment.
Done in c2eea33. Both set() and unset() now raise NotImplementedError with clear messages directing users to manage secrets through Vault's interface. Removed ~120 lines of unused implementation code and updated tests.
|
|
||
| def _authenticate_with_jwt(self) -> None: | ||
| """Authenticate with Vault using Kubernetes JWT token (for ArgoCD context).""" | ||
| jwt_path = Path("/var/run/secrets/kubernetes.io/serviceaccount/token") |
There was a problem hiding this comment.
@copilot The workload identity method described in #108 actually does not refer to the Kubernetes token, but instead through a token issued by Nyl to assert the ArgoCD application that is being templated.
I think we should consider allowing both options: Using the Kubernetes service account for simple usage, and Nyl-issuer ArgoCD-application specific workload identities for mutli-tenant deployments.
The payload for a token could be e.g.
{
"iss": "https://my-argocd.example.com/#nyl-v1",
"aud": "https://my-vault.example.com:8200",
"sub": "project:default:application:my-argo-app",
"argocd_project": "default",
"argocd_app": "my-argo-app",
"repository": "git@github.com:example-org/manifests.git"
}There was a problem hiding this comment.
Done in c2eea33. Added support for both authentication methods via jwt_auth_method config:
"kubernetes"(default): Uses Kubernetes service account token"nyl": Uses Nyl-issued JWT fromNYL_VAULT_JWTenv var with ArgoCD app claims
The Nyl JWT mode expects tokens with payload including argocd_project, argocd_app, and repository fields as described. Both methods coexist and are selected via configuration.
…T auth Co-authored-by: NiklasRosenstein <1318438+NiklasRosenstein@users.noreply.github.com>
| - repository: Git repository URL | ||
| """ | ||
| # Check for Nyl-issued JWT token in environment variable | ||
| jwt_token = os.environ.get("NYL_VAULT_JWT") |
There was a problem hiding this comment.
@copilot But this code is Nyl, we need to generate a token here (or use one we previously generated). I think a good place for issuing a new token would be in the main entrypoint when we detect that we're running in ArgoCD with the Nyl JWT auth method enabled.
There was a problem hiding this comment.
Done in f399007. Nyl now generates JWT tokens internally:
- Added
src/nyl/tools/jwt.pymodule withgenerate_nyl_jwt_from_argocd_env()function - Reads ArgoCD environment variables (
ARGOCD_APP_NAME,ARGOCD_APP_PROJECT_NAME,ARGOCD_APP_SOURCE_REPO_URL) - Generates HS256-signed JWT tokens with claims:
iss,aud,sub,argocd_project,argocd_app,repository - Token generation happens in template command entrypoint (after loading secrets config, before use)
- Added
jwt_signing_keyconfig option (orNYL_VAULT_JWT_SIGNING_KEYenv var) as shared secret - VaultSecretProvider receives generated token via
set_nyl_jwt_token()method - All tests passing (26 total: 18 Vault + 8 JWT)
Co-authored-by: NiklasRosenstein <1318438+NiklasRosenstein@users.noreply.github.com>
Adds HashiCorp Vault as a read-only secrets provider to support environments where the Vault Secrets Operator cannot inject values (e.g., templated config files, Helm chart limitations). Enables secure multi-tenant deployments with flexible JWT authentication options.
Implementation
VaultSecretProvider (
src/nyl/secrets/vault.py)kubernetes(default): Kubernetes service account token for simple deploymentsnyl: Nyl automatically generates ArgoCD application-specific JWT tokens for multi-tenant environmentsVAULT_TOKENenv var or~/.vault-token(local dev)ARGOCD_APP_NAMEpresencepath/to/secret#field.nested.pathsupports secrets with dots in pathsset()andunset()raiseNotImplementedErrorJWT Token Generation (
src/nyl/tools/jwt.py)jwt_signing_key)iss,aud,sub,argocd_project,argocd_app,repositoryConfiguration
Usage
Design Notes
#): Separates secret path from field access, allowing dots in secret pathsDependencies
hvac>=2.3.0(no known vulnerabilities)pyjwt>=2.8.0for JWT token generation (no known vulnerabilities)Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.