Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,23 @@ make ci-local
- ClickHouse sink de-duplicates within each batch and skips IDs already present in ClickHouse before insert; skipped rows are exposed via `tap_clickhouse_dedup_skipped_total`.
- NATS publish retries and JetStream advisories are exposed via `tap_nats_publish_retries_total{reason}`, `tap_nats_publish_retry_delay_seconds`, and `tap_jetstream_advisories_total{kind}`.

## Vault-backed secrets

Any string config value can be sourced from Vault by using a `vault://` reference:

- Format: `vault://<path>#<key>` (defaults to key `value` when `#<key>` is omitted).
- Example: `providers.generic.secret: vault://secret/data/homelab/ensemble-tap/runtime#generic-webhook-secret`
- Example: `server.admin_token: vault://secret/data/homelab/ensemble-tap/runtime#admin-token`

Vault auth config lives under `vault.*`:

- `vault.address` (or `VAULT_ADDR`) and optional `vault.namespace`
- `vault.auth_method`: `kubernetes` (default) or `token`
- `vault.auth_method=kubernetes`: set `vault.kubernetes_role`, optional `vault.kubernetes_mount_path` (default `kubernetes`), and optional `vault.kubernetes_jwt_file` (default `/var/run/secrets/kubernetes.io/serviceaccount/token`)
- `vault.auth_method=token`: set `vault.token`, `vault.token_file`, or `VAULT_TOKEN`

In Kubernetes, if you use `vault.auth_method=kubernetes`, ensure the Pod has a service-account JWT available (for Helm chart: set `serviceAccount.automount=true` or mount a projected token and set `vault.kubernetes_jwt_file` accordingly).

## Admin Endpoints

When any admin token is configured (`server.admin_token`, `server.admin_token_secondary`, `server.admin_token_read`, `server.admin_token_replay`, `server.admin_token_cancel`), these endpoints are available:
Expand Down
26 changes: 26 additions & 0 deletions charts/ensemble-tap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,32 @@ Notes:
- `config.server.admin_rate_limit_per_sec` and `config.server.admin_rate_limit_burst` must both be greater than `0`.
- `config.server.admin_allowed_cidrs` and `config.server.admin_mtls_required` provide network and client-cert guardrails for admin routes.

## Resolve secrets from Vault

Use `vault://<path>#<key>` in config values and configure `config.vault.*`.

Example (Kubernetes auth):

```bash
helm upgrade --install ensemble-tap ./charts/ensemble-tap \
--namespace ensemble \
--set serviceAccount.automount=true \
--set env[0].name=VAULT_ADDR \
--set env[0].value='https://vault.vault.svc:8200' \
--set config.vault.address='${VAULT_ADDR}' \
--set config.vault.auth_method=kubernetes \
--set config.vault.kubernetes_role=ensemble-tap-runtime \
--set config.vault.kubernetes_mount_path=kubernetes \
--set config.providers.generic.mode=webhook \
--set config.providers.generic.secret='vault://secret/data/homelab/ensemble-tap/runtime#generic-webhook-secret' \
--set config.server.admin_token='vault://secret/data/homelab/ensemble-tap/runtime#admin-token'
```

Notes:
- `config.vault.auth_method` supports `kubernetes` (default) and `token`.
- With `token` auth, set `config.vault.token`, `config.vault.token_file`, or `VAULT_TOKEN`.
- Reference format defaults to key `value` when `#<key>` is omitted (for example `vault://secret/data/path`).

## Tune NATS and ClickHouse

```bash
Expand Down
39 changes: 39 additions & 0 deletions charts/ensemble-tap/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,45 @@
],
"additionalProperties": false
},
"vault": {
"type": "object",
"properties": {
"address": {
"type": "string",
"description": "Vault API address. Required when any config field uses vault:// references unless provided via VAULT_ADDR."
},
"namespace": {
"type": "string",
"description": "Optional Vault Enterprise namespace header."
},
"auth_method": {
"type": "string",
"enum": ["kubernetes", "token"],
"description": "Vault auth method used to resolve vault:// references."
},
"token": {
"type": "string",
"description": "Vault token used when auth_method=token (falls back to VAULT_TOKEN when empty)."
},
"token_file": {
"type": "string",
"description": "Path to a file containing Vault token when auth_method=token."
},
"kubernetes_role": {
"type": "string",
"description": "Vault role used when auth_method=kubernetes."
},
"kubernetes_mount_path": {
"type": "string",
"description": "Vault Kubernetes auth mount path."
},
"kubernetes_jwt_file": {
"type": "string",
"description": "Path to Kubernetes service account JWT used for Vault login."
}
},
"additionalProperties": false
},
"server": {
"type": "object",
"properties": {
Expand Down
9 changes: 9 additions & 0 deletions charts/ensemble-tap/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,15 @@ config:
consumer_max_request_max_bytes: 0
insert_timeout: 10s
retention_ttl: 8760h
vault:
address: ${VAULT_ADDR}
namespace: ${VAULT_NAMESPACE}
auth_method: kubernetes
token: ${VAULT_TOKEN}
token_file: ""
kubernetes_role: ""
kubernetes_mount_path: kubernetes
kubernetes_jwt_file: /var/run/secrets/kubernetes.io/serviceaccount/token
server:
port: 8080
base_path: /webhooks
Expand Down
15 changes: 15 additions & 0 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,21 @@ clickhouse:
insert_timeout: 10s
retention_ttl: 8760h

vault:
# Optional. If set, any config string with `vault://path#key` is resolved at boot.
# `address` also defaults from VAULT_ADDR when omitted.
address: ${VAULT_ADDR}
namespace: ${VAULT_NAMESPACE}
# Supported: kubernetes, token
auth_method: kubernetes
# Used when auth_method=token. Falls back to VAULT_TOKEN when empty.
token: ${VAULT_TOKEN}
token_file: ""
# Used when auth_method=kubernetes.
kubernetes_role: ensemble-tap-runtime
kubernetes_mount_path: kubernetes
kubernetes_jwt_file: /var/run/secrets/kubernetes.io/serviceaccount/token

server:
port: 8080
base_path: /webhooks
Expand Down
27 changes: 27 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type Config struct {
Providers map[string]ProviderConfig `koanf:"providers"`
NATS NATSConfig `koanf:"nats"`
ClickHouse ClickHouseConfig `koanf:"clickhouse"`
Vault VaultConfig `koanf:"vault"`
Server ServerConfig `koanf:"server"`
State StateConfig `koanf:"state"`
}
Expand Down Expand Up @@ -218,6 +219,17 @@ type StateConfig struct {
SQLitePath string `koanf:"sqlite_path"`
}

type VaultConfig struct {
Address string `koanf:"address"`
Namespace string `koanf:"namespace"`
AuthMethod string `koanf:"auth_method"`
Token string `koanf:"token"`
TokenFile string `koanf:"token_file"`
KubernetesRole string `koanf:"kubernetes_role"`
KubernetesMountPath string `koanf:"kubernetes_mount_path"`
KubernetesJWTFile string `koanf:"kubernetes_jwt_file"`
}

func (c *Config) ApplyDefaults() {
if c.Providers == nil {
c.Providers = make(map[string]ProviderConfig)
Expand Down Expand Up @@ -375,6 +387,18 @@ func (c *Config) ApplyDefaults() {
if c.State.SQLitePath == "" {
c.State.SQLitePath = "tap-state.db"
}
if strings.TrimSpace(c.Vault.Address) == "" {
c.Vault.Address = strings.TrimSpace(os.Getenv("VAULT_ADDR"))
}
if strings.TrimSpace(c.Vault.AuthMethod) == "" {
c.Vault.AuthMethod = "kubernetes"
}
if strings.TrimSpace(c.Vault.KubernetesMountPath) == "" {
c.Vault.KubernetesMountPath = "kubernetes"
}
if strings.TrimSpace(c.Vault.KubernetesJWTFile) == "" {
c.Vault.KubernetesJWTFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
}
}

func (c Config) Validate() error {
Expand Down Expand Up @@ -975,6 +999,9 @@ func Load(path string) (Config, error) {
return Config{}, fmt.Errorf("decode config: %w", err)
}
cfg.ApplyDefaults()
if err := cfg.resolveVaultReferences(); err != nil {
return Config{}, fmt.Errorf("resolve vault references: %w", err)
}
if err := cfg.Validate(); err != nil {
return Config{}, fmt.Errorf("validate config: %w", err)
}
Expand Down
Loading
Loading