diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 26a076f..dc776ed 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,7 +36,7 @@ jobs: CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - name: Get app version from chart - uses: mikefarah/yq@v4.43.1 + uses: mikefarah/yq@v4.44.1 id: app_version with: cmd: yq '.appVersion' charts/bitwarden-crd-operator/Chart.yaml diff --git a/Dockerfile b/Dockerfile index 0c59fa9..ac6936c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,15 @@ -FROM alpine:3.19.0 +FROM alpine:3.19.1 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.11.8-r0 +ARG PYTHON_VERSION=3.11.9-r0 ARG PIP_VERSION=23.3.1-r0 ARG GCOMPAT_VERSION=1.1.0-r4 -ARG LIBCRYPTO_VERSION=3.1.4-r2 +ARG LIBCRYPTO_VERSION=3.1.4-r5 ARG BW_VERSION=2023.7.0 -ARG NODE_VERSION=20.11.1-r0 +ARG NODE_VERSION=20.12.1-r0 COPY requirements.txt /requirements.txt diff --git a/charts/bitwarden-crd-operator/Chart.yaml b/charts/bitwarden-crd-operator/Chart.yaml index ecdd652..7f1e104 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.11.3" +version: "v0.12.0" -appVersion: "0.10.3" +appVersion: "0.11.0" keywords: - operator @@ -32,22 +32,22 @@ annotations: url: https://github.com/Lerentis/bitwarden-crd-operator artifacthub.io/crds: | - kind: BitwardenSecret - version: v1beta5 + version: v1beta6 name: bitwarden-secret displayName: Bitwarden Secret description: Management Object to create secrets from bitwarden - kind: RegistryCredential - version: v1beta5 + version: v1beta6 name: registry-credential displayName: Regestry Credentials description: Management Object to create regestry secrets from bitwarden - kind: BitwardenTemplate - version: v1beta5 + version: v1beta6 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/v1beta5 + - apiVersion: lerentis.uploadfilter24.eu/v1beta6 kind: BitwardenSecret metadata: name: test @@ -64,7 +64,9 @@ annotations: namespace: "default" labels: key: value - - apiVersion: lerentis.uploadfilter24.eu/v1beta5 + annotations: + key: value + - apiVersion: lerentis.uploadfilter24.eu/v1beta6 kind: RegistryCredential metadata: name: test @@ -77,7 +79,9 @@ annotations: namespace: "default" labels: key: value - - apiVersion: "lerentis.uploadfilter24.eu/v1beta5" + annotations: + key: value + - apiVersion: "lerentis.uploadfilter24.eu/v1beta6" kind: BitwardenTemplate metadata: name: test @@ -87,6 +91,8 @@ annotations: namespace: "default" labels: key: value + annotations: + key: value template: | --- api: @@ -102,11 +108,21 @@ annotations: artifacthub.io/containsSecurityUpdates: "false" artifacthub.io/changes: | - kind: changed - description: "Update python to 3.11.8-r0" + description: "Update python to 3.11.9-r0" - kind: changed - description: "Update Node to 20.11.1-r0" + description: "Update Node to 20.12.1-r0" - kind: changed - description: "Unified bw cli installation methode" + description: "Update libcrypto3 to 3.1.4-r5" + - kind: changed + description: "Update alpine to 3.19.1" + - kind: changed + description: "Update kopf to 1.37.2" + - kind: changed + description: "Update jinja to 3.1.4" + - kind: added + description: "Allow custom annotations to generated secrets" + - kind: added + description: "Set ownership of generated secrets if CRD is in the same namespace" artifacthub.io/images: | - name: bitwarden-crd-operator - image: ghcr.io/lerentis/bitwarden-crd-operator:0.10.3 + image: ghcr.io/lerentis/bitwarden-crd-operator:0.11.0 diff --git a/charts/bitwarden-crd-operator/crds/bitwarden-secrets.yaml b/charts/bitwarden-crd-operator/crds/bitwarden-secrets.yaml index 52eb23d..6a1221f 100644 --- a/charts/bitwarden-crd-operator/crds/bitwarden-secrets.yaml +++ b/charts/bitwarden-crd-operator/crds/bitwarden-secrets.yaml @@ -52,7 +52,8 @@ spec: - name - name: v1beta5 served: true - storage: true + storage: false + deprecated: true schema: openAPIV3Schema: type: object @@ -89,3 +90,45 @@ spec: - id - namespace - name + - name: v1beta6 + served: true + storage: 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 + annotations: + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - id + - namespace + - name \ No newline at end of file diff --git a/charts/bitwarden-crd-operator/crds/bitwarden-templates.yaml b/charts/bitwarden-crd-operator/crds/bitwarden-templates.yaml index c85bcc5..8cc47f5 100644 --- a/charts/bitwarden-crd-operator/crds/bitwarden-templates.yaml +++ b/charts/bitwarden-crd-operator/crds/bitwarden-templates.yaml @@ -39,7 +39,8 @@ spec: - name - name: v1beta5 served: true - storage: true + storage: false + deprecated: true schema: openAPIV3Schema: type: object @@ -63,3 +64,32 @@ spec: - template - namespace - name + - name: v1beta6 + served: true + storage: 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 diff --git a/charts/bitwarden-crd-operator/crds/registry-credentials.yaml b/charts/bitwarden-crd-operator/crds/registry-credentials.yaml index 80cf6dc..233ddb6 100644 --- a/charts/bitwarden-crd-operator/crds/registry-credentials.yaml +++ b/charts/bitwarden-crd-operator/crds/registry-credentials.yaml @@ -45,7 +45,8 @@ spec: - registry - name: v1beta5 served: true - storage: true + storage: false + deprecated: true schema: openAPIV3Schema: type: object @@ -75,3 +76,38 @@ spec: - usernameRef - passwordRef - registry + - name: v1beta6 + served: true + storage: 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 + annotations: + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - id + - namespace + - name + - usernameRef + - passwordRef + - registry diff --git a/charts/bitwarden-crd-operator/templates/deployment.yaml b/charts/bitwarden-crd-operator/templates/deployment.yaml index 707aec6..d7e642a 100644 --- a/charts/bitwarden-crd-operator/templates/deployment.yaml +++ b/charts/bitwarden-crd-operator/templates/deployment.yaml @@ -8,6 +8,8 @@ spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} {{- end }} + strategy: + type: {{ .Values.deploymentStrategy }} selector: matchLabels: {{- include "bitwarden-crd-operator.selectorLabels" . | nindent 6 }} diff --git a/charts/bitwarden-crd-operator/values.yaml b/charts/bitwarden-crd-operator/values.yaml index 000f6d6..54b8afc 100644 --- a/charts/bitwarden-crd-operator/values.yaml +++ b/charts/bitwarden-crd-operator/values.yaml @@ -14,6 +14,8 @@ imagePullSecrets: [] nameOverride: "" fullnameOverride: "" +deploymentStrategy: "Recreate" + # env: # - name: BW_FORCE_SYNC # value: "false" diff --git a/example.yaml b/example.yaml index 3de4bd3..9579b48 100644 --- a/example.yaml +++ b/example.yaml @@ -1,8 +1,9 @@ --- -apiVersion: "lerentis.uploadfilter24.eu/v1beta5" +apiVersion: "lerentis.uploadfilter24.eu/v1beta6" kind: BitwardenSecret metadata: name: test + namespace: default spec: content: - element: @@ -19,8 +20,10 @@ spec: labels: key: value app: example-app + annotations: + custom.annotation: is-used --- -apiVersion: "lerentis.uploadfilter24.eu/v1beta5" +apiVersion: "lerentis.uploadfilter24.eu/v1beta6" kind: BitwardenSecret metadata: name: test-scope diff --git a/example_dockerlogin.yaml b/example_dockerlogin.yaml index 3df8a96..767f4cb 100644 --- a/example_dockerlogin.yaml +++ b/example_dockerlogin.yaml @@ -1,5 +1,5 @@ --- -apiVersion: "lerentis.uploadfilter24.eu/v1beta5" +apiVersion: "lerentis.uploadfilter24.eu/v1beta6" kind: RegistryCredential metadata: name: test @@ -12,4 +12,6 @@ spec: namespace: "default" labels: namespace: default - tenant: example-team \ No newline at end of file + tenant: example-team + annotations: + custom.annotation: is-used \ No newline at end of file diff --git a/example_template.yaml b/example_template.yaml index b159d32..490e145 100644 --- a/example_template.yaml +++ b/example_template.yaml @@ -1,5 +1,5 @@ --- -apiVersion: "lerentis.uploadfilter24.eu/v1beta5" +apiVersion: "lerentis.uploadfilter24.eu/v1beta6" kind: BitwardenTemplate metadata: name: test @@ -10,6 +10,8 @@ spec: labels: key: value app: example-app + annotations: + custom.annotation: is-used template: | --- api: diff --git a/requirements.txt b/requirements.txt index a58d0b2..10629d5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -kopf==1.37.1 +kopf==1.37.2 kubernetes==29.0.0 -Jinja2==3.1.3 +Jinja2==3.1.4 schedule==1.2.1 \ No newline at end of file diff --git a/src/dockerlogin.py b/src/dockerlogin.py index 7adba42..543e18f 100644 --- a/src/dockerlogin.py +++ b/src/dockerlogin.py @@ -45,6 +45,7 @@ def create_managed_registry_secret(spec, name, namespace, logger, **kwargs): secret_name = spec.get('name') secret_namespace = spec.get('namespace') labels = spec.get('labels') + custom_annotations = spec.get('annotations') unlock_bw(logger) logger.info(f"Locking up secret with ID: {id}") @@ -57,6 +58,9 @@ def create_managed_registry_secret(spec, name, namespace, logger, **kwargs): "managedObject": f"{namespace}/{name}" } + if custom_annotations: + annotations.update(custom_annotations) + if not labels: labels = {} @@ -70,6 +74,11 @@ def create_managed_registry_secret(spec, name, namespace, logger, **kwargs): username_ref, password_ref, registry) + + # 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( secret_namespace, secret @@ -97,6 +106,7 @@ def update_managed_registry_secret( secret_name = spec.get('name') secret_namespace = spec.get('namespace') labels = spec.get('labels') + custom_annotations = spec.get('annotations') old_config = None old_secret_name = None @@ -134,6 +144,9 @@ def update_managed_registry_secret( "managedObject": f"{namespace}/{name}" } + if custom_annotations: + annotations.update(custom_annotations) + if not labels: labels = {} @@ -147,6 +160,12 @@ def update_managed_registry_secret( username_ref, password_ref, registry) + + # 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, @@ -154,9 +173,12 @@ def update_managed_registry_secret( namespace="{}".format(secret_namespace)) logger.info( f"Secret {secret_namespace}/{secret_name} has been updated") - except BaseException: + except BaseException as e: logger.warn( f"Could not update secret {secret_namespace}/{secret_name}!") + logger.warn( + f"Exception: {e}" + ) @kopf.on.delete('registry-credential.lerentis.uploadfilter24.eu') diff --git a/src/kv.py b/src/kv.py index f299cde..921d849 100644 --- a/src/kv.py +++ b/src/kv.py @@ -42,6 +42,7 @@ def create_managed_secret(spec, name, namespace, logger, body, **kwargs): secret_name = spec.get('name') secret_namespace = spec.get('namespace') labels = spec.get('labels') + custom_annotations = spec.get('annotations') unlock_bw(logger) logger.info(f"Locking up secret with ID: {id}") @@ -54,6 +55,9 @@ def create_managed_secret(spec, name, namespace, logger, body, **kwargs): "managedObject": f"{namespace}/{name}" } + if custom_annotations: + annotations.update(custom_annotations) + if not labels: labels = {} @@ -62,6 +66,11 @@ def create_managed_secret(spec, name, namespace, logger, body, **kwargs): name=secret_name, annotations=annotations, labels=labels) secret = create_kv(secret, secret_json_object, 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 @@ -94,6 +103,7 @@ def update_managed_secret( secret_name = spec.get('name') secret_namespace = spec.get('namespace') labels = spec.get('labels') + custom_annotations = spec.get('annotations') if old_config is not None and ( old_secret_name != secret_name or old_secret_namespace != secret_namespace): @@ -120,6 +130,9 @@ def update_managed_secret( "managedObject": f"{namespace}/{name}" } + if custom_annotations: + annotations.update(custom_annotations) + if not labels: labels = {} @@ -128,6 +141,11 @@ def update_managed_secret( name=secret_name, annotations=annotations, labels=labels) secret = create_kv(secret, secret_json_object, 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, @@ -135,9 +153,12 @@ def update_managed_secret( namespace="{}".format(secret_namespace)) logger.info( f"Secret {secret_namespace}/{secret_name} has been updated") - except BaseException: + except BaseException as e: logger.warn( f"Could not update secret {secret_namespace}/{secret_name}!") + logger.warn( + f"Exception: {e}" + ) @kopf.on.delete('bitwarden-secret.lerentis.uploadfilter24.eu') diff --git a/src/template.py b/src/template.py index 2654f0c..c3ff026 100644 --- a/src/template.py +++ b/src/template.py @@ -34,6 +34,7 @@ def create_managed_secret(spec, name, namespace, logger, body, **kwargs): secret_name = spec.get('name') secret_namespace = spec.get('namespace') labels = spec.get('labels') + custom_annotations = spec.get('annotations') unlock_bw(logger) @@ -44,6 +45,9 @@ def create_managed_secret(spec, name, namespace, logger, body, **kwargs): "managedObject": f"{namespace}/{name}" } + if custom_annotations: + annotations.update(custom_annotations) + if not labels: labels = {} @@ -52,6 +56,11 @@ def create_managed_secret(spec, name, namespace, logger, body, **kwargs): name=secret_name, annotations=annotations, labels=labels) secret = create_template_secret(logger, secret, filename, template) + # 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( secret_namespace, secret ) @@ -75,6 +84,7 @@ def update_managed_secret( secret_name = spec.get('name') secret_namespace = spec.get('namespace') labels = spec.get('labels') + custom_annotations = spec.get('annotations') old_config = None old_secret_name = None @@ -110,6 +120,9 @@ def update_managed_secret( "managedObject": f"{namespace}/{name}" } + if custom_annotations: + annotations.update(custom_annotations) + if not labels: labels = {} @@ -118,6 +131,11 @@ def update_managed_secret( name=secret_name, annotations=annotations, labels=labels) secret = create_template_secret(logger, secret, filename, template) + # 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, @@ -125,9 +143,12 @@ def update_managed_secret( namespace="{}".format(secret_namespace)) logger.info( f"Secret {secret_namespace}/{secret_name} has been updated") - except BaseException: + 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')