Bitwarden support using webhook provider
Bitwarden is an integrated open source password management solution for individuals, teams, and business organizations.
How does it work?
To make external-secrets compatible with Bitwarden, we need:
- External Secrets Operator >= 0.8.0
- Multiple (Cluster)SecretStores using the webhook provider
- BitWarden CLI image running
bw serve
When you create a new external-secret object, the External Secrets webhook provider will query the Bitwarden CLI pod that is synced with the Bitwarden server.
Requirements
- Bitwarden account (it also works with Vaultwarden!)
- A Kubernetes secret which contains your Bitwarden credentials
- A Docker image running the Bitwarden CLI. You could use
ghcr.io/charlesthomas/bitwarden-cli:2023.12.1
or build your own.
Here is an example of a Dockerfile used to build the image:
FROM debian:sid
ENV BW_CLI_VERSION=2023.12.1
RUN apt update && \
apt install -y wget unzip && \
wget https://github.com/bitwarden/clients/releases/download/cli-v${BW_CLI_VERSION}/bw-linux-${BW_CLI_VERSION}.zip && \
unzip bw-linux-${BW_CLI_VERSION}.zip && \
chmod +x bw && \
mv bw /usr/local/bin/bw && \
rm -rfv *.zip
COPY entrypoint.sh /
CMD ["/entrypoint.sh"]
And the content of entrypoint.sh
:
#!/bin/bash
set -e
bw config server ${BW_HOST}
export BW_SESSION=$(bw login ${BW_USER} --passwordenv BW_PASSWORD --raw)
bw unlock --check
echo 'Running `bw server` on port 8087'
bw serve --hostname 0.0.0.0 #--disable-origin-protection
Deploy Bitwarden credentials
apiVersion: v1
data:
BW_HOST: ...
BW_USERNAME: ...
BW_PASSWORD: ....
kind: Secret
metadata:
name: bitwarden-cli
namespace: bitwarden
type: Opaque
Deploy Bitwarden CLI container
apiVersion: apps/v1
kind: Deployment
metadata:
name: bitwarden-cli
namespace: bitwarden
labels:
app.kubernetes.io/instance: bitwarden-cli
app.kubernetes.io/name: bitwarden-cli
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app.kubernetes.io/name: bitwarden-cli
app.kubernetes.io/instance: bitwarden-cli
template:
metadata:
labels:
app.kubernetes.io/name: bitwarden-cli
app.kubernetes.io/instance: bitwarden-cli
spec:
containers:
- name: bitwarden-cli
image: YOUR_BITWARDEN_CLI_IMAGE
imagePullPolicy: IfNotPresent
env:
- name: BW_HOST
valueFrom:
secretKeyRef:
name: bitwarden-cli
key: BW_HOST
- name: BW_USER
valueFrom:
secretKeyRef:
name: bitwarden-cli
key: BW_USERNAME
- name: BW_PASSWORD
valueFrom:
secretKeyRef:
name: bitwarden-cli
key: BW_PASSWORD
ports:
- name: http
containerPort: 8087
protocol: TCP
livenessProbe:
exec:
command:
- wget
- -q
- http://127.0.0.1:8087/sync?force=true
- --post-data=''
initialDelaySeconds: 20
failureThreshold: 3
timeoutSeconds: 10
periodSeconds: 120
readinessProbe:
tcpSocket:
port: 8087
initialDelaySeconds: 20
failureThreshold: 3
timeoutSeconds: 1
periodSeconds: 10
startupProbe:
tcpSocket:
port: 8087
initialDelaySeconds: 10
failureThreshold: 30
timeoutSeconds: 1
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: bitwarden-cli
namespace: bitwarden
labels:
app.kubernetes.io/instance: bitwarden-cli
app.kubernetes.io/name: bitwarden-cli
annotations:
spec:
type: ClusterIP
ports:
- port: 8087
targetPort: http
protocol: TCP
name: http
selector:
app.kubernetes.io/name: bitwarden-cli
app.kubernetes.io/instance: bitwarden-cli
---
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
namespace: bitwarden
name: external-secret-2-bw-cli
spec:
podSelector:
matchLabels:
app.kubernetes.io/instance: bitwarden-cli
app.kubernetes.io/name: bitwarden-cli
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/instance: external-secrets
app.kubernetes.io/name: external-secrets
NOTE: Deploying a network policy is recommended since there is no authentication to query the Bitwarden CLI, which means that your secrets are exposed.
NOTE: In this example the Liveness probe is querying /sync to ensure that the Bitwarden CLI is able to connect to the server and is also synchronised. (The secret sync is only every 2 minutes in this example)
Deploy (Cluster)SecretStores
There are four possible (Cluster)SecretStores to deploy, each can access different types of fields from an item in the Bitwarden vault. It is not required to deploy them all.
---
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: bitwarden-login
spec:
provider:
webhook:
url: "http://bitwarden-cli:8087/object/item/{{ .remoteRef.key }}"
headers:
Content-Type: application/json
result:
jsonPath: "$.data.login.{{ .remoteRef.property }}"
---
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: bitwarden-fields
spec:
provider:
webhook:
url: "http://bitwarden-cli:8087/object/item/{{ .remoteRef.key }}"
result:
jsonPath: "$.data.fields[?@.name==\"{{ .remoteRef.property }}\"].value"
---
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: bitwarden-notes
spec:
provider:
webhook:
url: "http://bitwarden-cli:8087/object/item/{{ .remoteRef.key }}"
result:
jsonPath: "$.data.notes"
---
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: bitwarden-attachments
spec:
provider:
webhook:
url: "http://bitwarden-cli:8087/object/attachment/{{ .remoteRef.property }}?itemid={{ .remoteRef.key }}"
result: {}
Usage
(Cluster)SecretStores:
bitwarden-login
: Use to get theusername
orpassword
fieldsbitwarden-fields
: Use to get custom fieldsbitwarden-notes
: Use to get notesbitwarden-attachments
: Use to get attachments
remoteRef:
-
key
: ID of a secret, which can be found in the URLitemId
parameter:https://myvault.com/#/vault?type=login&itemId=........-....-....-....-............
s -
property
: Name of the field to accessusername
for the username of a secret (bitwarden-login
SecretStore)password
for the password of a secret (bitwarden-login
SecretStore)name_of_the_custom_field
for any custom field (bitwarden-fields
SecretStore)id_or_name_of_the_attachment
for any attachment (bitwarden-attachment
, SecretStore)
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: my-secrets
namespace: default
spec:
target:
name: my-secrets
deletionPolicy: Delete
template:
type: Opaque
data:
username: |-
{{ .username }}
password: |-
{{ .password }}
postgres-password: |-
{{ .postgres_password }}
postgres-replication-password: |-
{{ .postgres_replication_password }}
db_url: |-
postgresql://{{ .username }}:{{ .password }}@my-postgresql:5432/mydb
service_account_key: |-
{{ .service_account_key }}
ssh_pub_key: |-
{{ .ssh_pub_key }}
data:
- secretKey: username
sourceRef:
storeRef:
name: bitwarden-login
kind: ClusterSecretStore # or SecretStore
remoteRef:
key: aaaabbbb-cccc-dddd-eeee-000011112222
property: username
- secretKey: password
sourceRef:
storeRef:
name: bitwarden-login
kind: ClusterSecretStore # or SecretStore
remoteRef:
key: aaaabbbb-cccc-dddd-eeee-000011112222
property: password
- secretKey: postgres_password
sourceRef:
storeRef:
name: bitwarden-fields
kind: ClusterSecretStore # or SecretStore
remoteRef:
key: aaaabbbb-cccc-dddd-eeee-000011112222
property: admin-password
- secretKey: postgres_replication_password
sourceRef:
storeRef:
name: bitwarden-fields
kind: ClusterSecretStore # or SecretStore
remoteRef:
key: aaaabbbb-cccc-dddd-eeee-000011112222
property: postgres-replication-password
- secretKey: service_account_key
sourceRef:
storeRef:
name: bitwarden-notes
kind: ClusterSecretStore # or SecretStore
remoteRef:
key: service_account_key
- secretKey: ssh_pub_key
sourceRef:
storeRef:
name: bitwarden-attachments
kind: ClusterSecretStore # or SecretStore
remoteRef:
key: aaaabbbb-cccc-dddd-eeee-000011112222
property: id_rsa.pub