Bitwarden CRD Operator

Artifact Hub Release Workflow Status

Bitwarden CRD Operator is a kubernetes Operator based on kopf. The goal is to create kubernetes native secret objects from bitwarden.

Bitwarden CRD Operator Logo

DISCLAIMER: This project is still very work in progress :)

Getting started

You will need a ClientID and ClientSecret (where to get these) as well as your password. Expose these to the operator as described in this example:

env:
  - name: BW_HOST
    value: "https://bitwarden.your.tld.org"
  - name: BW_CLIENTID
    value: "user.your-client-id"
  - name: BW_CLIENTSECRET
    value: "YoUrCliEntSecRet"
  - name: BW_PASSWORD
    value: "YourSuperSecurePassword"

You can also create a secret manually with these information and reference the existing secret like this in the values.yaml:

externalConfigSecret:
  enabled: true
  name: "my-existing-secret"

BW_HOST can be omitted if you are using the Bitwarden SaaS offering. After that it is a basic helm deployment:

helm repo add bitwarden-operator https://lerentis.github.io/bitwarden-crd-operator
helm repo update 
kubectl create namespace bw-operator
helm upgrade --install --namespace bw-operator -f values.yaml bw-operator bitwarden-operator/bitwarden-crd-operator

Wording

The Bitwarden API distinguishes between simple login entries (username/password), custom fields, and attachments; the operator exposes these via the secretScope setting to control how items are mapped into Kubernetes Secrets.

  • login — Use when you need the entry's username or password; specify secretScope: login.
  • fields — Use for custom key/value fields stored on an item; specify secretScope: fields.
  • attachment — Use for files attached to a Bitwarden item; specify secretScope: attachment.

Examples:

Login entry:

content:
  - element:
      secretName: username
      secretRef: my-username-key
      secretScope: login

Custom field:

content:
  - element:
      secretName: api_key
      secretRef: my-api-key
      secretScope: fields

Attachment (file):

content:
  - element:
      secretName: some-file.txt
      secretRef: file-key
      secretScope: attachment

BitwardenSecret

Create a CRD object like this:

---
apiVersion: "lerentis.uploadfilter24.eu/v1beta8"
kind: BitwardenSecret
metadata:
  name: name-of-your-management-object
spec:
  content:
    - element:
        secretName: nameOfTheFieldInBitwarden
        secretRef: nameOfTheKeyInTheSecretToBeCreated 
        secretScope: login
    - element:
        secretName: nameOfAnotherFieldInBitwarden
        secretRef: nameOfAnotherKeyInTheSecretToBeCreated 
        secretScope: login
  id: "A Secret ID from bitwarden"
  name: "Name of the secret to be created"
  secretType:
  namespace: "Namespace of the secret to be created"
  labels:
    key: value
  annotations:
    key: value

The ID can be extracted from the browser URL. The resulting secret looks like:

apiVersion: v1
data:
  nameOfTheKeyInTheSecretToBeCreated: "base64 encoded value of TheFieldInBitwarden"
  nameOfAnotherKeyInTheSecretToBeCreated: "base64 encoded value of AnotherFieldInBitwarden"
kind: Secret
metadata:
  annotations:
    managed: bitwarden-secrets.lerentis.uploadfilter24.eu
    managedObject: bw-operator/test
  labels:
    key: value
  name: name-of-your-management-object
  namespace: default
type: Opaque

RegistryCredential

For managing registry credentials, create:

---
apiVersion: "lerentis.uploadfilter24.eu/v1beta8"
kind: RegistryCredential
metadata:
  name: name-of-your-management-object
spec:
  usernameRef: nameOfTheFieldInBitwarden
  passwordRef: nameOfTheFieldInBitwarden
  registry: "docker.io"
  id: "A Secret ID from bitwarden"
  name: "Name of the secret to be created"
  namespace: "Namespace of the secret to be created"
  labels:
    key: value
  annotations:
    key: value
apiVersion: v1
data:
  .dockerconfigjson: "base64 encoded json auth string for your registry"
kind: Secret
metadata:
  annotations:
    managed: bitwarden-secrets.lerentis.uploadfilter24.eu
    managedObject: bw-operator/test
  labels:
    key: value
  name: name-of-your-management-object
  namespace: default
type: dockerconfigjson

BitwardenTemplate

A template-driven type that allows jinja2 templates with a helper bitwarden_lookup:

---
apiVersion: "lerentis.uploadfilter24.eu/v1beta8"
kind: BitwardenTemplate
metadata:
  name: name-of-your-management-object
spec:
  name: "Name of the secret to be created"
  secretType:
  namespace: "Namespace of the secret to be created"
  labels:
    key: value
  annotations:
    key: value
  content:
    - element:
        filename: config.yaml
        template: |
          ---
          api:
            enabled: True
            key: {{ bitwarden_lookup("A Secret ID from bitwarden", "login or fields or attachment", "name of a field in bitwarden") }}
            allowCrossOrigin: false
            apps:
              "some.app.identifier:some_version":
                pubkey: {{ bitwarden_lookup("A Secret ID from bitwarden", "login or fields or attachment", "name of a field in bitwarden") }}
                enabled: true

Resulting secret example:

apiVersion: v1
data:
  Key of the secret to be created: "base64 encoded and rendered template with secrets injected directly from bitwarden"
kind: Secret
metadata:
  annotations:
    managed: bitwarden-template.lerentis.uploadfilter24.eu
    managedObject: namespace/name-of-your-management-object
  labels:
    key: value
  name: Name of the secret to be created
  namespace: Namespace of the secret to be created
type: Opaque

The signature of bitwarden_lookup is (item_id, scope, field) and details about parameters are described in the original docs.

Configuration parameters

The operator uses the bitwarden CLI and sync behaviour can be configured with env vars such as BW_SYNC_INTERVAL, BW_FORCE_SYNC and BW_RELOGIN_INTERVAL (defaults to 3600 seconds).