From 559c08c1876d6da09dddb70ad771ac153e5113b7 Mon Sep 17 00:00:00 2001 From: Tobias Trabelsi Date: Sun, 6 Oct 2024 22:32:30 +0200 Subject: [PATCH 1/2] (feat): refactor bitwardenTemplate to handle more than one file (chore): update dependencies (chore): drop old CRD versions --- Dockerfile | 6 +- README.md | 42 +++-- charts/bitwarden-crd-operator/Chart.yaml | 59 ++++--- .../crds/bitwarden-secrets.yaml | 81 +-------- .../crds/bitwarden-templates.yaml | 124 +++++-------- .../crds/registry-credentials.yaml | 67 +------ example.yaml | 4 +- example_dockerlogin.yaml | 2 +- example_template.yaml | 38 ++-- requirements.txt | 2 +- src/template.py | 164 +++++++++++++++++- 11 files changed, 297 insertions(+), 292 deletions(-) diff --git a/Dockerfile b/Dockerfile index d9c1569..fab365e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,13 @@ -FROM alpine:3.20.2 +FROM alpine:3.20.3 LABEL org.opencontainers.image.source=https://github.com/Lerentis/bitwarden-crd-operator LABEL org.opencontainers.image.description="Kubernetes Operator to create k8s secrets from bitwarden" LABEL org.opencontainers.image.licenses=MIT -ARG PYTHON_VERSION=3.12.3-r1 +ARG PYTHON_VERSION=3.12.6-r0 ARG PIP_VERSION=24.0-r2 ARG GCOMPAT_VERSION=1.1.0-r4 -ARG LIBCRYPTO_VERSION=3.3.1-r3 +ARG LIBCRYPTO_VERSION=3.3.2-r0 ARG BW_VERSION=2024.7.2 ARG NODE_VERSION=20.15.1-r0 diff --git a/README.md b/README.md index cecfdfd..d07388c 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ And you are set to create your first secret using this operator. For that you ne ```yaml --- -apiVersion: "lerentis.uploadfilter24.eu/v1beta7" +apiVersion: "lerentis.uploadfilter24.eu/v1beta8" kind: BitwardenSecret metadata: name: name-of-your-management-object @@ -106,7 +106,7 @@ For managing registry credentials, or pull secrets, you can create another kind ```yaml --- -apiVersion: "lerentis.uploadfilter24.eu/v1beta7" +apiVersion: "lerentis.uploadfilter24.eu/v1beta8" kind: RegistryCredential metadata: name: name-of-your-management-object @@ -147,12 +147,11 @@ One of the more freely defined types that can be used with this operator you can ```yaml --- -apiVersion: "lerentis.uploadfilter24.eu/v1beta7" +apiVersion: "lerentis.uploadfilter24.eu/v1beta8" 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" @@ -160,16 +159,31 @@ spec: 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 + 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 + - element: + filename: config2.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: false ``` This will result in something like the following object: diff --git a/charts/bitwarden-crd-operator/Chart.yaml b/charts/bitwarden-crd-operator/Chart.yaml index 5038e01..6897a8c 100644 --- a/charts/bitwarden-crd-operator/Chart.yaml +++ b/charts/bitwarden-crd-operator/Chart.yaml @@ -4,9 +4,9 @@ description: Deploy the Bitwarden CRD Operator type: application -version: "v0.14.0" +version: "v0.15.0" -appVersion: "0.13.0" +appVersion: "0.14.0" keywords: - operator @@ -20,7 +20,7 @@ home: https://lerentis.github.io/bitwarden-crd-operator/ sources: - https://github.com/Lerentis/bitwarden-crd-operator -kubeVersion: ">= 1.23.0-0" +kubeVersion: ">= 1.28.0-0" maintainers: - name: lerentis @@ -32,22 +32,22 @@ annotations: url: https://github.com/Lerentis/bitwarden-crd-operator artifacthub.io/crds: | - kind: BitwardenSecret - version: v1beta7 + version: v1beta8 name: bitwarden-secret displayName: Bitwarden Secret description: Management Object to create secrets from bitwarden - kind: RegistryCredential - version: v1beta7 + version: v1beta8 name: registry-credential displayName: Regestry Credentials description: Management Object to create regestry secrets from bitwarden - kind: BitwardenTemplate - version: v1beta7 + version: v1beta8 name: bitwarden-template displayName: Bitwarden Template description: Management Object to create secrets from a jinja template with a bitwarden lookup artifacthub.io/crdsExamples: | - - apiVersion: lerentis.uploadfilter24.eu/v1beta7 + - apiVersion: lerentis.uploadfilter24.eu/v1beta8 kind: BitwardenSecret metadata: name: test @@ -67,7 +67,7 @@ annotations: key: value annotations: key: value - - apiVersion: lerentis.uploadfilter24.eu/v1beta7 + - apiVersion: lerentis.uploadfilter24.eu/v1beta8 kind: RegistryCredential metadata: name: test @@ -82,12 +82,11 @@ annotations: key: value annotations: key: value - - apiVersion: "lerentis.uploadfilter24.eu/v1beta7" + - apiVersion: "lerentis.uploadfilter24.eu/v1beta8" kind: BitwardenTemplate metadata: name: test spec: - filename: "config.yaml" name: "test-regcred" secretType: Obaque #Optional namespace: "default" @@ -95,32 +94,32 @@ annotations: key: value annotations: key: value - template: | - --- - api: - enabled: True - key: {{ bitwarden_lookup("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "fields", "key") }} - allowCrossOrigin: false - apps: - "some.app.identifier:some_version": - pubkey: {{ bitwarden_lookup("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "attachment", "public_key") }} - enabled: true + content: + - element: + filename: "config.yaml" + template: | + --- + api: + enabled: True + key: {{ bitwarden_lookup("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "fields", "key") }} + allowCrossOrigin: false + apps: + "some.app.identifier:some_version": + pubkey: {{ bitwarden_lookup("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "attachment", "public_key") }} + enabled: true + artifacthub.io/license: MIT artifacthub.io/operator: "true" artifacthub.io/containsSecurityUpdates: "false" artifacthub.io/changes: | - kind: changed - description: "Update Alpine to 3.20.2" + description: "BitwardenTemplate can now handle multiple files" - kind: changed - description: "Update Python to 3.12.3-r1" + description: "Removed long deprecated versions" + - kind changed + description: "Update kubernetes from v29.0.0 to v30.1.0" - kind: changed - description: "Update Pip to 24.0-r2" - - kind: changed - description: "Update NodeJS to 20.15.1-r0" - - kind: changed - description: "Update libcrypto3 to 3.3.1-r3" - - kind: changed - description: "Update BitwardenCLI to 2024.7.2" + description: "Update alpine from 3.20.2 to 3.20.3" artifacthub.io/images: | - name: bitwarden-crd-operator - image: ghcr.io/lerentis/bitwarden-crd-operator:0.13.0 + image: ghcr.io/lerentis/bitwarden-crd-operator:0.14.0 diff --git a/charts/bitwarden-crd-operator/crds/bitwarden-secrets.yaml b/charts/bitwarden-crd-operator/crds/bitwarden-secrets.yaml index 00b66fe..c985394 100644 --- a/charts/bitwarden-crd-operator/crds/bitwarden-secrets.yaml +++ b/charts/bitwarden-crd-operator/crds/bitwarden-secrets.yaml @@ -13,7 +13,7 @@ spec: shortNames: - bws versions: - - name: v1beta4 + - name: v1beta7 served: true storage: false deprecated: true @@ -46,82 +46,7 @@ spec: type: string name: type: string - required: - - id - - namespace - - name - - name: v1beta5 - served: true - storage: false - deprecated: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - content: - type: array - items: - type: object - properties: - element: - type: object - properties: - secretName: - type: string - secretRef: - type: string - secretScope: - type: string - required: - - secretName - id: - type: string - namespace: - type: string - name: - type: string - labels: - type: object - x-kubernetes-preserve-unknown-fields: true - required: - - id - - namespace - - name - - name: v1beta6 - served: true - storage: false - deprecated: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - content: - type: array - items: - type: object - properties: - element: - type: object - properties: - secretName: - type: string - secretRef: - type: string - secretScope: - type: string - required: - - secretName - id: - type: string - namespace: - type: string - name: + secretType: type: string labels: type: object @@ -133,7 +58,7 @@ spec: - id - namespace - name - - name: v1beta7 + - name: v1beta8 served: true storage: true schema: diff --git a/charts/bitwarden-crd-operator/crds/bitwarden-templates.yaml b/charts/bitwarden-crd-operator/crds/bitwarden-templates.yaml index fd5ef4a..ac60a07 100644 --- a/charts/bitwarden-crd-operator/crds/bitwarden-templates.yaml +++ b/charts/bitwarden-crd-operator/crds/bitwarden-templates.yaml @@ -13,90 +13,10 @@ spec: shortNames: - bwt versions: - - name: v1beta4 - served: true - storage: false - deprecated: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - filename: - type: string - template: - type: string - namespace: - type: string - name: - type: string - required: - - filename - - template - - namespace - - name - - name: v1beta5 - served: true - storage: false - deprecated: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - filename: - type: string - template: - type: string - namespace: - type: string - name: - type: string - labels: - type: object - x-kubernetes-preserve-unknown-fields: true - required: - - filename - - template - - namespace - - name - - name: v1beta6 - served: true - storage: false - deprecated: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - filename: - type: string - template: - type: string - namespace: - type: string - name: - type: string - labels: - type: object - x-kubernetes-preserve-unknown-fields: true - annotations: - type: object - x-kubernetes-preserve-unknown-fields: true - required: - - filename - - template - - namespace - - name - name: v1beta7 served: true - storage: true + storage: false + deprecated: true schema: openAPIV3Schema: type: object @@ -125,3 +45,43 @@ spec: - template - namespace - name + - name: v1beta8 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + namespace: + type: string + name: + type: string + secretType: + type: string + content: + type: array + items: + type: object + properties: + element: + type: object + properties: + filename: + type: string + template: + type: string + required: + - filename + - template + labels: + type: object + x-kubernetes-preserve-unknown-fields: true + annotations: + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - namespace + - name \ No newline at end of file diff --git a/charts/bitwarden-crd-operator/crds/registry-credentials.yaml b/charts/bitwarden-crd-operator/crds/registry-credentials.yaml index 5fe4b75..e631abd 100644 --- a/charts/bitwarden-crd-operator/crds/registry-credentials.yaml +++ b/charts/bitwarden-crd-operator/crds/registry-credentials.yaml @@ -13,70 +13,7 @@ spec: shortNames: - rgc versions: - - name: v1beta4 - served: true - storage: false - deprecated: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - usernameRef: - type: string - passwordRef: - type: string - registry: - type: string - id: - type: string - namespace: - type: string - name: - type: string - required: - - id - - namespace - - name - - usernameRef - - passwordRef - - registry - - name: v1beta5 - served: true - storage: false - deprecated: true - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - usernameRef: - type: string - passwordRef: - type: string - registry: - type: string - id: - type: string - namespace: - type: string - name: - type: string - labels: - type: object - x-kubernetes-preserve-unknown-fields: true - required: - - id - - namespace - - name - - usernameRef - - passwordRef - - registry - - name: v1beta6 + - name: v1beta7 served: true storage: false deprecated: true @@ -112,7 +49,7 @@ spec: - usernameRef - passwordRef - registry - - name: v1beta7 + - name: v1beta8 served: true storage: true schema: diff --git a/example.yaml b/example.yaml index d948f10..7916b38 100644 --- a/example.yaml +++ b/example.yaml @@ -1,5 +1,5 @@ --- -apiVersion: "lerentis.uploadfilter24.eu/v1beta7" +apiVersion: "lerentis.uploadfilter24.eu/v1beta8" kind: BitwardenSecret metadata: name: test @@ -24,7 +24,7 @@ spec: annotations: custom.annotation: is-used --- -apiVersion: "lerentis.uploadfilter24.eu/v1beta7" +apiVersion: "lerentis.uploadfilter24.eu/v1beta8" kind: BitwardenSecret metadata: name: test-scope diff --git a/example_dockerlogin.yaml b/example_dockerlogin.yaml index 29f626b..91c7731 100644 --- a/example_dockerlogin.yaml +++ b/example_dockerlogin.yaml @@ -1,5 +1,5 @@ --- -apiVersion: "lerentis.uploadfilter24.eu/v1beta7" +apiVersion: "lerentis.uploadfilter24.eu/v1beta8" kind: RegistryCredential metadata: name: test diff --git a/example_template.yaml b/example_template.yaml index d47eee9..71e53f7 100644 --- a/example_template.yaml +++ b/example_template.yaml @@ -1,10 +1,9 @@ --- -apiVersion: "lerentis.uploadfilter24.eu/v1beta7" +apiVersion: "lerentis.uploadfilter24.eu/v1beta8" kind: BitwardenTemplate metadata: name: test spec: - filename: "config.yaml" name: "test-template" namespace: "default" labels: @@ -12,13 +11,28 @@ spec: app: example-app annotations: custom.annotation: is-used - template: | - --- - api: - enabled: True - key: {{ bitwarden_lookup("466fc4b0-ffca-4444-8d88-b59d4de3d928", "fields", "key") }} - allowCrossOrigin: false - apps: - "some.app.identifier:some_version": - pubkey: {{ bitwarden_lookup("466fc4b0-ffca-4444-8d88-b59d4de3d928", "fields", "public_key") }} - enabled: true \ No newline at end of file + content: + - element: + filename: config.yaml + template: | + --- + api: + enabled: True + key: {{ bitwarden_lookup("466fc4b0-ffca-4444-8d88-b59d4de3d928", "fields", "key") }} + allowCrossOrigin: false + apps: + "some.app.identifier:some_version": + pubkey: {{ bitwarden_lookup("466fc4b0-ffca-4444-8d88-b59d4de3d928", "fields", "public_key") }} + enabled: true + - element: + filename: config2.yaml + template: | + --- + api: + enabled: True + key: {{ bitwarden_lookup("466fc4b0-ffca-4444-8d88-b59d4de3d928", "fields", "key") }} + allowCrossOrigin: false + apps: + "some.app.identifier:some_version": + pubkey: {{ bitwarden_lookup("466fc4b0-ffca-4444-8d88-b59d4de3d928", "fields", "public_key") }} + enabled: false diff --git a/requirements.txt b/requirements.txt index cd36e7b..2e3baad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ kopf==1.37.2 -kubernetes==29.0.0 +kubernetes==30.1.0 Jinja2==3.1.4 schedule==1.2.2 \ No newline at end of file diff --git a/src/template.py b/src/template.py index f6ba633..31cf17b 100644 --- a/src/template.py +++ b/src/template.py @@ -24,9 +24,72 @@ def create_template_secret(logger, secret, filename, template): "utf-8") return secret +def create_template_obj(logger, secret, content_def): + secret.data = {} + for eleml in content_def: + for k, elem in eleml.items(): + for key, value in elem.items(): + if key == "filename": + _file_name = value + if key == "template": + _template = value + secret.data[_file_name] = str( + base64.b64encode( + render_template(logger, _template).encode("utf-8")), + "utf-8") + return secret + @kopf.on.create('bitwarden-template.lerentis.uploadfilter24.eu') def create_managed_secret(spec, name, namespace, logger, body, **kwargs): + template = spec.get('template') + if template is not None: + create_beta7_secret(spec, name, namespace, logger, body, **kwargs) + secret_name = spec.get('name') + secret_namespace = spec.get('namespace') + custom_secret_type = spec.get('secretType') + labels = spec.get('labels') + custom_annotations = spec.get('annotations') + content_def = spec.get('content') + + unlock_bw(logger) + + api = kubernetes.client.CoreV1Api() + + annotations = { + "managed": "bitwarden-template.lerentis.uploadfilter24.eu", + "managedObject": f"{namespace}/{name}" + } + + if custom_annotations: + annotations.update(custom_annotations) + + if not custom_secret_type: + custom_secret_type = 'Opaque' + + if not labels: + labels = {} + + secret = kubernetes.client.V1Secret() + secret.metadata = kubernetes.client.V1ObjectMeta( + name=secret_name, annotations=annotations, labels=labels) + secret.type = custom_secret_type + secret = create_template_obj(logger, secret, content_def) + + # Garbage collection will delete the generated secret if the owner + # Is not in the same namespace as the generated secret + if secret_namespace == namespace: + kopf.append_owner_reference(secret) + + api.create_namespaced_secret( + namespace="{}".format(secret_namespace), + body=secret + ) + + logger.info(f"Secret {secret_namespace}/{secret_name} has been created") + + +def create_beta7_secret(spec, name, namespace, logger, body, **kwargs): template = spec.get('template') filename = spec.get('filename') @@ -71,10 +134,7 @@ def create_managed_secret(spec, name, namespace, logger, body, **kwargs): logger.info(f"Secret {secret_namespace}/{secret_name} has been created") - -@kopf.on.update('bitwarden-template.lerentis.uploadfilter24.eu') -@kopf.timer('bitwarden-template.lerentis.uploadfilter24.eu', interval=bw_sync_interval) -def update_managed_secret( +def update_beta7_secret( spec, status, name, @@ -165,6 +225,102 @@ def update_managed_secret( ) + +@kopf.on.update('bitwarden-template.lerentis.uploadfilter24.eu') +@kopf.timer('bitwarden-template.lerentis.uploadfilter24.eu', interval=bw_sync_interval) +def update_managed_secret( + spec, + status, + name, + namespace, + logger, + body, + **kwargs): + + template = spec.get('template') + if template is not None: + update_beta7_secret(spec, status, name, namespace, logger, body, **kwargs) + secret_name = spec.get('name') + secret_namespace = spec.get('namespace') + labels = spec.get('labels') + custom_annotations = spec.get('annotations') + custom_secret_type = spec.get('secretType') + content_def = spec.get('content') + + if not custom_secret_type: + custom_secret_type = 'Opaque' + + old_config = None + old_secret_name = None + old_secret_namespace = None + old_secret_type = None + if 'kopf.zalando.org/last-handled-configuration' in body.metadata.annotations: + old_config = json.loads( + body.metadata.annotations['kopf.zalando.org/last-handled-configuration']) + old_secret_name = old_config['spec'].get('name') + old_secret_namespace = old_config['spec'].get('namespace') + old_secret_type = old_config['spec'].get('secretType') + secret_name = spec.get('name') + secret_namespace = spec.get('namespace') + + if not old_secret_type: + old_secret_type = 'Opaque' + + if old_config is not None and ( + old_secret_name != secret_name or old_secret_namespace != secret_namespace or old_secret_type != custom_secret_type): + # If the name of the secret or the namespace of the secret is different + # We have to delete the secret an recreate it + logger.info("Secret name or namespace changed, let's recreate it") + delete_managed_secret( + old_config['spec'], + name, + namespace, + logger, + **kwargs) + create_managed_secret(spec, name, namespace, logger, body, **kwargs) + return + + unlock_bw(logger) + + api = kubernetes.client.CoreV1Api() + + annotations = { + "managed": "bitwarden-template.lerentis.uploadfilter24.eu", + "managedObject": f"{namespace}/{name}" + } + + if custom_annotations: + annotations.update(custom_annotations) + + if not labels: + labels = {} + + secret = kubernetes.client.V1Secret() + secret.metadata = kubernetes.client.V1ObjectMeta( + name=secret_name, annotations=annotations, labels=labels) + secret.type = custom_secret_type + secret = create_template_obj(logger, secret, content_def) + + # Garbage collection will delete the generated secret if the owner + # Is not in the same namespace as the generated secret + if secret_namespace == namespace: + kopf.append_owner_reference(secret) + + try: + api.replace_namespaced_secret( + name=secret_name, + body=secret, + namespace="{}".format(secret_namespace)) + logger.info( + f"Secret {secret_namespace}/{secret_name} has been updated") + except BaseException as e: + logger.warn( + f"Could not update secret {secret_namespace}/{secret_name}!") + logger.warn( + f"Exception: {e}" + ) + + @kopf.on.delete('bitwarden-template.lerentis.uploadfilter24.eu') def delete_managed_secret(spec, name, namespace, logger, **kwargs): secret_name = spec.get('name') From 90a3e9f73da59dacc0b881359b77f8f5c4df3c18 Mon Sep 17 00:00:00 2001 From: Tobias Trabelsi Date: Sun, 6 Oct 2024 22:34:22 +0200 Subject: [PATCH 2/2] (fix): fixed typo in changelog --- charts/bitwarden-crd-operator/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/bitwarden-crd-operator/Chart.yaml b/charts/bitwarden-crd-operator/Chart.yaml index 6897a8c..9cc541a 100644 --- a/charts/bitwarden-crd-operator/Chart.yaml +++ b/charts/bitwarden-crd-operator/Chart.yaml @@ -116,7 +116,7 @@ annotations: description: "BitwardenTemplate can now handle multiple files" - kind: changed description: "Removed long deprecated versions" - - kind changed + - kind: changed description: "Update kubernetes from v29.0.0 to v30.1.0" - kind: changed description: "Update alpine from 3.20.2 to 3.20.3"