Compare commits
	
		
			72 Commits
		
	
	
		
			v0.4.0
			...
			4d36cd468f
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 4d36cd468f | ||
| 6f099c4bf2 | |||
|  | aa015cc7ba | ||
|  | 2de9bbb0bf | ||
|  | 4505f3985c | ||
|  | 82b684e460 | ||
|  | 8ec698f50e | ||
|  | 9b8fe1d8ef | ||
|  | 516f2a34cf | ||
| 361d0866e9 | |||
| 9d4ade904e | |||
| 8c3714f7e0 | |||
| 36ae5cc602 | |||
| d908419b78 | |||
| 2d399ff8ce | |||
| c753737497 | |||
| 886fe3783d | |||
|  | 18a47f8ad2 | ||
|  | e405734e72 | ||
| 8bf4292991 | |||
|  | b149b26485 | ||
|  | 5263a811e1 | ||
|  | 4b59ff1aac | ||
|  | ad1cc9f646 | ||
| 0f518ab28d | |||
| 1bf2a24cf2 | |||
| a73e8ff982 | |||
|  | 54a4ffa212 | ||
| 16040bf87a | |||
|  | 9c1c7417e1 | ||
|  | 0f9ca0869c | ||
|  | 6fbf060044 | ||
|  | 3bb40cdcb4 | ||
|  | 219c9d0413 | ||
|  | 4f92bfe86a | ||
|  | 640333cfc7 | ||
|  | 6a907f149f | ||
|  | 3db74524ca | ||
|  | e49df1fb4d | ||
|  | bb3ca7573b | ||
| 097712c6c6 | |||
|  | 3845fd8045 | ||
|  | 3caacac98a | ||
|  | beeca5a6b6 | ||
|  | 2d4c8ec14b | ||
| 10cc864275 | |||
| 689a6e5bae | |||
|  | 4e23b67f5d | ||
|  | f4d05fdd0f | ||
|  | 48bc422974 | ||
|  | 41d4959422 | ||
|  | c2116c24ec | ||
|  | 67692b372f | ||
| 8a6219718a | |||
|  | a10f6b3c9a | ||
|  | 56657df85a | ||
|  | 6a324e66da | ||
|  | 6081374696 | ||
|  | a3cec12284 | ||
| 40f76a8bdb | |||
| 8546855412 | |||
| 938ddd1bb6 | |||
| 5adc7785d0 | |||
| df1fdcbb14 | |||
| 3328016da4 | |||
| fdd3808c7e | |||
|  | fee8dfb97a | ||
|  | 2611231c8a | ||
| 884476606c | |||
|  | 92c51a21d0 | ||
|  | 12edc8445f | ||
|  | df7b9fd043 | 
							
								
								
									
										59
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										59
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -8,7 +8,9 @@ on: | ||||
| jobs: | ||||
|   release: | ||||
|     permissions: | ||||
|       id-token: write | ||||
|       contents: write | ||||
|       packages: write | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout | ||||
| @@ -27,8 +29,63 @@ jobs: | ||||
|           version: v3.10.0 | ||||
|  | ||||
|       - name: Run chart-releaser | ||||
|         uses: helm/chart-releaser-action@v1.4.1 | ||||
|         uses: helm/chart-releaser-action@v1.5.0 | ||||
|         with: | ||||
|           charts_dir: charts | ||||
|         env: | ||||
|           CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" | ||||
|  | ||||
|       - name: Get app version from chart | ||||
|         uses: mikefarah/yq@v4.34.1 | ||||
|         id: app_version | ||||
|         with: | ||||
|           cmd: yq '.appVersion' charts/bitwarden-crd-operator/Chart.yaml | ||||
|  | ||||
|       - name: "GHCR Login" | ||||
|         uses: docker/login-action@v2 | ||||
|         with: | ||||
|           registry: ghcr.io | ||||
|           username: lerentis | ||||
|           password: ${{ secrets.GITHUB_TOKEN }} | ||||
|  | ||||
|       - name: Set up QEMU | ||||
|         uses: docker/setup-qemu-action@v2 | ||||
|        | ||||
|       - name: Set up Docker Buildx | ||||
|         uses: docker/setup-buildx-action@v2 | ||||
|  | ||||
|       - name: "GHCR Build and Push" | ||||
|         id: docker_build | ||||
|         uses: docker/build-push-action@v4 | ||||
|         with: | ||||
|           push: true | ||||
|           platforms: linux/amd64,linux/arm64 | ||||
|           tags: ghcr.io/lerentis/bitwarden-crd-operator:${{ steps.app_version.outputs.result }} | ||||
|  | ||||
|       - name: Create SBOM | ||||
|         uses: anchore/sbom-action@v0 | ||||
|         with: | ||||
|           image: ghcr.io/lerentis/bitwarden-crd-operator:${{ steps.app_version.outputs.result }} | ||||
|          | ||||
|       - name: Publish SBOM | ||||
|         uses: anchore/sbom-action/publish-sbom@v0 | ||||
|         with: | ||||
|           sbom-artifact-match: ".*\\.spdx\\.json" | ||||
|  | ||||
|       - name: Get Latest Tag | ||||
|         id: previoustag | ||||
|         uses: WyriHaximus/github-action-get-previous-tag@v1 | ||||
|  | ||||
|       - name: Download SBOM from github action | ||||
|         uses: actions/download-artifact@v3 | ||||
|         with: | ||||
|           name: ${{ env.ANCHORE_SBOM_ACTION_PRIOR_ARTIFACT }} | ||||
|  | ||||
|       - name: Add SBOM to release | ||||
|         uses: svenstaro/upload-release-action@v2 | ||||
|         with: | ||||
|           repo_token: ${{ secrets.GITHUB_TOKEN }} | ||||
|           file_glob: true | ||||
|           file: lerentis-bitwarden-crd-operator_*.spdx.json | ||||
|           tag:  ${{ steps.previoustag.outputs.tag }} | ||||
|           overwrite: true | ||||
|   | ||||
							
								
								
									
										55
									
								
								.github/workflows/test-and-lint.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								.github/workflows/test-and-lint.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| name: Lint and Test | ||||
|  | ||||
| on: pull_request | ||||
|  | ||||
| jobs: | ||||
|   lint-test: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v3 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|  | ||||
|       - name: Set up Helm | ||||
|         uses: azure/setup-helm@v3 | ||||
|         with: | ||||
|           version: v3.11.2 | ||||
|  | ||||
|       - uses: actions/setup-python@v4 | ||||
|         with: | ||||
|           python-version: '3.9' | ||||
|           check-latest: true | ||||
|  | ||||
|       - name: Set up chart-testing | ||||
|         uses: helm/chart-testing-action@v2.4.0 | ||||
|  | ||||
|       - name: Run chart-testing (list-changed) | ||||
|         id: list-changed | ||||
|         run: | | ||||
|           changed=$(ct list-changed --target-branch ${{ github.event.repository.default_branch }}) | ||||
|           if [[ -n "$changed" ]]; then | ||||
|             echo "changed=true" >> "$GITHUB_OUTPUT" | ||||
|           fi | ||||
|  | ||||
|       - name: Run chart-testing (lint) | ||||
|         if: steps.list-changed.outputs.changed == 'true' | ||||
|         run: ct lint --target-branch ${{ github.event.repository.default_branch }} | ||||
|  | ||||
|   pr-build: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Set up QEMU | ||||
|         uses: docker/setup-qemu-action@v2 | ||||
|        | ||||
|       - name: Set up Docker Buildx | ||||
|         uses: docker/setup-buildx-action@v2 | ||||
|  | ||||
|       - name: "GHCR Build" | ||||
|         id: docker_build | ||||
|         uses: docker/build-push-action@v4 | ||||
|         with: | ||||
|           push: false | ||||
|           platforms: linux/amd64,linux/arm64 | ||||
|           tags: ghcr.io/lerentis/bitwarden-crd-operator:dev | ||||
|  | ||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -166,3 +166,5 @@ lib | ||||
| lib64 | ||||
|  | ||||
| myvalues.yaml | ||||
|  | ||||
| .vscode | ||||
							
								
								
									
										48
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								Dockerfile
									
									
									
									
									
								
							| @@ -1,30 +1,50 @@ | ||||
| FROM alpine:latest as builder | ||||
| FROM alpine:3.18.0 | ||||
|  | ||||
| ARG BW_VERSION=2022.8.0 | ||||
| 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 | ||||
|  | ||||
| RUN apk add wget unzip | ||||
| ARG PYTHON_VERSION=3.11.4-r0 | ||||
| ARG PIP_VERSION=23.1.2-r0 | ||||
| ARG GCOMPAT_VERSION=1.1.0-r1 | ||||
| ARG LIBCRYPTO_VERSION=3.1.0-r4 | ||||
| ARG BW_VERSION=2023.1.0 | ||||
|  | ||||
| RUN cd /tmp && wget https://github.com/bitwarden/clients/releases/download/cli-v${BW_VERSION}/bw-linux-${BW_VERSION}.zip && \ | ||||
|     unzip /tmp/bw-linux-${BW_VERSION}.zip | ||||
|  | ||||
| FROM alpine:3.17 | ||||
|  | ||||
| COPY --from=builder /tmp/bw /usr/local/bin/bw | ||||
| COPY requirements.txt requirements.txt | ||||
| COPY requirements.txt /requirements.txt | ||||
|  | ||||
| RUN set -eux; \ | ||||
|     apk add --virtual build-dependencies wget unzip; \ | ||||
|     ARCH="$(apk --print-arch)"; \ | ||||
|     case "${ARCH}" in \ | ||||
|        aarch64|arm64) \ | ||||
|           apk add npm; \ | ||||
|           npm install -g @bitwarden/cli@${BW_VERSION}; \ | ||||
|          ;; \ | ||||
|        amd64|x86_64) \ | ||||
|           cd /tmp; \ | ||||
|           wget https://github.com/bitwarden/clients/releases/download/cli-v${BW_VERSION}/bw-linux-${BW_VERSION}.zip; \ | ||||
|           unzip /tmp/bw-linux-${BW_VERSION}.zip; \ | ||||
|           mv /tmp/bw /usr/local/bin/bw; \ | ||||
|           chmod +x /usr/local/bin/bw; \ | ||||
|          ;; \ | ||||
|        *) \ | ||||
|          echo "Unsupported arch: ${ARCH}"; \ | ||||
|          exit 1; \ | ||||
|          ;; \ | ||||
|     esac; \ | ||||
|     apk del --purge build-dependencies; \ | ||||
|     addgroup -S -g 1000 bw-operator; \ | ||||
|     adduser -S -D -u 1000 -G bw-operator bw-operator; \ | ||||
|     mkdir -p /home/bw-operator; \ | ||||
|     chown -R bw-operator /home/bw-operator; \ | ||||
|     chmod +x /usr/local/bin/bw; \ | ||||
|     apk add gcc musl-dev libstdc++ gcompat python3 py-pip; \ | ||||
|     pip install -r requirements.txt --no-warn-script-location; \ | ||||
|     apk add gcc musl-dev libstdc++ gcompat=${GCOMPAT_VERSION} python3=${PYTHON_VERSION} py3-pip=${PIP_VERSION} libcrypto3=${LIBCRYPTO_VERSION}; \ | ||||
|     pip install -r /requirements.txt --no-warn-script-location; \ | ||||
|     rm /requirements.txt; \ | ||||
|     apk del --purge gcc musl-dev libstdc++; | ||||
|  | ||||
| COPY --chown=bw-operator:bw-operator src /home/bw-operator | ||||
|  | ||||
| USER bw-operator | ||||
|  | ||||
| ENTRYPOINT [ "kopf", "run", "--all-namespaces", "--liveness=http://0.0.0.0:8080/healthz" ] | ||||
| ENTRYPOINT [ "kopf", "run", "--log-format=json", "--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", "/home/bw-operator/template.py"] | ||||
|   | ||||
							
								
								
									
										50
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								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/v1beta3" | ||||
| apiVersion: "lerentis.uploadfilter24.eu/v1beta4" | ||||
| kind: BitwardenSecret | ||||
| metadata: | ||||
|   name: name-of-your-management-object | ||||
| @@ -65,9 +65,11 @@ spec: | ||||
|     - element: | ||||
|         secretName: nameOfTheFieldInBitwarden # for example username | ||||
|         secretRef: nameOfTheKeyInTheSecretToBeCreated  | ||||
|         secretScope: login # for custom entries on bitwarden use 'fields'  | ||||
|     - element: | ||||
|         secretName: nameOfAnotherFieldInBitwarden # for example password | ||||
|         secretRef: nameOfAnotherKeyInTheSecretToBeCreated  | ||||
|         secretScope: login # for custom entries on bitwarden use 'fields'  | ||||
|   id: "A Secret ID from bitwarden" | ||||
|   name: "Name of the secret to be created" | ||||
|   namespace: "Namespace of the secret to be created" | ||||
| @@ -96,7 +98,7 @@ For managing registry credentials, or pull secrets, you can create another kind | ||||
|  | ||||
| ```yaml | ||||
| --- | ||||
| apiVersion: "lerentis.uploadfilter24.eu/v1beta3" | ||||
| apiVersion: "lerentis.uploadfilter24.eu/v1beta4" | ||||
| kind: RegistryCredential | ||||
| metadata: | ||||
|   name: name-of-your-management-object | ||||
| @@ -125,6 +127,50 @@ metadata: | ||||
| type: dockerconfigjson | ||||
| ``` | ||||
|  | ||||
| ## BitwardenTemplate | ||||
|  | ||||
| One of the more freely defined types that can be used with this operator you can just pass a whole template: | ||||
|  | ||||
| ```yaml | ||||
| --- | ||||
| apiVersion: "lerentis.uploadfilter24.eu/v1beta4" | ||||
| 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" | ||||
|   namespace: "Namespace of the secret to be created" | ||||
|   template: | | ||||
|     --- | ||||
|     api: | ||||
|       enabled: True | ||||
|       key: {{ bitwarden_lookup("A Secret ID from bitwarden", "login or fields", "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", "name of a field in bitwarden") }} | ||||
|           enabled: true | ||||
| ``` | ||||
|  | ||||
| This will result in something like the following object: | ||||
|  | ||||
| ```yaml | ||||
| 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 | ||||
|   name: Name of the secret to be created | ||||
|   namespace: Namespace of the secret to be created | ||||
| type: Opaque | ||||
| ``` | ||||
|  | ||||
| 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. | ||||
|  | ||||
| ## Short Term Roadmap | ||||
|  | ||||
| - [ ] support more types | ||||
|   | ||||
| @@ -4,9 +4,9 @@ description: Deploy the Bitwarden CRD Operator | ||||
|  | ||||
| type: application | ||||
|  | ||||
| version: "v0.4.0" | ||||
| version: "v0.7.4" | ||||
|  | ||||
| appVersion: "0.4.0" | ||||
| appVersion: "0.6.4" | ||||
|  | ||||
| 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.23.0-0" | ||||
|  | ||||
| maintainers: | ||||
|   - name: lerentis | ||||
| @@ -85,23 +85,18 @@ annotations: | ||||
|           --- | ||||
|           api: | ||||
|             enabled: True | ||||
|             key: {{ bitwarden_lookup("466fc4b0-ffca-4444-8d88-b59d4de3d928", "fields", "key") }} | ||||
|             key: {{ bitwarden_lookup("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "fields", "key") }} | ||||
|             allowCrossOrigin: false | ||||
|             apps: | ||||
|               "some.app.identifier:some_version": | ||||
|                 pubkey: {{ bitwarden_lookup("466fc4b0-ffca-4444-8d88-b59d4de3d928", "fields", "public_key") }} | ||||
|                 pubkey: {{ bitwarden_lookup("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "fields", "public_key") }} | ||||
|                 enabled: true | ||||
|   artifacthub.io/license: MIT | ||||
|   artifacthub.io/operator: "true" | ||||
|   artifacthub.io/containsSecurityUpdates: "false" | ||||
|   artifacthub.io/changes: | | ||||
|     - kind: added | ||||
|       description: "Added Template type" | ||||
|     - kind: added | ||||
|       description: "Added logo" | ||||
|     - kind: changed | ||||
|       description: "BitwardenSecret now requires a 'secretScope' to be defined. Can eigher be 'login' or 'fields'" | ||||
|     - kind: fixed | ||||
|       description: "fixed hardcoded reference to 'login' even tho secrets could also be in 'fields' scope" | ||||
|       description: "Fixed bitwarden installation" | ||||
|   artifacthub.io/images: | | ||||
|     - name: bitwarden-crd-operator | ||||
|       image: lerentis/bitwarden-crd-operator:0.4.0 | ||||
|       image: ghcr.io/lerentis/bitwarden-crd-operator:0.6.4 | ||||
|   | ||||
| @@ -4,9 +4,14 @@ | ||||
|  | ||||
| Bitwarden CRD Operator is a kubernetes Operator based on [kopf](https://github.com/nolar/kopf/). The goal is to create kubernetes native secret objects from bitwarden. | ||||
|  | ||||
| <p align="center"> | ||||
|   <img src="https://github.com/Lerentis/bitwarden-crd-operator/blob/main/logo.png?raw=true" alt="Bitwarden CRD Operator Logo" width="200"/> | ||||
| </p> | ||||
|  | ||||
| > DISCLAIMER:   | ||||
| > This project is still very work in progress :) | ||||
|  | ||||
|  | ||||
| ## Getting started | ||||
|  | ||||
| You will need a `ClientID` and `ClientSecret` ([where to get these](https://bitwarden.com/help/personal-api-key/)) as well as your password. | ||||
| @@ -51,7 +56,7 @@ And you are set to create your first secret using this operator. For that you ne | ||||
|  | ||||
| ```yaml | ||||
| --- | ||||
| apiVersion: "lerentis.uploadfilter24.eu/v1beta3" | ||||
| apiVersion: "lerentis.uploadfilter24.eu/v1beta4" | ||||
| kind: BitwardenSecret | ||||
| metadata: | ||||
|   name: name-of-your-management-object | ||||
| @@ -60,9 +65,11 @@ spec: | ||||
|     - element: | ||||
|         secretName: nameOfTheFieldInBitwarden # for example username | ||||
|         secretRef: nameOfTheKeyInTheSecretToBeCreated  | ||||
|         secretScope: login # for custom entries on bitwarden use 'fields'  | ||||
|     - element: | ||||
|         secretName: nameOfAnotherFieldInBitwarden # for example password | ||||
|         secretRef: nameOfAnotherKeyInTheSecretToBeCreated  | ||||
|         secretScope: login # for custom entries on bitwarden use 'fields'  | ||||
|   id: "A Secret ID from bitwarden" | ||||
|   name: "Name of the secret to be created" | ||||
|   namespace: "Namespace of the secret to be created" | ||||
| @@ -91,7 +98,7 @@ For managing registry credentials, or pull secrets, you can create another kind | ||||
|  | ||||
| ```yaml | ||||
| --- | ||||
| apiVersion: "lerentis.uploadfilter24.eu/v1beta3" | ||||
| apiVersion: "lerentis.uploadfilter24.eu/v1beta4" | ||||
| kind: RegistryCredential | ||||
| metadata: | ||||
|   name: name-of-your-management-object | ||||
| @@ -120,10 +127,46 @@ metadata: | ||||
| type: dockerconfigjson | ||||
| ``` | ||||
|  | ||||
| ## Short Term Roadmap | ||||
| ## BitwardenTemplate | ||||
|  | ||||
| - [ ] support more types | ||||
| - [x] offer option to use a existing secret in helm chart | ||||
| - [x] host chart on gh pages | ||||
| - [x] write release pipeline | ||||
| - [x] maybe extend spec to offer modification of keys as well | ||||
| One of the more freely defined types that can be used with this operator you can just pass a whole template: | ||||
|  | ||||
| ```yaml | ||||
| --- | ||||
| apiVersion: "lerentis.uploadfilter24.eu/v1beta4" | ||||
| 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" | ||||
|   namespace: "Namespace of the secret to be created" | ||||
|   template: | | ||||
|     --- | ||||
|     api: | ||||
|       enabled: True | ||||
|       key: {{ bitwarden_lookup("A Secret ID from bitwarden", "login or fields", "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", "name of a field in bitwarden") }} | ||||
|           enabled: true | ||||
| ``` | ||||
|  | ||||
| This will result in something like the following object: | ||||
|  | ||||
| ```yaml | ||||
| 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 | ||||
|   name: Name of the secret to be created | ||||
|   namespace: Namespace of the secret to be created | ||||
| type: Opaque | ||||
| ``` | ||||
|  | ||||
| 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. | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
| replicaCount: 1 | ||||
|  | ||||
| image: | ||||
|   repository: lerentis/bitwarden-crd-operator | ||||
|   repository: ghcr.io/lerentis/bitwarden-crd-operator | ||||
|   pullPolicy: IfNotPresent | ||||
|   # Overrides the image tag whose default is the chart appVersion. | ||||
|   # tag: "0.1.0" | ||||
| @@ -14,15 +14,15 @@ imagePullSecrets: [] | ||||
| nameOverride: "" | ||||
| fullnameOverride: "" | ||||
|  | ||||
| #env: | ||||
| #  - name: BW_HOST | ||||
| #    value: "define_it" | ||||
| #  - name: BW_CLIENTID | ||||
| #    value: "define_it" | ||||
| #  - name: BW_CLIENTSECRET | ||||
| #    value: "define_it" | ||||
| #  - name: BW_PASSWORD | ||||
| #    value: "define_id" | ||||
| # env: | ||||
| #   - name: BW_HOST | ||||
| #     value: "define_it" | ||||
| #   - name: BW_CLIENTID | ||||
| #     value: "define_it" | ||||
| #   - name: BW_CLIENTSECRET | ||||
| #     value: "define_it" | ||||
| #   - name: BW_PASSWORD | ||||
| #     value: "define_id" | ||||
|  | ||||
| externalConfigSecret: | ||||
|   enabled: false | ||||
|   | ||||
							
								
								
									
										14
									
								
								example.yaml
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								example.yaml
									
									
									
									
									
								
							| @@ -16,3 +16,17 @@ spec: | ||||
|   id: "88781348-c81c-4367-9801-550360c21295" | ||||
|   name: "test-secret" | ||||
|   namespace: "default" | ||||
| --- | ||||
| apiVersion: "lerentis.uploadfilter24.eu/v1beta4" | ||||
| kind: BitwardenSecret | ||||
| metadata: | ||||
|   name: test-scope | ||||
| spec: | ||||
|   content: | ||||
|     - element: | ||||
|         secretName: public_key | ||||
|         secretRef: pubKey  | ||||
|         secretScope: fields | ||||
|   id: "466fc4b0-ffca-4444-8d88-b59d4de3d928" | ||||
|   name: "test-scope" | ||||
|   namespace: "default" | ||||
| @@ -1,3 +1,3 @@ | ||||
| kopf==1.35.6 | ||||
| kubernetes==25.3.0 | ||||
| kopf==1.36.1 | ||||
| kubernetes==26.1.0 | ||||
| Jinja2==3.1.2 | ||||
|   | ||||
| @@ -1,20 +1,20 @@ | ||||
| #!/usr/bin/env python3 | ||||
| import kopf | ||||
| import os | ||||
| import kopf | ||||
|  | ||||
| from utils.utils import command_wrapper, unlock_bw | ||||
|  | ||||
|  | ||||
| @kopf.on.startup() | ||||
| def bitwarden_signin(logger, **kwargs): | ||||
|     if 'BW_HOST' in os.environ: | ||||
|         try: | ||||
|             command_wrapper(f"config server {os.getenv('BW_HOST')}") | ||||
|         except: | ||||
|             logger.warn("Revieved none zero exit code from server config") | ||||
|             command_wrapper(logger, f"config server {os.getenv('BW_HOST')}") | ||||
|         except BaseException: | ||||
|             logger.warn("Received non-zero exit code from server config") | ||||
|             logger.warn("This is expected from startup") | ||||
|             pass | ||||
|     else: | ||||
|         logger.info(f"BW_HOST not set. Assuming SaaS installation") | ||||
|     command_wrapper("login --apikey") | ||||
|         logger.info("BW_HOST not set. Assuming SaaS installation") | ||||
|     command_wrapper(logger, "login --apikey") | ||||
|     unlock_bw(logger) | ||||
|  | ||||
|   | ||||
| @@ -5,7 +5,14 @@ import json | ||||
|  | ||||
| from utils.utils import unlock_bw, get_secret_from_bitwarden | ||||
|  | ||||
| def create_dockerlogin(logger, secret, secret_json, username_ref, password_ref, registry): | ||||
|  | ||||
| def create_dockerlogin( | ||||
|         logger, | ||||
|         secret, | ||||
|         secret_json, | ||||
|         username_ref, | ||||
|         password_ref, | ||||
|         registry): | ||||
|     secret.type = "dockerconfigjson" | ||||
|     secret.data = {} | ||||
|     auths_dict = {} | ||||
| @@ -15,14 +22,19 @@ def create_dockerlogin(logger, secret, secret_json, username_ref, password_ref, | ||||
|     _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") | ||||
|     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") | ||||
|     secret.data[".dockerconfigjson"] = str(base64.b64encode( | ||||
|         json.dumps(auths_dict).encode("utf-8")), "utf-8") | ||||
|     return secret | ||||
|  | ||||
|  | ||||
| @kopf.on.create('registry-credential.lerentis.uploadfilter24.eu') | ||||
| def create_managed_registry_secret(spec, name, namespace, logger, **kwargs): | ||||
|     username_ref = spec.get('usernameRef') | ||||
| @@ -34,7 +46,7 @@ def create_managed_registry_secret(spec, name, namespace, logger, **kwargs): | ||||
|  | ||||
|     unlock_bw(logger) | ||||
|     logger.info(f"Locking up secret with ID: {id}") | ||||
|     secret_json_object = json.loads(get_secret_from_bitwarden(id)) | ||||
|     secret_json_object = get_secret_from_bitwarden(logger, id) | ||||
|  | ||||
|     api = kubernetes.client.CoreV1Api() | ||||
|  | ||||
| @@ -43,18 +55,98 @@ def create_managed_registry_secret(spec, name, namespace, logger, **kwargs): | ||||
|         "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)    | ||||
|     secret.metadata = kubernetes.client.V1ObjectMeta( | ||||
|         name=secret_name, annotations=annotations) | ||||
|     secret = create_dockerlogin( | ||||
|         logger, | ||||
|         secret, | ||||
|         secret_json_object["data"], | ||||
|         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") | ||||
|     logger.info( | ||||
|         f"Registry Secret {secret_namespace}/{secret_name} has been created") | ||||
|  | ||||
|  | ||||
| @kopf.on.update('registry-credential.lerentis.uploadfilter24.eu') | ||||
| def my_handler(spec, old, new, diff, **_): | ||||
|     pass | ||||
| @kopf.timer('registry-credential.lerentis.uploadfilter24.eu', interval=900) | ||||
| def update_managed_registry_secret( | ||||
|         spec, | ||||
|         status, | ||||
|         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') | ||||
|  | ||||
|     old_config = None | ||||
|     old_secret_name = None | ||||
|     old_secret_namespace = 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') | ||||
|     secret_name = spec.get('name') | ||||
|     secret_namespace = spec.get('namespace') | ||||
|  | ||||
|     if old_config is not None and ( | ||||
|             old_secret_name != secret_name or old_secret_namespace != secret_namespace): | ||||
|         # 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_registry_secret(spec, name, namespace, logger, **kwargs) | ||||
|         return | ||||
|  | ||||
|     unlock_bw(logger) | ||||
|     logger.info(f"Locking up secret with ID: {id}") | ||||
|     secret_json_object = get_secret_from_bitwarden(logger, id) | ||||
|  | ||||
|     api = kubernetes.client.CoreV1Api() | ||||
|  | ||||
|     annotations = { | ||||
|         "managed": "registry-credential.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["data"], | ||||
|         username_ref, | ||||
|         password_ref, | ||||
|         registry) | ||||
|     try: | ||||
|         obj = 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: | ||||
|         logger.warn( | ||||
|             f"Could not update secret {secret_namespace}/{secret_name}!") | ||||
|  | ||||
|  | ||||
| @kopf.on.delete('registry-credential.lerentis.uploadfilter24.eu') | ||||
| def delete_managed_secret(spec, name, namespace, logger, **kwargs): | ||||
| @@ -64,6 +156,8 @@ def delete_managed_secret(spec, name, namespace, logger, **kwargs): | ||||
|  | ||||
|     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}!") | ||||
|         logger.info( | ||||
|             f"Secret {secret_namespace}/{secret_name} has been deleted") | ||||
|     except BaseException: | ||||
|         logger.warn( | ||||
|             f"Could not delete secret {secret_namespace}/{secret_name}!") | ||||
|   | ||||
							
								
								
									
										101
									
								
								src/kv.py
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								src/kv.py
									
									
									
									
									
								
							| @@ -5,12 +5,13 @@ import json | ||||
|  | ||||
| from utils.utils import unlock_bw, get_secret_from_bitwarden, parse_login_scope, parse_fields_scope | ||||
|  | ||||
|  | ||||
| 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(): | ||||
|             for key, value in elem.items(): | ||||
|                 if key == "secretName": | ||||
|                     _secret_key = value | ||||
|                 if key == "secretRef": | ||||
| @@ -18,11 +19,22 @@ def create_kv(secret, secret_json, content_def): | ||||
|                 if key == "secretScope": | ||||
|                     _secret_scope = value | ||||
|             if _secret_scope == "login": | ||||
|                 secret.data[_secret_ref] = str(base64.b64encode(parse_login_scope(secret_json, _secret_key).encode("utf-8")), "utf-8") | ||||
|                 value = parse_login_scope(secret_json, _secret_key) | ||||
|                 if value is None: | ||||
|                     raise Exception( | ||||
|                         f"Field {_secret_key} has no value in bitwarden secret") | ||||
|                 secret.data[_secret_ref] = str(base64.b64encode( | ||||
|                     value.encode("utf-8")), "utf-8") | ||||
|             if _secret_scope == "fields": | ||||
|                 secret.data[_secret_ref] = str(base64.b64encode(parse_fields_scope(secret_json, _secret_key).encode("utf-8")), "utf-8") | ||||
|                 value = parse_fields_scope(secret_json, _secret_key) | ||||
|                 if value is None: | ||||
|                     raise Exception( | ||||
|                         f"Field {_secret_key} has no value in bitwarden secret") | ||||
|                 secret.data[_secret_ref] = str(base64.b64encode( | ||||
|                     value.encode("utf-8")), "utf-8") | ||||
|     return secret | ||||
|  | ||||
|  | ||||
| @kopf.on.create('bitwarden-secret.lerentis.uploadfilter24.eu') | ||||
| def create_managed_secret(spec, name, namespace, logger, body, **kwargs): | ||||
|  | ||||
| @@ -33,7 +45,7 @@ def create_managed_secret(spec, name, namespace, logger, body, **kwargs): | ||||
|  | ||||
|     unlock_bw(logger) | ||||
|     logger.info(f"Locking up secret with ID: {id}") | ||||
|     secret_json_object = json.loads(get_secret_from_bitwarden(id)) | ||||
|     secret_json_object = get_secret_from_bitwarden(logger, id) | ||||
|  | ||||
|     api = kubernetes.client.CoreV1Api() | ||||
|  | ||||
| @@ -42,18 +54,83 @@ def create_managed_secret(spec, name, namespace, logger, body, **kwargs): | ||||
|         "managedObject": f"{namespace}/{name}" | ||||
|     } | ||||
|     secret = kubernetes.client.V1Secret() | ||||
|     secret.metadata = kubernetes.client.V1ObjectMeta(name=secret_name, annotations=annotations) | ||||
|     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 | ||||
|         namespace="{}".format(secret_namespace), | ||||
|         body=secret | ||||
|     ) | ||||
|  | ||||
|     logger.info(f"Secret {secret_namespace}/{secret_name} has been created") | ||||
|  | ||||
|  | ||||
| @kopf.on.update('bitwarden-secret.lerentis.uploadfilter24.eu') | ||||
| def my_handler(spec, old, new, diff, **_): | ||||
|     pass | ||||
| @kopf.timer('bitwarden-secret.lerentis.uploadfilter24.eu', interval=900) | ||||
| def update_managed_secret( | ||||
|         spec, | ||||
|         status, | ||||
|         name, | ||||
|         namespace, | ||||
|         logger, | ||||
|         body, | ||||
|         **kwargs): | ||||
|  | ||||
|     content_def = body['spec']['content'] | ||||
|     id = spec.get('id') | ||||
|     old_config = None | ||||
|     old_secret_name = None | ||||
|     old_secret_namespace = 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') | ||||
|     secret_name = spec.get('name') | ||||
|     secret_namespace = spec.get('namespace') | ||||
|  | ||||
|     if old_config is not None and ( | ||||
|             old_secret_name != secret_name or old_secret_namespace != secret_namespace): | ||||
|         # 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) | ||||
|     logger.info(f"Locking up secret with ID: {id}") | ||||
|     secret_json_object = get_secret_from_bitwarden(logger, id) | ||||
|  | ||||
|     api = kubernetes.client.CoreV1Api() | ||||
|  | ||||
|     annotations = { | ||||
|         "managed": "bitwarden-secret.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) | ||||
|  | ||||
|     try: | ||||
|         obj = 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: | ||||
|         logger.warn( | ||||
|             f"Could not update secret {secret_namespace}/{secret_name}!") | ||||
|  | ||||
|  | ||||
| @kopf.on.delete('bitwarden-secret.lerentis.uploadfilter24.eu') | ||||
| def delete_managed_secret(spec, name, namespace, logger, **kwargs): | ||||
| @@ -63,6 +140,8 @@ def delete_managed_secret(spec, name, namespace, logger, **kwargs): | ||||
|  | ||||
|     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}!") | ||||
|         logger.info( | ||||
|             f"Secret {secret_namespace}/{secret_name} has been deleted") | ||||
|     except BaseException: | ||||
|         logger.warn( | ||||
|             f"Could not delete secret {secret_namespace}/{secret_name}!") | ||||
|   | ||||
| @@ -2,8 +2,9 @@ import json | ||||
|  | ||||
| from utils.utils import get_secret_from_bitwarden, parse_fields_scope, parse_login_scope | ||||
|  | ||||
|  | ||||
| def bitwarden_lookup(id, scope, field): | ||||
|     _secret_json = json.loads(get_secret_from_bitwarden(id)) | ||||
|     _secret_json = get_secret_from_bitwarden(None, id) | ||||
|     if scope == "login": | ||||
|         return parse_login_scope(_secret_json, field) | ||||
|     if scope == "fields": | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import kopf | ||||
| import base64 | ||||
| import kubernetes | ||||
| import json | ||||
|  | ||||
| from utils.utils import unlock_bw | ||||
| from lookups.bitwarden_lookup import bitwarden_lookup | ||||
| @@ -11,17 +12,23 @@ lookup_func_dict = { | ||||
|     "bitwarden_lookup": bitwarden_lookup, | ||||
| } | ||||
|  | ||||
|  | ||||
| def render_template(template): | ||||
|     jinja_template = Environment(loader=BaseLoader()).from_string(template) | ||||
|     jinja_template.globals.update(lookup_func_dict) | ||||
|     return jinja_template.render() | ||||
|  | ||||
|  | ||||
| def create_template_secret(secret, filename, template): | ||||
|     secret.type = "Opaque" | ||||
|     secret.data = {} | ||||
|     secret.data[filename] = str(base64.b64encode(render_template(template).encode("utf-8")), "utf-8") | ||||
|     secret.data[filename] = str( | ||||
|         base64.b64encode( | ||||
|             render_template(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): | ||||
|  | ||||
| @@ -39,7 +46,8 @@ def create_managed_secret(spec, name, namespace, logger, body, **kwargs): | ||||
|         "managedObject": f"{namespace}/{name}" | ||||
|     } | ||||
|     secret = kubernetes.client.V1Secret() | ||||
|     secret.metadata = kubernetes.client.V1ObjectMeta(name=secret_name, annotations=annotations) | ||||
|     secret.metadata = kubernetes.client.V1ObjectMeta( | ||||
|         name=secret_name, annotations=annotations) | ||||
|     secret = create_template_secret(secret, filename, template) | ||||
|  | ||||
|     obj = api.create_namespaced_secret( | ||||
| @@ -48,9 +56,72 @@ 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') | ||||
| def my_handler(spec, old, new, diff, **_): | ||||
|     pass | ||||
| @kopf.timer('bitwarden-template.lerentis.uploadfilter24.eu', interval=900) | ||||
| def update_managed_secret( | ||||
|         spec, | ||||
|         status, | ||||
|         name, | ||||
|         namespace, | ||||
|         logger, | ||||
|         body, | ||||
|         **kwargs): | ||||
|  | ||||
|     template = spec.get('template') | ||||
|     filename = spec.get('filename') | ||||
|     secret_name = spec.get('name') | ||||
|     secret_namespace = spec.get('namespace') | ||||
|  | ||||
|     old_config = None | ||||
|     old_secret_name = None | ||||
|     old_secret_namespace = 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') | ||||
|     secret_name = spec.get('name') | ||||
|     secret_namespace = spec.get('namespace') | ||||
|  | ||||
|     if old_config is not None and ( | ||||
|             old_secret_name != secret_name or old_secret_namespace != secret_namespace): | ||||
|         # 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}" | ||||
|     } | ||||
|     secret = kubernetes.client.V1Secret() | ||||
|     secret.metadata = kubernetes.client.V1ObjectMeta( | ||||
|         name=secret_name, annotations=annotations) | ||||
|     secret = create_template_secret(secret, filename, template) | ||||
|  | ||||
|     try: | ||||
|         obj = 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: | ||||
|         logger.warn( | ||||
|             f"Could not update secret {secret_namespace}/{secret_name}!") | ||||
|  | ||||
|  | ||||
| @kopf.on.delete('bitwarden-template.lerentis.uploadfilter24.eu') | ||||
| def delete_managed_secret(spec, name, namespace, logger, **kwargs): | ||||
| @@ -60,13 +131,8 @@ def delete_managed_secret(spec, name, namespace, logger, **kwargs): | ||||
|  | ||||
|     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}!") | ||||
|  | ||||
| #if __name__ == '__main__': | ||||
| #    tpl = """ | ||||
| #        Calling the 'bitwarden_lookup' function: | ||||
| #        {{ bitwarden_lookup(2, 2) }} | ||||
| #    """ | ||||
| #    print(render_template(tpl)) | ||||
|         logger.info( | ||||
|             f"Secret {secret_namespace}/{secret_name} has been deleted") | ||||
|     except BaseException: | ||||
|         logger.warn( | ||||
|             f"Could not delete secret {secret_namespace}/{secret_name}!") | ||||
|   | ||||
| @@ -1,30 +1,53 @@ | ||||
| import os | ||||
| import json | ||||
| import subprocess | ||||
|  | ||||
|  | ||||
| class BitwardenCommandException(Exception): | ||||
|     pass | ||||
|  | ||||
| def get_secret_from_bitwarden(id): | ||||
|     return command_wrapper(command=f"get item {id}") | ||||
|  | ||||
| def get_secret_from_bitwarden(logger, id): | ||||
|     return command_wrapper(logger, command=f"get item {id}") | ||||
|  | ||||
|  | ||||
| def unlock_bw(logger): | ||||
|     token_output = command_wrapper("unlock --passwordenv BW_PASSWORD") | ||||
|     tokens = token_output.split('"')[1::2] | ||||
|     os.environ["BW_SESSION"] = tokens[1] | ||||
|     status_output = command_wrapper(logger, "status", False) | ||||
|     status = status_output['data']['template']['status'] | ||||
|     if status == 'unlocked': | ||||
|         logger.info("Already unlocked") | ||||
|         return | ||||
|     token_output = command_wrapper(logger, "unlock --passwordenv BW_PASSWORD") | ||||
|     os.environ["BW_SESSION"] = token_output["data"]["raw"] | ||||
|     logger.info("Signin successful. Session exported") | ||||
|  | ||||
| def command_wrapper(command): | ||||
|  | ||||
| def command_wrapper(logger, command, use_success: bool = True): | ||||
|     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) | ||||
|     sp = subprocess.Popen( | ||||
|         [f"bw --response {command}"], | ||||
|         stdout=subprocess.PIPE, | ||||
|         stderr=subprocess.PIPE, | ||||
|         close_fds=True, | ||||
|         shell=True, | ||||
|         env=system_env) | ||||
|     out, err = sp.communicate() | ||||
|     if err: | ||||
|         raise BitwardenCommandException(err) | ||||
|     return out.decode(encoding='UTF-8') | ||||
|     if "DEBUG" in system_env: | ||||
|         logger.info(out.decode(encoding='UTF-8')) | ||||
|     resp = json.loads(out.decode(encoding='UTF-8')) | ||||
|     if resp["success"] != None and (not use_success or (use_success and resp["success"] == True)): | ||||
|         return resp | ||||
|     logger.warn(resp) | ||||
|     return None | ||||
|  | ||||
|  | ||||
| def parse_login_scope(secret_json, key): | ||||
|     return secret_json["login"][key] | ||||
|     return secret_json["data"]["login"][key] | ||||
|  | ||||
|  | ||||
| def parse_fields_scope(secret_json, key): | ||||
|     for entry in secret_json["fields"]: | ||||
|     if "fields" not in secret_json["data"]: | ||||
|         return None | ||||
|     for entry in secret_json["data"]["fields"]: | ||||
|         if entry['name'] == key: | ||||
|             return entry['value'] | ||||
|   | ||||
		Reference in New Issue
	
	Block a user