NG: secret stores with lookup by path or id
Update Kubernetes Services Wiki / Generate and Update K8s Wiki (push) Successful in 20s

Move the Vaultwarden bridge and the ClusterSecretStores out of external-secrets
into secret-stores (wave 4, after vaultwarden). A store key may be an item UUID
or an item name used as a path (k8s/<namespace>/<what>), matched exactly.
This commit is contained in:
Aleksandr Bogomiakov committed 2026-10-06 15:13:09 +01:00
1 parent 41729bf256
commit 7db427f332
6 files changed
+70 -45

No files matched your search

+5
View File
@@ -53,3 +53,8 @@ apps:
namespace: keycloak
wave: 3
skipDryRunOnMissingResource: true # ExternalSecret/Certificate CRDs come from earlier waves
- name: secret-stores
path: k8s-ng/core/secret-stores
namespace: external-secrets
wave: 4 # reads the Vaultwarden vault, so it comes after vaultwarden
skipDryRunOnMissingResource: true # ClusterSecretStore CRD comes from the external-secrets app
@@ -1,10 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
# Enable after the Vaultwarden bot account exists and the bitwarden-cli
# secret is created (see the header of bitwarden-store.yaml):
# resources:
# - bitwarden-store.yaml
# The Vaultwarden bridge and the ClusterSecretStores are in ../secret-stores (they need Vaultwarden).
helmCharts:
- name: external-secrets
@@ -1,15 +1,11 @@
# ---
# apiVersion: v1
# kind: Secret
# metadata:
# name: bitwarden-cli
# namespace: external-secrets
# data:
# BW_HOST: base64(url)
# BW_USERNAME: base64(name)
# BW_PASSWORD: base64(pass)
# Vaultwarden bot - 81212111-6350-4069-8bcf-19a67d3964a5
---
# Bridge between External Secrets and Vaultwarden: `bw serve` logged in as the
# bot account. Needs the Secret bitwarden-cli (created by hand; the bot logs in
# with email + password, so keep password login enabled for it, i.e. no
# SSO_ONLY, or switch the image to API-key login):
# kubectl -n external-secrets create secret generic bitwarden-cli \
# --from-literal=BW_HOST=https://vw.btwiusearch.net \
# --from-literal=BW_USERNAME=<bot email> --from-literal=BW_PASSWORD=<password>
# Image source: Dockerfile-bitwarden-cli in this directory.
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -37,7 +33,7 @@ spec:
containers:
- name: bitwarden-cli
image: ultradesu/bitwarden-client:2025.12.1
imagePullPolicy: Always
imagePullPolicy: IfNotPresent
resources:
requests:
memory: "128Mi"
@@ -98,7 +94,6 @@ metadata:
labels:
app.kubernetes.io/instance: bitwarden-cli
app.kubernetes.io/name: bitwarden-cli
annotations:
spec:
type: ClusterIP
ports:
@@ -110,8 +105,9 @@ spec:
app.kubernetes.io/name: bitwarden-cli
app.kubernetes.io/instance: bitwarden-cli
---
kind: NetworkPolicy
# Only the External Secrets controller may talk to the bridge: it serves the whole vault.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: external-secret-2-bw-cli
spec:
@@ -125,29 +121,3 @@ spec:
matchLabels:
app.kubernetes.io/instance: external-secrets
app.kubernetes.io/name: external-secrets
---
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: vaultwarden-login
spec:
provider:
webhook:
url: "http://bitwarden-cli:8087/object/item/{{ .remoteRef.key }}"
headers:
Content-Type: application/json
result:
jsonPath: "$.data.{{ .remoteRef.property }}"
---
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: vaultwarden-fields
spec:
provider:
webhook:
url: "http://bitwarden-cli:8087/object/item/{{ .remoteRef.key }}"
result:
jsonPath: "$.data.fields[?@.name==\"{{ .remoteRef.property }}\"].value"
@@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- bitwarden-cli.yaml
- stores.yaml
+47
View File
@@ -0,0 +1,47 @@
# ClusterSecretStores in front of the Vaultwarden vault that belongs to the bot
# account (the vault holds server secrets only, nothing personal).
#
# `key` may be either
# - the item UUID (old style, changes whenever items are imported again), or
# - the item path: the item NAME, which by convention is the full path
# k8s/<namespace>/<what> e.g. k8s/cert-manager/cloudflare-api-token
# Names are looked up by exact match (a name that is a substring of another
# item name, login or URL is fine). Do not use double quotes in names.
#
# vaultwarden-login: `property` is a path inside the item: login.username,
# login.password, notes, fields[0].value, ...
# vaultwarden-fields: `property` is the NAME of a custom field of the item.
#
# data:
# - secretKey: password
# sourceRef:
# storeRef: {name: vaultwarden-login, kind: ClusterSecretStore}
# remoteRef:
# key: k8s/cert-manager/cloudflare-api-token
# property: login.password
#
# The bridge syncs the vault every couple of minutes, so a new item becomes
# visible to ESO after that.
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: vaultwarden-login
spec:
provider:
webhook:
url: 'http://bitwarden-cli.external-secrets.svc:8087/{{ if regexMatch "^[0-9a-fA-F-]{36}$" .remoteRef.key }}object/item/{{ .remoteRef.key }}{{ else }}list/object/items?search={{ .remoteRef.key }}{{ end }}'
headers:
Content-Type: application/json
result:
jsonPath: '{{ if regexMatch "^[0-9a-fA-F-]{36}$" .remoteRef.key }}$.data.{{ .remoteRef.property }}{{ else }}$.data.data[?@.name=="{{ .remoteRef.key }}"].{{ .remoteRef.property }}{{ end }}'
---
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
name: vaultwarden-fields
spec:
provider:
webhook:
url: 'http://bitwarden-cli.external-secrets.svc:8087/{{ if regexMatch "^[0-9a-fA-F-]{36}$" .remoteRef.key }}object/item/{{ .remoteRef.key }}{{ else }}list/object/items?search={{ .remoteRef.key }}{{ end }}'
result:
jsonPath: '{{ if regexMatch "^[0-9a-fA-F-]{36}$" .remoteRef.key }}$.data.fields[?@.name=="{{ .remoteRef.property }}"].value{{ else }}$.data.data[?@.name=="{{ .remoteRef.key }}"].fields[?@.name=="{{ .remoteRef.property }}"].value{{ end }}'