Skip to content

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: 1
            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 the username or password fields
  • bitwarden-fields: Use to get custom fields
  • bitwarden-notes: Use to get notes
  • bitwarden-attachments: Use to get attachments

remoteRef:

  • key: ID of a secret, which can be found in the URL itemId parameter: https://myvault.com/#/vault?type=login&itemId=........-....-....-....-............s

  • property: Name of the field to access

    • username 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