From 69e56ef4f585f21df59fefc2298d094ad253c495 Mon Sep 17 00:00:00 2001 From: Tobias Trabelsi Date: Sat, 12 Nov 2022 23:57:42 +0100 Subject: [PATCH] separated source files --- Dockerfile | 16 +-- bitwarden-crd-operator.py | 153 ----------------------- charts/bitwarden-crd-operator/Chart.yaml | 10 +- requirements.txt | 4 +- src/bitwardenCrdOperator.py | 33 +++++ src/dockerlogin.py | 69 ++++++++++ src/kv.py | 63 ++++++++++ 7 files changed, 181 insertions(+), 167 deletions(-) delete mode 100755 bitwarden-crd-operator.py create mode 100755 src/bitwardenCrdOperator.py create mode 100644 src/dockerlogin.py create mode 100644 src/kv.py diff --git a/Dockerfile b/Dockerfile index ce684c1..4471461 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,14 +40,16 @@ RUN set -eux; \ apt-get update; \ apt-get upgrade -y; \ apt-get install -y --no-install-recommends python3 python3-pip; \ - apt-get clean; + apt-get clean; \ + apt-get -y autoremove; \ + pip install -r requirements.txt; \ + rm requirements.txt; \ + pip cache purge; \ + rm -rf /root/.cache; -COPY --chown=bw-operator:bw-operator bitwarden-crd-operator.py /home/bw-operator/bitwarden-crd-operator.py +COPY --chown=bw-operator:bw-operator src /home/bw-operator USER bw-operator -RUN set -eux; \ - pip install -r requirements.txt --no-warn-script-location - -ENTRYPOINT [ "/home/bw-operator/.local/bin/kopf", "run", "--all-namespaces", "--liveness=http://0.0.0.0:8080/healthz" ] -CMD [ "/home/bw-operator/bitwarden-crd-operator.py" ] +ENTRYPOINT [ "kopf", "run", "--all-namespaces", "--liveness=http://0.0.0.0:8080/healthz" ] +CMD [ "/home/bw-operator/bitwardenCrdOperator.py", "/home/bw-operator/kv.py", "/home/bw-operator/dockerlogin.py" ] diff --git a/bitwarden-crd-operator.py b/bitwarden-crd-operator.py deleted file mode 100755 index f204e04..0000000 --- a/bitwarden-crd-operator.py +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env python3 -import kopf -import kubernetes -import base64 -import os -import subprocess -import json - -from pprint import pprint - -def get_secret_from_bitwarden(logger, id): - logger.info(f"Locking up secret with ID: {id}") - return command_wrapper(logger, f"get item {id}") - -def unlock_bw(logger): - token_output = command_wrapper(logger, "unlock --passwordenv BW_PASSWORD") - tokens = token_output.split('"')[1::2] - os.environ["BW_SESSION"] = tokens[1] - logger.info("Signin successful. Session exported") - -def command_wrapper(logger, command): - system_env = dict(os.environ) - sp = subprocess.Popen([f"bw {command}"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, shell=True, env=system_env) - out, err = sp.communicate() - if err: - logger.warn(f"Error during bw cli invokement: {err}") - return out.decode(encoding='UTF-8') - -def create_kv(secret, secret_json, content_def): - secret.type = "Opaque" - secret.data = {} - for eleml in content_def: - for k, elem in eleml.items(): - for key,value in elem.items(): - if key == "secretName": - _secret_key = value - if key == "secretRef": - _secret_ref = value - secret.data[_secret_ref] = str(base64.b64encode(secret_json["login"][_secret_key].encode("utf-8")), "utf-8") - return secret - -def create_dockerlogin(logger, secret, secret_json, username_ref, password_ref, registry): - secret.type = "dockerconfigjson" - secret.data = {} - auths_dict = {} - registry_dict = {} - reg_auth_dict = {} - - _username = secret_json["login"][username_ref] - logger.info(f"Creating login with username: {_username}") - _password = secret_json["login"][password_ref] - cred_field = str(base64.b64encode(f"{_username}:{_password}".encode("utf-8")), "utf-8") - - reg_auth_dict["auth"] = cred_field - registry_dict[registry] = reg_auth_dict - auths_dict["auths"] = registry_dict - secret.data[".dockerconfigjson"] = str(base64.b64encode(json.dumps(auths_dict).encode("utf-8")), "utf-8") - return secret - -@kopf.on.startup() -def bitwarden_signin(logger, **kwargs): - if 'BW_HOST' in os.environ: - command_wrapper(logger, f"config server {os.getenv('BW_HOST')}") - else: - logger.info(f"BW_HOST not set. Assuming SaaS installation") - command_wrapper(logger, "login --apikey") - unlock_bw(logger) - -@kopf.on.create('registry-credentials.lerentis.uploadfilter24.eu') -def create_managed_registry_secret(spec, name, namespace, logger, body, **kwargs): - username_ref = spec.get('usernameRef') - password_ref = spec.get('passwordRef') - registry = spec.get('registry') - id = spec.get('id') - secret_name = spec.get('name') - secret_namespace = spec.get('namespace') - - unlock_bw(logger) - - secret_json_object = json.loads(get_secret_from_bitwarden(logger, id)) - - api = kubernetes.client.CoreV1Api() - - annotations = { - "managed": "registry-credentials.lerentis.uploadfilter24.eu", - "managedObject": f"{namespace}/{name}" - } - secret = kubernetes.client.V1Secret() - secret.metadata = kubernetes.client.V1ObjectMeta(name=secret_name, annotations=annotations) - secret = create_dockerlogin(logger, secret, secret_json_object, username_ref, password_ref, registry) - - obj = api.create_namespaced_secret( - secret_namespace, secret - ) - - logger.info(f"Registry Secret {secret_namespace}/{secret_name} has been created") - -@kopf.on.create('bitwarden-secrets.lerentis.uploadfilter24.eu') -def create_managed_secret(spec, name, namespace, logger, body, **kwargs): - - content_def = body['spec']['content'] - id = spec.get('id') - secret_name = spec.get('name') - secret_namespace = spec.get('namespace') - - unlock_bw(logger) - - secret_json_object = json.loads(get_secret_from_bitwarden(logger, id)) - - api = kubernetes.client.CoreV1Api() - - annotations = { - "managed": "bitwarden-secrets.lerentis.uploadfilter24.eu", - "managedObject": f"{namespace}/{name}" - } - secret = kubernetes.client.V1Secret() - secret.metadata = kubernetes.client.V1ObjectMeta(name=secret_name, annotations=annotations) - secret = create_kv(secret, secret_json_object, content_def) - - obj = api.create_namespaced_secret( - secret_namespace, secret - ) - - logger.info(f"Secret {secret_namespace}/{secret_name} has been created") - - -@kopf.on.update('bitwarden-secrets.lerentis.uploadfilter24.eu') -def my_handler(spec, old, new, diff, **_): - pass - -@kopf.on.delete('bitwarden-secrets.lerentis.uploadfilter24.eu') -def delete_managed_secret(spec, name, namespace, logger, **kwargs): - secret_name = spec.get('name') - secret_namespace = spec.get('namespace') - api = kubernetes.client.CoreV1Api() - - try: - api.delete_namespaced_secret(secret_name, secret_namespace) - logger.info(f"Secret {secret_namespace}/{secret_name} has been deleted") - except: - logger.warn(f"Could not delete secret {secret_namespace}/{secret_name}!") - -@kopf.on.delete('registry-credentials.lerentis.uploadfilter24.eu') -def delete_managed_secret(spec, name, namespace, logger, **kwargs): - secret_name = spec.get('name') - secret_namespace = spec.get('namespace') - api = kubernetes.client.CoreV1Api() - - try: - api.delete_namespaced_secret(secret_name, secret_namespace) - logger.info(f"Secret {secret_namespace}/{secret_name} has been deleted") - except: - logger.warn(f"Could not delete secret {secret_namespace}/{secret_name}!") diff --git a/charts/bitwarden-crd-operator/Chart.yaml b/charts/bitwarden-crd-operator/Chart.yaml index 4a3372b..9c1a040 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.3.0" +version: "v0.3.1" -appVersion: "0.2.0" +appVersion: "0.2.1" keywords: - operator @@ -42,8 +42,8 @@ annotations: artifacthub.io/license: MIT artifacthub.io/operator: "true" artifacthub.io/changes: | - - kind: added - description: "Added support for regestry credentials" + - kind: changed + description: "Mainternence update and image rebuild to include upstream fixes" artifacthub.io/images: | - name: bitwarden-crd-operator - image: lerentis/bitwarden-crd-operator:0.2.0 + image: lerentis/bitwarden-crd-operator:0.2.1 diff --git a/requirements.txt b/requirements.txt index 7511155..edc4ab1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -kopf -kubernetes \ No newline at end of file +kopf==1.35.6 +kubernetes==24.2.0 \ No newline at end of file diff --git a/src/bitwardenCrdOperator.py b/src/bitwardenCrdOperator.py new file mode 100755 index 0000000..4eee3b7 --- /dev/null +++ b/src/bitwardenCrdOperator.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +import kopf +import os +import subprocess + +def get_secret_from_bitwarden(logger, id): + logger.info(f"Locking up secret with ID: {id}") + return command_wrapper(logger, f"get item {id}") + +def unlock_bw(logger): + token_output = command_wrapper(logger, "unlock --passwordenv BW_PASSWORD") + tokens = token_output.split('"')[1::2] + os.environ["BW_SESSION"] = tokens[1] + logger.info("Signin successful. Session exported") + +def command_wrapper(logger, command): + system_env = dict(os.environ) + sp = subprocess.Popen([f"bw {command}"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, shell=True, env=system_env) + out, err = sp.communicate() + if err: + logger.warn(f"Error during bw cli invokement: {err}") + return out.decode(encoding='UTF-8') + + +@kopf.on.startup() +def bitwarden_signin(logger, **kwargs): + if 'BW_HOST' in os.environ: + command_wrapper(logger, f"config server {os.getenv('BW_HOST')}") + else: + logger.info(f"BW_HOST not set. Assuming SaaS installation") + command_wrapper(logger, "login --apikey") + unlock_bw(logger) + diff --git a/src/dockerlogin.py b/src/dockerlogin.py new file mode 100644 index 0000000..4171c27 --- /dev/null +++ b/src/dockerlogin.py @@ -0,0 +1,69 @@ +import kopf +import kubernetes +import base64 +import json + +from bitwardenCrdOperator import unlock_bw, get_secret_from_bitwarden + +def create_dockerlogin(logger, secret, secret_json, username_ref, password_ref, registry): + secret.type = "dockerconfigjson" + secret.data = {} + auths_dict = {} + registry_dict = {} + reg_auth_dict = {} + + _username = secret_json["login"][username_ref] + logger.info(f"Creating login with username: {_username}") + _password = secret_json["login"][password_ref] + cred_field = str(base64.b64encode(f"{_username}:{_password}".encode("utf-8")), "utf-8") + + reg_auth_dict["auth"] = cred_field + registry_dict[registry] = reg_auth_dict + auths_dict["auths"] = registry_dict + secret.data[".dockerconfigjson"] = str(base64.b64encode(json.dumps(auths_dict).encode("utf-8")), "utf-8") + return secret + +@kopf.on.create('registry-credentials.lerentis.uploadfilter24.eu') +def create_managed_registry_secret(spec, name, namespace, logger, **kwargs): + username_ref = spec.get('usernameRef') + password_ref = spec.get('passwordRef') + registry = spec.get('registry') + id = spec.get('id') + secret_name = spec.get('name') + secret_namespace = spec.get('namespace') + + unlock_bw(logger) + + secret_json_object = json.loads(get_secret_from_bitwarden(logger, id)) + + api = kubernetes.client.CoreV1Api() + + annotations = { + "managed": "registry-credentials.lerentis.uploadfilter24.eu", + "managedObject": f"{namespace}/{name}" + } + secret = kubernetes.client.V1Secret() + secret.metadata = kubernetes.client.V1ObjectMeta(name=secret_name, annotations=annotations) + secret = create_dockerlogin(logger, secret, secret_json_object, username_ref, password_ref, registry) + + obj = api.create_namespaced_secret( + secret_namespace, secret + ) + + logger.info(f"Registry Secret {secret_namespace}/{secret_name} has been created") + +@kopf.on.update('registry-credentials.lerentis.uploadfilter24.eu') +def my_handler(spec, old, new, diff, **_): + pass + +@kopf.on.delete('registry-credentials.lerentis.uploadfilter24.eu') +def delete_managed_secret(spec, name, namespace, logger, **kwargs): + secret_name = spec.get('name') + secret_namespace = spec.get('namespace') + api = kubernetes.client.CoreV1Api() + + try: + api.delete_namespaced_secret(secret_name, secret_namespace) + logger.info(f"Secret {secret_namespace}/{secret_name} has been deleted") + except: + logger.warn(f"Could not delete secret {secret_namespace}/{secret_name}!") diff --git a/src/kv.py b/src/kv.py new file mode 100644 index 0000000..59e97b1 --- /dev/null +++ b/src/kv.py @@ -0,0 +1,63 @@ +import kopf +import kubernetes +import base64 +import json + +from bitwardenCrdOperator import unlock_bw, get_secret_from_bitwarden + +def create_kv(secret, secret_json, content_def): + secret.type = "Opaque" + secret.data = {} + for eleml in content_def: + for k, elem in eleml.items(): + for key,value in elem.items(): + if key == "secretName": + _secret_key = value + if key == "secretRef": + _secret_ref = value + secret.data[_secret_ref] = str(base64.b64encode(secret_json["login"][_secret_key].encode("utf-8")), "utf-8") + return secret + +@kopf.on.create('bitwarden-secrets.lerentis.uploadfilter24.eu') +def create_managed_secret(spec, name, namespace, logger, body, **kwargs): + + content_def = body['spec']['content'] + id = spec.get('id') + secret_name = spec.get('name') + secret_namespace = spec.get('namespace') + + unlock_bw(logger) + + secret_json_object = json.loads(get_secret_from_bitwarden(logger, id)) + + api = kubernetes.client.CoreV1Api() + + annotations = { + "managed": "bitwarden-secrets.lerentis.uploadfilter24.eu", + "managedObject": f"{namespace}/{name}" + } + secret = kubernetes.client.V1Secret() + secret.metadata = kubernetes.client.V1ObjectMeta(name=secret_name, annotations=annotations) + secret = create_kv(secret, secret_json_object, content_def) + + obj = api.create_namespaced_secret( + secret_namespace, secret + ) + + logger.info(f"Secret {secret_namespace}/{secret_name} has been created") + +@kopf.on.update('bitwarden-secrets.lerentis.uploadfilter24.eu') +def my_handler(spec, old, new, diff, **_): + pass + +@kopf.on.delete('bitwarden-secrets.lerentis.uploadfilter24.eu') +def delete_managed_secret(spec, name, namespace, logger, **kwargs): + secret_name = spec.get('name') + secret_namespace = spec.get('namespace') + api = kubernetes.client.CoreV1Api() + + try: + api.delete_namespaced_secret(secret_name, secret_namespace) + logger.info(f"Secret {secret_namespace}/{secret_name} has been deleted") + except: + logger.warn(f"Could not delete secret {secret_namespace}/{secret_name}!") \ No newline at end of file