Coder Social home page Coder Social logo

lerentis / bitwarden-crd-operator Goto Github PK

View Code? Open in Web Editor NEW
35.0 2.0 6.0 360 KB

Kubernetes Operator to create k8s secrets from bitwarden

License: MIT License

Dockerfile 5.10% Python 85.70% Smarty 6.84% Makefile 2.36%
bitwarden bitwarden-cli kubernetes-operator

bitwarden-crd-operator's Introduction

Bitwarden CRD Operator

Build Status Artifact Hub

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"

the helm template will use all environment variables from this secret, so make sure to prepare this secret with the key value pairs as described above.

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

BitwardenSecret

And you are set to create your first secret using this operator. For that you need to add a CRD Object like this to your cluster:

---
apiVersion: "lerentis.uploadfilter24.eu/v1beta7"
kind: BitwardenSecret
metadata:
  name: name-of-your-management-object
spec:
  content:
    - element:
        secretName: nameOfTheFieldInBitwarden # for example username or filename
        secretRef: nameOfTheKeyInTheSecretToBeCreated 
        secretScope: login # for custom entries on bitwarden use 'fields, for attachments use attachment' 
    - element:
        secretName: nameOfAnotherFieldInBitwarden # for example password or filename
        secretRef: nameOfAnotherKeyInTheSecretToBeCreated 
        secretScope: login # for custom entries on bitwarden use 'fields, for attachments use attachment' 
  id: "A Secret ID from bitwarden"
  name: "Name of the secret to be created"
  secretType: # Optional (Default: Opaque)
  namespace: "Namespace of the secret to be created"
  labels: # Optional
    key: value
  annotations: # Optional
    key: value

The ID can be extracted from the browser when you open a item the ID is in the URL. The resulting secret looks something like this:

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, or pull secrets, you can create another kind of object to let the operator create these as well for you:

---
apiVersion: "lerentis.uploadfilter24.eu/v1beta7"
kind: RegistryCredential
metadata:
  name: name-of-your-management-object
spec:
  usernameRef: nameOfTheFieldInBitwarden # for example username
  passwordRef: nameOfTheFieldInBitwarden # for example password
  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: # Optional
    key: value
  annotations: # Optional
    key: value

The resulting secret looks something like this:

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

One of the more freely defined types that can be used with this operator you can just pass a whole template. Also the lookup function bitwarden_lookup is available to reference parts of the secret:

---
apiVersion: "lerentis.uploadfilter24.eu/v1beta7"
kind: BitwardenTemplate
metadata:
  name: name-of-your-management-object
spec:
  filename: "Key of the secret to be created"
  name: "Name of the secret to be created"
  secretType: # Optional (Default: Opaque)
  namespace: "Namespace of the secret to be created"
  labels: # Optional
    key: value
  annotations: # Optional
    key: value
  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

This will result in something like the following object:

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):

  • item_id: The item ID of the secret in Bitwarden
  • scope: one of login, fields or attachment
  • field:
    • when scope is login: either username or password
    • when scope is fields: the name of a custom field
    • when scope is attachment: the filename of a file attached to the item

Please note that the rendering engine for this template is jinja2, with an addition of a custom bitwarden_lookup function, so there are more possibilities to inject here.

Configurations parameters

The operator uses the bitwarden cli in the background and does not communicate to the api directly. The cli mirrors the credential store locally but doesn't sync it on every get request. Instead it will sync each secret every 15 minutes (900 seconds). You can adjust the interval by setting BW_SYNC_INTERVAL in the values. If your secrets update very very frequently, you can force the operator to do a sync before each get by setting BW_FORCE_SYNC="true". You might run into rate limits if you do this too frequent.

Additionally the bitwarden cli session may expire at some time. In order to create a new session, the login command is triggered from time to time. In what interval exactly can be configured with the env BW_RELOGIN_INTERVAL which defaults to 3600s.

bitwarden-crd-operator's People

Contributors

chrthal avatar dependabot[bot] avatar kaotika avatar lerentis avatar nicoangelo avatar titilambert avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

bitwarden-crd-operator's Issues

Push to more Registries

With the latest move by the docker hub more and more people are migrating away from it. the bitwarden crd operator should make it easy to move to another registry as well.
Possible candidates:

  • ghcr.io
  • quay.io
  • registry.gitlab.com/lerentis/bitwarden-crd-operator

TypeError: 'NoneType' object is not subscriptable

Hi,

finally got the operaotpr working, but now i have the problem that i cant get secrets working:
I get the folloiwng error message:

Handler 'create_managed_secret' failed with an exception. Will retry. Traceback (most recent call last): File "/usr/lib/python3.10/site-packages/kopf/_core/actions/execution.py", line 276, in execute_handler_once result = await invoke_handler( File "/usr/lib/python3.10/site-packages/kopf/_core/actions/execution.py", line 371, in invoke_handler result = await invocation.invoke( File "/usr/lib/python3.10/site-packages/kopf/_core/actions/invocation.py", line 139, in invoke await asyncio.shie....10/concurrent/futures/thread.py", line 58, in run result = self.fn(*self.args, **self.kwargs) File "/home/bw-operator/kv.py", line 59, in create_managed_secret secret = create_kv(secret, secret_json_object, content_def) File "/home/bw-operator/kv.py", line 22, in create_kv value = parse_login_scope(secret_json, _secret_key) File "/home/bw-operator/utils/utils.py", line 45, in parse_login_scope return secret_json["data"]["login"][key] TypeError: 'NoneType' object is not subscriptable

My bitwarden.yaml
image

The secret from bw-cli:
image

The complete error:
image

Any idea or hint why its not wortking?

Have a nice day,

Michel

Docker image giving segmentation fault for simple bitwarden command

This happens from 0.10.X

With 0.9.X is all good.

โ”— # docker run --rm -ti --entrypoint sh ghcr.io/lerentis/bitwarden-crd-operator:0.10.2
Unable to find image 'ghcr.io/lerentis/bitwarden-crd-operator:0.10.2' locally
0.10.2: Pulling from lerentis/bitwarden-crd-operator
661ff4d9561e: Already exists
383d1dffd5bc: Already exists
ef0e8474cde5: Already exists
76fa5ba72f96: Already exists
Digest: sha256:4c5a3bc9d70f92160425af8099459ba72206883c4193a2a1d9e487ba0f888b02
Status: Downloaded newer image for ghcr.io/lerentis/bitwarden-crd-operator:0.10.2
/ $ bw
Segmentation fault (core dumped)
/ $

With the current Dockerfile on the main branch, the last BW version that works for me it's ARG BW_VERSION=2023.1.0

JSONDecodeError

Hi, I installed the operator like you wrote in the docs, but im getting the following error:

{"message": "Activity 'bitwarden_signin' failed with an exception. Will retry.", "exc_info": "Traceback (most recent call last):\n File \"/usr/lib/python3.10/site-packages/kopf/_core/actions/execution.py\", line 279, in execute_handler_once\n result = await invoke_handler(\n File \"/usr/lib/python3.10/site-packages/kopf/_core/actions/execution.py\", line 374, in invoke_handler\n result = await invocation.invoke(\n File \"/usr/lib/python3.10/site-packages/kopf/_core/actions/invocation.py\", line 139, in invoke\n await asyncio.shield(future) # slightly expensive: creates tasks\n File \"/usr/lib/python3.10/concurrent/futures/thread.py\", line 58, in run\n result = self.fn(*self.args, **self.kwargs)\n File \"/home/bw-operator/bitwardenCrdOperator.py\", line 19, in bitwarden_signin\n command_wrapper(logger, \"login --apikey\")\n File \"/home/bw-operator/utils/utils.py\", line 35, in command_wrapper\n resp = json.loads(out.decode(encoding='UTF-8'))\n File \"/usr/lib/python3.10/json/__init__.py\", line 346, in loads\n return _default_decoder.decode(s)\n File \"/usr/lib/python3.10/json/decoder.py\", line 337, in decode\n obj, end = self.raw_decode(s, idx=_w(s, 0).end())\n File \"/usr/lib/python3.10/json/decoder.py\", line 355, in raw_decode\n raise JSONDecodeError(\"Expecting value\", s, err.value) from None\njson.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)", "timestamp": "2023-05-09T00:30:55.035056+00:00", "severity": "error"}

Any idea how to fix it? Do you need more informations?

Custom fields returning 'has no value'

Hey there,
with your newest version 0.6.0 there might be a bug.
Trying to read a custom field from my bitwarden. SecretScope is set to fields. Tested it numerous times. The only scope which is working is the login scope.
Even tested it manually with the Bitwarden CLI to check, if there is anything there. There is.
Downgraded to 0.5.4 and have no problems, even with custom fields.
Might have to do with your recent changes to utils.py in parse_fields_scope. :)
Let me know if you need any more information.

"You are not logged in"

Hi,

have the problem that from time to time the operator pod gets logged out. (not sure about the time now)
I got the follwing error:
You are not logged in and the password lookup fails.

The vaultwarden server isn't restarted, same as the operator. If I restart the operator he is back logged in and working.
Any idea to fix the problem?

Regards,

Michel

PR Pipeline

There should be a CI pipeline that runs chart testing as a minimum

Custom Annotations

Hi,
is it possible to extend the template of the secret to add additional custom annotations?

Namespaced operator

Hi, I'am using the operator extensivly on semi-prod clusters and for development.
Last week I stumbled over a situation where I wished me a namespace scoped deployment of the operator rather then the current cluster wide approach.
We run multiple instances of a stack seperated by namespace. The downside of the current implementation is that the same operator credentials are needed, or the BW User needs to have access to all credentials.
Having namespace scoped environments enables a user to manage its own namespace.

I tested it locally by installing the operator to a single namespace, using roles instead of clusterroles and by editing the start command in the dockerfile. That simply did the job.

In reality one namespace per instance/customer might be too much (and seems to complicate the installation). So listening on multiple namespaces, but not all, might be the better approach.

I tested it locally and its relativly simple to do.

  • prefix the serviceaccount, clusterrole and clusterrolebindings (maybe with the first watched namespace)
  • use a entrypoint script instead of hard coded start params to use environment variables to let the operator just watch on specific namespaces

What do you think about it?

Upgrading from 0.7.1 to 0.7.3 the operator is no longer able to start

Hi,

I wanted to upgrade to the latest version of the operator, however the deployment gets stuck in a restart loop because of a Python error (see below). Unfortunately, I don't have time right now to debug further, so I downgraded back to 0.7.1 which works fine

Output of the signin action

{
  "message": "Activity 'bitwarden_signin' failed with an exception. Will retry.",
  "exc_info": "Traceback (most recent call last):\n  File \"/usr/lib /python3.11/site-packages/kopf/_core/actions/execution.py\", line 276, in execute_handler_once\n    result = await invoke_handler(\n             ^^ ^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.11/site-packages/kopf/_core/actions/execution.py\", line 371, in invoke_handler\n    result = await  invocation.invoke(\n             ^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.11/site-packages/kopf/_core/actions/invocation.py\", line 139,  in invoke\n    await asyncio.shield(future)  # slightly expensive: creates tasks\n    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.11/c oncurrent/futures/thread.py\", line 58, in run\n    result = self.fn(*self.args, **self.kwargs)\n             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n   File \"/home/bw-operator/bitwardenCrdOperator.py\", line 19, in bitwarden_signin\n    command_wrapper(logger, \"login --apikey\")\n  File \"/home/ bw-operator/utils/utils.py\", line 35, in command_wrapper\n    resp = json.loads(out.decode(encoding='UTF-8'))\n           ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.11/json/__init__.py\", line 346, in loads\n    return _default_decoder.decode(s)\n           ^^^^^^^^^^ ^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.11/json/decoder.py\", line 337, in decode\n    obj, end = self.raw_decode(s, idx=_w(s, 0).end())\n                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.11/json/decoder.py\", line 355, in raw_decode\n    raise JSONDecodeErro r(\"Expecting value\", s, err.value) from None\njson.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)",
  "timestamp": "2023-06-15T 13:59:18.613253+00:00",
  "severity": "error"
}

Labels for Secrets

Hi,

I was wondering if there is a way to add labels to the secret? Some applications need them f.e. argocd.

Example:

spec:
  content:
    - element:
        secretName: 'username' # for example password
        secretRef: 'username'
        secretScope: 'login' # for custom entries on bitwarden use 'fields'
  name: 'name'
  namespace: 'namespace'
  labels:
    label: 'xyz'

Is it possible or is there another way?

Regards

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.