Bitwarden - Funky Penguin's Geek Cookbook

Heard about the latest passsword breach (since lunch)? HaveYouBeenPowned lately? Passwords are broken, and as the amount of sites for which you need to store credentials grows exponetially, so does the risk of using a common password.


This is a companion discussion topic for the original entry at https://geek-cookbook.funkypenguin.co.nz/recipes/bitwarden/

Hi!
I will give this recipe a try. I have been using the chrome/google password manager for a couple of years. Probably not my smartest move :slight_smile:

In you docker compose file, you are refering to an environment file:
/var/data/config/bitwarden/bitwarden.env
But you do not specify its content.
What should we do with this file ?

OK, found everything i need to know here:

Wow, that’s brilliant. I wasn’t aware of all that, I’ll add it to the recipe, thanks!

I am really impressed by bitwarden by the way: Much better than lastpass, and as convenient as chrome password manager once the chrome extension and android app are installed.
The integration of TOTP to each account is a great improvement.

I have not been able to use my U2F usb key to secure the access to bitwarden at this stage. not sure why. The key seems not detected by the browser even if it works well on other website.
The 2FA authentification with authenticator/authy works very well though.

Regarding the env variables, I am using myself config fully centralized on portainer: i create app template on github that are called by Portainer to create the stack.
For obvious safety reasons , Some critical parameters are set as variables define within portainer.

example:

Works like a charm but i am also considering using the config and secrets mechanism of docker through portainer.

I agree, BitWarden is really nicely polished. If I wasn’t already invested in the 1Password world, along with the rest of the family, I’d be a convert :slight_smile:

Agreed. I like BitWarden, but have recently gotten everyone migrated to 1Password.

Hi @sorg! I also could not get U2F working with my Yubikey. However, I was able to make Yubico OTP work which requires setting these two environment variables:

YUBICO_CLIENT_ID
YUBICO_SECRET_KEY

These are obtained by signing up for YubiCloud at this URL: https://upgrade.yubico.com/getapikey/

After setup, I can even use the YubiKey 5 NFC and hold it to the back of my iPhone to provide a secure hardware second factor to BitWarden on the iPhone. It’s pretty sweet and I wish that 1Password supported it, but they have been reticent almost to the point of obstinate about not doing it for some reason.

Hi! I note you are looking for recipes for Kubernetes, well, I have Bitwarden for you :slight_smile:

It’s not a helm chart, but I have a basic Kubernetes YAML file that sets up a configmap for environment variables, and a deployment with all containers in a single pod so they can communicate with each other via localhost. I used a specific environment variable in several containers - ASPNETCORE_URLS, to have each web component listen on a different port to 5000. The ingress configuration is for HAProxy, but shouldn’t need much effort to convert to nginx/traefik.

The prerequisites are the same - run the bitwarden script to setup the directory structure, then set the path on the volumeMounts in the deployment. Replace the usernames, passwords, IDs and keys with your own from the global.env, uid.env and override files.

Hope this helps!

apiVersion: v1
kind: ConfigMap
metadata:
  name: bitwarden-config
  namespace: default
data:
  ACCEPT_EULA: "Y"
  MSSQL_PID: "Express"
  SA_PASSWORD: "<sa_password>"
  ASPNETCORE_ENVIRONMENT: "Production"
  globalSettings__selfHosted: "true"
  globalSettings__baseServiceUri__vault: "http://bitwarden.mydomain.com"
  globalSettings__baseServiceUri__api: "http://bitwarden.mydomain.com/api"
  globalSettings__baseServiceUri__identity: "http://bitwarden.mydomain.com/identity"
  globalSettings__baseServiceUri__admin: "http://bitwarden.mydomain.com/admin"
  globalSettings__baseServiceUri__notifications: "http://bitwarden.mydomain.com/notifications"
  globalSettings__baseServiceUri__internalNotifications: "http://localhost:5006"
  globalSettings__baseServiceUri__internalAdmin: "http://localhost:5004"
  globalSettings__baseServiceUri__internalIdentity: "http://localhost:5003"
  globalSettings__baseServiceUri__internalApi: "http://localhost:5002"
  globalSettings__baseServiceUri__internalVault: "http://localhost:5000"
  globalSettings__pushRelayBaseUri: "https://push.bitwarden.com"
  globalSettings__installation__identityUri: "https://identity.bitwarden.com"
  globalSettings__sqlServer__connectionString: "Data Source=tcp:localhost,1433;Initial Catalog=vault;Persist Security Info=False;User ID=sa;Password=<password>;MultipleActiveResultSets=False;Connect Timeout=30;Encrypt=True;TrustServerCertificate=True"
  globalSettings__identityServer__certificatePassword: "<cert pwd>"
  globalSettings__attachment__baseDirectory: "/etc/bitwarden/core/attachments"
  globalSettings__attachment__baseUrl: "http://bitwarden.mydomain.com/attachments"
  globalSettings__dataProtection__directory: "/etc/bitwarden/core/aspnet-dataprotection"
  globalSettings__logDirectory: "/etc/bitwarden/logs"
  globalSettings__licenseDirectory: "/etc/bitwarden/core/licenses"
  globalSettings__internalIdentityKey: "<idkey>"
  globalSettings__duo__aKey: "<duo_akey>"
  globalSettings__installation__id: "<install_id>"
  globalSettings__installation__key: "<install_key>"
  globalSettings__yubico__clientId: "<yubico_id>"
  globalSettings__yubico__key: "<yubico_key>"
  globalSettings__mail__replyToEmail: "no-reply@bitwarden.mydomain.com"
  globalSettings__mail__smtp__host: "smtp.mydomain.com"
  globalSettings__mail__smtp__port: "587"
  globalSettings__mail__smtp__ssl: "false"
  globalSettings__mail__smtp__username: "username"
  globalSettings__mail__smtp__password: "password"
  globalSettings__disableUserRegistration: "false"
  adminSettings__admins: 
  LOCAL_UID: "UID"
  LOCAL_GID: "GID"    
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    k8s.app: bitwarden
  name: bitwarden
  namespace: default
spec:
  selector:
    matchLabels:
      k8s.app: bitwarden
  template:
    metadata:
      labels:
        k8s.app: bitwarden
    spec:
      containers:
      - image: bitwarden/mssql:1.30.4
        name: bitwarden-mssql
        envFrom:
        - configMapRef:
            name: bitwarden-config
        ports:
        - containerPort: 1433
          protocol: TCP
        volumeMounts:
        - mountPath: /var/opt/mssql/data
          name: mssql-data
        - mountPath: /var/opt/mssql/log
          name: mssql-logs
        - mountPath: /etc/bitwarden/mssql/backups
          name: mssql-backup
      - image: bitwarden/web
        name: bitwarden-web
        envFrom:
        - configMapRef:
            name: bitwarden-config
        ports:
        - containerPort: 5000
          protocol: TCP
        volumeMounts:
        - mountPath: /etc/bitwarden/web
          name: web-data
      - image: bitwarden/attachments:1.30.4
        name: bitwarden-attachments
        envFrom:
        - configMapRef:
            name: bitwarden-config
        env:
        - name: ASPNETCORE_URLS
          value: http://+:5001
        ports:
        - containerPort: 5001
          protocol: TCP
        volumeMounts:
        - mountPath: /etc/bitwarden/core/attachments
          name: attachment-core
      - image: bitwarden/api:1.30.4
        name: bitwarden-api
        envFrom:
        - configMapRef:
            name: bitwarden-config
        env:
        - name: ASPNETCORE_URLS
          value: http://+:5002
        ports:
        - containerPort: 5002
          protocol: TCP
        volumeMounts:
        - mountPath: /etc/bitwarden/core
          name: bitwarden-core
        - mountPath: /etc/bitwarden/ca-certificates
          name: cacerts
        - mountPath: /etc/bitwarden/logs
          name: api-logs
      - image: bitwarden/identity:1.30.4
        name: bitwarden-identity
        envFrom:
        - configMapRef:
            name: bitwarden-config
        env:
        - name: ASPNETCORE_URLS
          value: http://+:5003
        ports:
        - containerPort: 5003
          protocol: TCP
        volumeMounts:
        - mountPath: /etc/bitwarden/identity
          name: id-core
        - mountPath: /etc/bitwarden/core
          name: bitwarden-core
        - mountPath: /etc/bitwarden/ca-certificates
          name: cacerts
        - mountPath: /etc/bitwarden/logs
          name: id-logs
      - image: bitwarden/admin:1.30.4
        name: bitwarden-admin
        envFrom:
        - configMapRef:
            name: bitwarden-config
        env:
        - name: ASPNETCORE_URLS
          value: http://+:5004
        ports:
        - containerPort: 5004
          protocol: TCP
        volumeMounts:
        - mountPath: /etc/bitwarden/core
          name: bitwarden-core
        - mountPath: /etc/bitwarden/ca-certificates
          name: cacerts
        - mountPath: /etc/bitwarden/logs
          name: admin-logs
      - image: bitwarden/icons:1.30.4
        name: bitwarden-icons
        envFrom:
        - configMapRef:
            name: bitwarden-config
        env:
        - name: ASPNETCORE_URLS
          value: http://+:5005
        ports:
        - containerPort: 5005
          protocol: TCP
        volumeMounts:
        - mountPath: /etc/bitwarden/ca-certificates
          name: cacerts
        - mountPath: /etc/bitwarden/logs
          name: icon-logs
      - image: bitwarden/notifications:1.30.4
        name: bitwarden-notifications
        envFrom:
        - configMapRef:
            name: bitwarden-config
        env:
        - name: ASPNETCORE_URLS
          value: http://+:5006
        ports:
        - containerPort: 5006
          protocol: TCP
        volumeMounts:
        - mountPath: /etc/bitwarden/ca-certificates
          name: cacerts
        - mountPath: /etc/bitwarden/logs
          name: notify-logs
      volumes:
      - hostPath:
          path: /path/to/bwdata/mssql/data
        name: mssql-data
      - hostPath:
          path: /path/to/bwdata/logs/mssql
        name: mssql-logs
      - hostPath:
          path: /path/to/bwdata/mssql/backups
        name: mssql-backup
      - hostPath:
          path: /path/to/bwdata/web
        name: web-data
      - hostPath:
          path: /path/to/bwdata/core/attachments
        name: attachment-core
      - hostPath:
          path: /path/to/bwdata/logs/api
        name: api-logs
      - hostPath:
          path: /path/to/bwdata/identity
        name: id-core
      - hostPath:
          path: /path/to/bwdata/core
        name: bitwarden-core
      - hostPath:
          path: /path/to/bwdata/ca-certificates
        name: cacerts
      - hostPath:
          path: /path/to/bwdata/logs/identity
        name: id-logs
      - hostPath:
          path: /path/to/bwdata/logs/admin
        name: admin-logs
      - hostPath:
          path: /path/to/bwdata/logs/icons
        name: icon-logs
      - hostPath:
          path: /path/to/bwdata/logs/notifications
        name: notify-logs        
---
apiVersion: v1
kind: Service
metadata:
  labels:
    k8s.app: bitwarden
  name: bitwarden
  namespace: default
spec:
  ports:
  - name: web
    port: 5000
    protocol: TCP
    targetPort: 5000
  - name: attachments
    port: 5001
    protocol: TCP
    targetPort: 5001
  - name: api
    port: 5002
    protocol: TCP
    targetPort: 5002
  - name: identity
    port: 5003
    protocol: TCP
    targetPort: 5003
  - name: admin
    port: 5004
    protocol: TCP
    targetPort: 5004
  - name: icons
    port: 5005
    protocol: TCP
    targetPort: 5005
  - name: notifications
    port: 5006
    protocol: TCP
    targetPort: 5006
  selector:
    k8s.app: bitwarden
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    ingress.kubernetes.io/config-backend: |
      http-response set-header Referrer-Policy same-origin
      http-response set-header X-Content-Type-Options nosniff
      http-response set-header X-XSS-Protection "1; mode=block"
      http-response set-header Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://haveibeenpwned.com https://www.gravatar.com; child-src 'self' https://*.duosecurity.com; frame-src 'self' https://*.duosecurity.com; connect-src 'self' wss://bitwarden.mydomain.com https://api.pwnedpasswords.com https://twofactorauth.org; object-src 'self' blob:;"
      http-response set-header X-Frame-Options SAMEORIGIN
      http-response set-header X-Robots-Tag "noindex, nofollow"
      http-response set-header Content-Type application/fido.trusted-apps+json if { var(txn.path) -m str /app-id.json }
    ingress.kubernetes.io/secure-backends: "false"
    ingress.kubernetes.io/ssl-passthrough: "false"
    kubernetes.io/ingress.class: haproxy
  name: bitwarden
  namespace: default
spec:
  rules:
  - host: bitwarden.mydomain.com
    http:
      paths:
      - path: /
        backend:
          serviceName: bitwarden
          servicePort: 5000
  tls:
  - hosts:
    - bitwarden.mydomain.com
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    ingress.kubernetes.io/config-backend: |
      http-response set-header Referrer-Policy same-origin
      http-response set-header X-Content-Type-Options nosniff
      http-response set-header X-XSS-Protection "1; mode=block"
      http-response set-header X-Frame-Options SAMEORIGIN
    ingress.kubernetes.io/secure-backends: "false"
    ingress.kubernetes.io/ssl-passthrough: "false"
    kubernetes.io/ingress.class: haproxy
  name: bitwarden-admin
  namespace: default
spec:
  rules:
  - host: bitwarden.mydomain.com
    http:
      paths:
      - path: /admin
        backend:
          serviceName: bitwarden
          servicePort: 5004
  tls:
  - hosts:
    - bitwarden.mydomain.com
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    ingress.kubernetes.io/config-backend: |
      http-response set-header Referrer-Policy same-origin
      http-response set-header X-Content-Type-Options nosniff
      http-response set-header X-XSS-Protection "1; mode=block"
    ingress.kubernetes.io/secure-backends: "false"
    ingress.kubernetes.io/ssl-passthrough: "false"
    ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: haproxy
  name: bitwarden-api-id-icons
  namespace: default
spec:
  rules:
  - host: bitwarden.mydomain.com
    http:
      paths:
      - path: /api/
        backend:
          serviceName: bitwarden
          servicePort: 5002
      - path: /identity/
        backend:
          serviceName: bitwarden
          servicePort: 5003
      - path: /icons/
        backend:
          serviceName: bitwarden
          servicePort: 5005
      - path: /notifications/
        backend:
          serviceName: bitwarden
          servicePort: 5006
      - path: /attachments/
        backend:
          serviceName: bitwarden
          servicePort: 5001
  tls:
  - hosts:
    - bitwarden.mydomain.com

Wow, that’s amazing! Thank you, I’ll work on adding it to the cookbook. Any objections if I “helmify” it?

No objections, happy to help more people host it on K8s. I’d be interested to see the ingress definitions translated to traefik too. The difficult part was trawling through Bitwarden’s GitHub repo to see if there were any command line arguments etc. to change the port number of the .NET core services, turns out their Dockerfiles specified that environment variable so I was able to change that :slight_smile:

Initially I was going to host each container in its own pod but that looked messy with more services etc., and it didn’t make sense as I couldn’t see one particular service requiring scaling over others.