Compare commits

..

No commits in common. "main" and "v0.6.2" have entirely different histories.
main ... v0.6.2

23 changed files with 180 additions and 889 deletions

View File

@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
fetch-depth: 0
@ -24,39 +24,39 @@ jobs:
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Install Helm
uses: azure/setup-helm@v4
uses: azure/setup-helm@v3
with:
version: v3.10.0
- name: Run chart-releaser
uses: helm/chart-releaser-action@v1.6.0
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.44.3
uses: mikefarah/yq@v4.33.3
id: app_version
with:
cmd: yq '.appVersion' charts/bitwarden-crd-operator/Chart.yaml
- name: "GHCR Login"
uses: docker/login-action@v3
uses: docker/login-action@v2
with:
registry: ghcr.io
username: lerentis
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v2
- name: "GHCR Build and Push"
id: docker_build
uses: docker/build-push-action@v6
uses: docker/build-push-action@v4
with:
push: true
platforms: linux/amd64,linux/arm64
@ -77,7 +77,7 @@ jobs:
uses: WyriHaximus/github-action-get-previous-tag@v1
- name: Download SBOM from github action
uses: actions/download-artifact@v4
uses: actions/download-artifact@v3
with:
name: ${{ env.ANCHORE_SBOM_ACTION_PRIOR_ARTIFACT }}

View File

@ -7,22 +7,22 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Helm
uses: azure/setup-helm@v4
uses: azure/setup-helm@v3
with:
version: v3.11.2
- uses: actions/setup-python@v5
- uses: actions/setup-python@v4
with:
python-version: '3.9'
check-latest: true
- name: Set up chart-testing
uses: helm/chart-testing-action@v2.6.1
uses: helm/chart-testing-action@v2.4.0
- name: Run chart-testing (list-changed)
id: list-changed
@ -36,31 +36,20 @@ jobs:
if: steps.list-changed.outputs.changed == 'true'
run: ct lint --target-branch ${{ github.event.repository.default_branch }}
- name: Install ah cli
run: |
export AH_VERSION=1.17.0
curl -LO https://github.com/artifacthub/hub/releases/download/v${AH_VERSION}/ah_${AH_VERSION}_linux_amd64.tar.gz
tar -xf ah_${AH_VERSION}_linux_amd64.tar.gz
chmod +x ./ah
sudo mv ./ah /usr/bin/ah
rm LICENSE
- name: ah lint
run: |
ah lint
pr-build:
runs-on: ubuntu-latest
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v2
- name: GHCR Build
- name: "GHCR Build"
id: docker_build
uses: docker/build-push-action@v6
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
View File

@ -166,5 +166,3 @@ lib
lib64
myvalues.yaml
.vscode

View File

@ -1,29 +1,42 @@
FROM alpine:3.20.3
FROM alpine:3.17.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.6-r0
ARG PIP_VERSION=24.0-r2
ARG GCOMPAT_VERSION=1.1.0-r4
ARG LIBCRYPTO_VERSION=3.3.2-r0
ARG BW_VERSION=2024.7.2
ARG NODE_VERSION=20.15.1-r0
ARG PYTHON_VERSION=3.10.11-r0
ARG PIP_VERSION=22.3.1-r1
ARG GCOMPAT_VERSION=1.1.0-r0
ARG LIBCRYPTO_VERSION=3.0.8-r4
ARG BW_VERSION=2023.1.0
COPY requirements.txt /requirements.txt
RUN set -eux; \
apk update; \
apk del nodejs-current; \
apk add nodejs=${NODE_VERSION} npm; \
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; \
;; \
*) \
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; \
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 --break-system-packages; \
apk add gcc musl-dev libstdc++ gcompat=${GCOMPAT_VERSION} python3=${PYTHON_VERSION} py3-pip=${PIP_VERSION} libcrypto3=${LIBCRYPTO_VERSION} libssl3=${LIBCRYPTO_VERSION}; \
pip install -r /requirements.txt --no-warn-script-location; \
rm /requirements.txt; \
apk del --purge gcc musl-dev libstdc++;

View File

@ -1,27 +0,0 @@
deployment_name ?= bitwarden-crd-operator
namespace ?= bitwarden-crd-operator
label_filter = -l app.kubernetes.io/instance=bitwarden-crd-operator -l app.kubernetes.io/name=bitwarden-crd-operator
create-namespace:
kubectl create namespace ${namespace}
dev:
skaffold dev -n ${namespace}
run:
skaffold run -n ${namespace}
pods:
kubectl -n ${namespace} get pods
desc-pods:
kubectl -n ${namespace} describe pod ${label_filter}
delete-pods-force:
kubectl -n ${namespace} delete pod ${label_filter} --force
exec:
kubectl -n ${namespace} exec -it deployment/${deployment_name} -- sh
logs:
kubectl -n ${namespace} logs -f --tail 30 deployment/${deployment_name}

View File

@ -56,29 +56,23 @@ And you are set to create your first secret using this operator. For that you ne
```yaml
---
apiVersion: "lerentis.uploadfilter24.eu/v1beta8"
apiVersion: "lerentis.uploadfilter24.eu/v1beta4"
kind: BitwardenSecret
metadata:
name: name-of-your-management-object
spec:
content:
- element:
secretName: nameOfTheFieldInBitwarden # for example username or filename
secretName: nameOfTheFieldInBitwarden # for example username
secretRef: nameOfTheKeyInTheSecretToBeCreated
secretScope: login # for custom entries on bitwarden use 'fields, for attachments use attachment'
secretScope: login # for custom entries on bitwarden use 'fields'
- element:
secretName: nameOfAnotherFieldInBitwarden # for example password or filename
secretName: nameOfAnotherFieldInBitwarden # for example password
secretRef: nameOfAnotherKeyInTheSecretToBeCreated
secretScope: login # for custom entries on bitwarden use 'fields, for attachments use attachment'
secretScope: login # for custom entries on bitwarden use 'fields'
id: "A Secret ID from bitwarden"
name: "Name of the secret to be created"
secretType: # Optional (Default: Opaque)
namespace: "Namespace of the secret to be created"
labels: # Optional
key: value
annotations: # Optional
key: value
```
The ID can be extracted from the browser when you open a item the ID is in the URL. The resulting secret looks something like this:
@ -93,8 +87,6 @@ metadata:
annotations:
managed: bitwarden-secrets.lerentis.uploadfilter24.eu
managedObject: bw-operator/test
labels:
key: value
name: name-of-your-management-object
namespace: default
type: Opaque
@ -106,7 +98,7 @@ For managing registry credentials, or pull secrets, you can create another kind
```yaml
---
apiVersion: "lerentis.uploadfilter24.eu/v1beta8"
apiVersion: "lerentis.uploadfilter24.eu/v1beta4"
kind: RegistryCredential
metadata:
name: name-of-your-management-object
@ -117,10 +109,6 @@ spec:
id: "A Secret ID from bitwarden"
name: "Name of the secret to be created"
namespace: "Namespace of the secret to be created"
labels: # Optional
key: value
annotations: # Optional
key: value
```
The resulting secret looks something like this:
@ -134,8 +122,6 @@ metadata:
annotations:
managed: bitwarden-secrets.lerentis.uploadfilter24.eu
managedObject: bw-operator/test
labels:
key: value
name: name-of-your-management-object
namespace: default
type: dockerconfigjson
@ -143,47 +129,28 @@ type: dockerconfigjson
## BitwardenTemplate
One of the more freely defined types that can be used with this operator you can just pass a whole template. Also the lookup function `bitwarden_lookup` is available to reference parts of the secret:
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/v1beta8"
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"
secretType: # Optional (Default: Opaque)
namespace: "Namespace of the secret to be created"
labels: # Optional
key: value
annotations: # Optional
key: value
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") }}
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 or attachment", "name of a field in bitwarden") }}
pubkey: {{ bitwarden_lookup("A Secret ID from bitwarden", "login or fields", "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:
@ -197,25 +164,17 @@ metadata:
annotations:
managed: bitwarden-template.lerentis.uploadfilter24.eu
managedObject: namespace/name-of-your-management-object
labels:
key: value
name: Name of the secret to be created
namespace: Namespace of the secret to be created
type: Opaque
```
The signature of `bitwarden_lookup` is `(item_id, scope, field)`:
- `item_id`: The item ID of the secret in Bitwarden
- `scope`: one of `login`, `fields` or `attachment`
- `field`:
- when `scope` is `login`: either `username` or `password`
- when `scope` is `fields`: the name of a custom field
- when `scope` is `attachment`: the filename of a file attached to the item
please note that the rendering engine for this template is jinja2, with an addition of a custom `bitwarden_lookup` function, so there are more possibilities to inject here.
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
## Configurations parameters
The operator uses the bitwarden cli in the background and does not communicate to the api directly. The cli mirrors the credential store locally but doesn't sync it on every get request. Instead it will sync each secret every 15 minutes (900 seconds). You can adjust the interval by setting `BW_SYNC_INTERVAL` in the values. If your secrets update very very frequently, you can force the operator to do a sync before each get by setting `BW_FORCE_SYNC="true"`. You might run into rate limits if you do this too frequent.
Additionally the bitwarden cli session may expire at some time. In order to create a new session, the login command is triggered from time to time. In what interval exactly can be configured with the env `BW_RELOGIN_INTERVAL` which defaults to `3600` seconds.
- [ ] 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

View File

@ -4,9 +4,9 @@ description: Deploy the Bitwarden CRD Operator
type: application
version: "v0.15.0"
version: "v0.7.2"
appVersion: "0.14.0"
appVersion: "0.6.2"
keywords:
- operator
@ -20,7 +20,7 @@ home: https://lerentis.github.io/bitwarden-crd-operator/
sources:
- https://github.com/Lerentis/bitwarden-crd-operator
kubeVersion: ">= 1.28.0-0"
kubeVersion: ">= 1.23.0-0"
maintainers:
- name: lerentis
@ -32,22 +32,22 @@ annotations:
url: https://github.com/Lerentis/bitwarden-crd-operator
artifacthub.io/crds: |
- kind: BitwardenSecret
version: v1beta8
version: v1beta4
name: bitwarden-secret
displayName: Bitwarden Secret
description: Management Object to create secrets from bitwarden
- kind: RegistryCredential
version: v1beta8
version: v1beta4
name: registry-credential
displayName: Regestry Credentials
description: Management Object to create regestry secrets from bitwarden
- kind: BitwardenTemplate
version: v1beta8
version: v1beta1
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/v1beta8
- apiVersion: lerentis.uploadfilter24.eu/v1beta4
kind: BitwardenSecret
metadata:
name: test
@ -61,13 +61,8 @@ annotations:
secretRef: passwordOfUser
id: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
name: "test-secret"
secretType: Obaque #Optional
namespace: "default"
labels:
key: value
annotations:
key: value
- apiVersion: lerentis.uploadfilter24.eu/v1beta8
- apiVersion: lerentis.uploadfilter24.eu/v1beta4
kind: RegistryCredential
metadata:
name: test
@ -78,25 +73,14 @@ annotations:
id: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
name: "test-regcred"
namespace: "default"
labels:
key: value
annotations:
key: value
- apiVersion: "lerentis.uploadfilter24.eu/v1beta8"
- apiVersion: "lerentis.uploadfilter24.eu/v1beta4"
kind: BitwardenTemplate
metadata:
name: test
spec:
name: "test-regcred"
secretType: Obaque #Optional
namespace: "default"
labels:
key: value
annotations:
key: value
content:
- element:
filename: "config.yaml"
name: "test-regcred"
namespace: "default"
template: |
---
api:
@ -105,21 +89,14 @@ annotations:
allowCrossOrigin: false
apps:
"some.app.identifier:some_version":
pubkey: {{ bitwarden_lookup("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "attachment", "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: changed
description: "BitwardenTemplate can now handle multiple files"
- kind: changed
description: "Removed long deprecated versions"
- 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"
- kind: fixed
description: "Fallback to npm package on ARM as native bitwarden cli package is not compatible with ARM"
artifacthub.io/images: |
- name: bitwarden-crd-operator
image: ghcr.io/lerentis/bitwarden-crd-operator:0.14.0
image: ghcr.io/lerentis/bitwarden-crd-operator:0.6.2

View File

@ -13,52 +13,7 @@ spec:
shortNames:
- bws
versions:
- name: v1beta7
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
secretType:
type: string
labels:
type: object
x-kubernetes-preserve-unknown-fields: true
annotations:
type: object
x-kubernetes-preserve-unknown-fields: true
required:
- id
- namespace
- name
- name: v1beta8
- name: v1beta4
served: true
storage: true
schema:
@ -90,14 +45,6 @@ spec:
type: string
name:
type: string
secretType:
type: string
labels:
type: object
x-kubernetes-preserve-unknown-fields: true
annotations:
type: object
x-kubernetes-preserve-unknown-fields: true
required:
- id
- namespace

View File

@ -13,39 +13,7 @@ spec:
shortNames:
- bwt
versions:
- name: v1beta7
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
secretType:
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: v1beta8
- name: v1beta4
served: true
storage: true
schema:
@ -53,35 +21,18 @@ spec:
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
namespace:
type: string
name:
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

View File

@ -13,43 +13,7 @@ spec:
shortNames:
- rgc
versions:
- name: v1beta7
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
annotations:
type: object
x-kubernetes-preserve-unknown-fields: true
required:
- id
- namespace
- name
- usernameRef
- passwordRef
- registry
- name: v1beta8
- name: v1beta4
served: true
storage: true
schema:
@ -71,12 +35,6 @@ spec:
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

View File

@ -8,8 +8,6 @@ spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
strategy:
type: {{ .Values.deploymentStrategy }}
selector:
matchLabels:
{{- include "bitwarden-crd-operator.selectorLabels" . | nindent 6 }}
@ -52,20 +50,10 @@ spec:
httpGet:
path: /healthz
port: http
failureThreshold: {{ .Values.livenessProbe.failureThreshold }}
initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
successThreshold: {{ .Values.livenessProbe.successThreshold }}
timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }}
readinessProbe:
httpGet:
path: /healthz
port: http
failureThreshold: {{ .Values.readinessProbe.failureThreshold }}
initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
successThreshold: {{ .Values.readinessProbe.successThreshold }}
timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}

View File

@ -14,13 +14,7 @@ imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
deploymentStrategy: "Recreate"
# env:
# - name: BW_FORCE_SYNC
# value: "false"
# - name: BW_SYNC_INTERVAL
# value: "900"
# - name: BW_HOST
# value: "define_it"
# - name: BW_CLIENTID
@ -29,8 +23,6 @@ deploymentStrategy: "Recreate"
# value: "define_it"
# - name: BW_PASSWORD
# value: "define_id"
## - name: BW_RELOGIN_INTERVAL
## value: "3600"
externalConfigSecret:
enabled: false
@ -59,20 +51,6 @@ securityContext: {}
# runAsNonRoot: true
# runAsUser: 1000
readinessProbe:
failureThreshold: 3
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
livenessProbe:
failureThreshold: 3
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little

View File

@ -1,9 +1,8 @@
---
apiVersion: "lerentis.uploadfilter24.eu/v1beta8"
apiVersion: "lerentis.uploadfilter24.eu/v1beta4"
kind: BitwardenSecret
metadata:
name: test
namespace: default
spec:
content:
- element:
@ -16,15 +15,9 @@ spec:
secretScope: login
id: "88781348-c81c-4367-9801-550360c21295"
name: "test-secret"
secretType: Opaque
namespace: "default"
labels:
key: value
app: example-app
annotations:
custom.annotation: is-used
---
apiVersion: "lerentis.uploadfilter24.eu/v1beta8"
apiVersion: "lerentis.uploadfilter24.eu/v1beta4"
kind: BitwardenSecret
metadata:
name: test-scope

View File

@ -1,5 +1,5 @@
---
apiVersion: "lerentis.uploadfilter24.eu/v1beta8"
apiVersion: "lerentis.uploadfilter24.eu/v1beta4"
kind: RegistryCredential
metadata:
name: test
@ -10,8 +10,3 @@ spec:
id: "3b249ec7-9ce7-440a-9558-f34f3ab10680"
name: "test-regcred"
namespace: "default"
labels:
namespace: default
tenant: example-team
annotations:
custom.annotation: is-used

View File

@ -1,19 +1,12 @@
---
apiVersion: "lerentis.uploadfilter24.eu/v1beta8"
apiVersion: "lerentis.uploadfilter24.eu/v1beta4"
kind: BitwardenTemplate
metadata:
name: test
spec:
filename: "config.yaml"
name: "test-template"
namespace: "default"
labels:
key: value
app: example-app
annotations:
custom.annotation: is-used
content:
- element:
filename: config.yaml
template: |
---
api:
@ -24,15 +17,3 @@ spec:
"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

View File

@ -1,4 +1,3 @@
kopf==1.37.2
kubernetes==30.1.0
Jinja2==3.1.4
schedule==1.2.2
kopf==1.36.1
kubernetes==26.1.0
Jinja2==3.1.2

View File

@ -1,57 +0,0 @@
apiVersion: skaffold/v4beta9
kind: Config
metadata:
name: bitwarden-crd-operator
build:
tagPolicy:
sha256: {}
artifacts:
- image: ghcr.io/lerentis/bitwarden-crd-operator
docker:
dockerfile: Dockerfile
deploy:
helm:
releases:
- name: bitwarden-crd-operator
chartPath: charts/bitwarden-crd-operator
valuesFiles:
- ./charts/bitwarden-crd-operator/myvalues.yaml
setValueTemplates:
image.repository: "{{.IMAGE_REPO_ghcr_io_lerentis_bitwarden_crd_operator}}"
image.tag: "{{.IMAGE_TAG_ghcr_io_lerentis_bitwarden_crd_operator}}@{{.IMAGE_DIGEST_ghcr_io_lerentis_bitwarden_crd_operator}}"
hooks:
after:
- host:
command:
- kubectl
- apply
- -f
- ./example*.yaml
- host:
command:
- sleep
- '5'
- host:
command:
- kubectl
- get
- secret
- test-regcred
- host:
command:
- kubectl
- get
- secret
- test-scope
- host:
command:
- kubectl
- get
- secret
- test-secret
- host:
command:
- kubectl
- get
- secret
- test-template

View File

@ -1,12 +1,11 @@
#!/usr/bin/env python3
import os
import kopf
import schedule
import time
import threading
from utils.utils import command_wrapper, unlock_bw, sync_bw
from utils.utils import command_wrapper, unlock_bw
@kopf.on.startup()
def bitwarden_signin(logger, **kwargs):
if 'BW_HOST' in os.environ:
try:
@ -19,29 +18,3 @@ def bitwarden_signin(logger, **kwargs):
logger.info("BW_HOST not set. Assuming SaaS installation")
command_wrapper(logger, "login --apikey")
unlock_bw(logger)
def run_continuously(interval=30):
cease_continuous_run = threading.Event()
class ScheduleThread(threading.Thread):
@classmethod
def run(cls):
while not cease_continuous_run.is_set():
schedule.run_pending()
time.sleep(interval)
continuous_thread = ScheduleThread()
continuous_thread.start()
return cease_continuous_run
@kopf.on.startup()
def load_schedules(logger, **kwargs):
bitwarden_signin(logger)
logger.info("Loading schedules")
bw_relogin_interval = float(os.environ.get('BW_RELOGIN_INTERVAL', 3600))
bw_sync_interval = float(os.environ.get('BW_SYNC_INTERVAL', 900))
schedule.every(bw_relogin_interval).seconds.do(bitwarden_signin, logger=logger)
logger.info(f"relogin scheduled every {bw_relogin_interval} seconds")
schedule.every(bw_sync_interval).seconds.do(sync_bw, logger=logger)
logger.info(f"sync scheduled every {bw_relogin_interval} seconds")
stop_run_continuously = run_continuously()

View File

@ -3,7 +3,7 @@ import kubernetes
import base64
import json
from utils.utils import unlock_bw, get_secret_from_bitwarden, bw_sync_interval
from utils.utils import unlock_bw, get_secret_from_bitwarden
def create_dockerlogin(
@ -13,7 +13,7 @@ def create_dockerlogin(
username_ref,
password_ref,
registry):
secret.type = "kubernetes.io/dockerconfigjson"
secret.type = "dockerconfigjson"
secret.data = {}
auths_dict = {}
registry_dict = {}
@ -26,8 +26,7 @@ def create_dockerlogin(
base64.b64encode(
f"{_username}:{_password}".encode("utf-8")),
"utf-8")
reg_auth_dict["username"] = _username
reg_auth_dict["password"] = _password
reg_auth_dict["auth"] = cred_field
registry_dict[registry] = reg_auth_dict
auths_dict["auths"] = registry_dict
@ -44,8 +43,6 @@ def create_managed_registry_secret(spec, name, namespace, logger, **kwargs):
id = spec.get('id')
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,16 +54,9 @@ def create_managed_registry_secret(spec, name, namespace, logger, **kwargs):
"managed": "registry-credential.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)
name=secret_name, annotations=annotations)
secret = create_dockerlogin(
logger,
secret,
@ -75,12 +65,7 @@ def create_managed_registry_secret(spec, name, namespace, logger, **kwargs):
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(
obj = api.create_namespaced_secret(
secret_namespace, secret
)
@ -89,7 +74,7 @@ def create_managed_registry_secret(spec, name, namespace, logger, **kwargs):
@kopf.on.update('registry-credential.lerentis.uploadfilter24.eu')
@kopf.timer('registry-credential.lerentis.uploadfilter24.eu', interval=bw_sync_interval)
@kopf.timer('registry-credential.lerentis.uploadfilter24.eu', interval=900)
def update_managed_registry_secret(
spec,
status,
@ -105,8 +90,6 @@ def update_managed_registry_secret(
id = spec.get('id')
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
@ -143,16 +126,9 @@ def update_managed_registry_secret(
"managed": "registry-credential.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)
name=secret_name, annotations=annotations)
secret = create_dockerlogin(
logger,
secret,
@ -160,25 +136,16 @@ 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(
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 as e:
except BaseException:
logger.warn(
f"Could not update secret {secret_namespace}/{secret_name}!")
logger.warn(
f"Exception: {e}"
)
@kopf.on.delete('registry-credential.lerentis.uploadfilter24.eu')

View File

@ -3,9 +3,11 @@ import kubernetes
import base64
import json
from utils.utils import unlock_bw, get_secret_from_bitwarden, parse_login_scope, parse_fields_scope, get_attachment, bw_sync_interval
from utils.utils import unlock_bw, get_secret_from_bitwarden, parse_login_scope, parse_fields_scope
def create_kv(logger, id, secret, secret_json, content_def):
def create_kv(secret, secret_json, content_def):
secret.type = "Opaque"
secret.data = {}
for eleml in content_def:
for k, elem in eleml.items():
@ -30,13 +32,6 @@ def create_kv(logger, id, secret, secret_json, content_def):
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 == "attachment":
value = get_attachment(logger, id, _secret_key)
if value is None:
raise Exception(
f"Attachment {_secret_key} has no value in bitwarden secret")
secret.data[_secret_ref] = str(base64.b64encode(
value.encode("utf-8")), "utf-8")
return secret
@ -47,9 +42,6 @@ def create_managed_secret(spec, name, namespace, logger, body, **kwargs):
id = spec.get('id')
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')
unlock_bw(logger)
logger.info(f"Locking up secret with ID: {id}")
@ -61,28 +53,12 @@ def create_managed_secret(spec, name, namespace, logger, body, **kwargs):
"managed": "bitwarden-secret.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_kv(logger, id, secret, secret_json_object, content_def)
name=secret_name, annotations=annotations)
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(
obj = api.create_namespaced_secret(
namespace="{}".format(secret_namespace),
body=secret
)
@ -91,7 +67,7 @@ def create_managed_secret(spec, name, namespace, logger, body, **kwargs):
@kopf.on.update('bitwarden-secret.lerentis.uploadfilter24.eu')
@kopf.timer('bitwarden-secret.lerentis.uploadfilter24.eu', interval=bw_sync_interval)
@kopf.timer('bitwarden-secret.lerentis.uploadfilter24.eu', interval=900)
def update_managed_secret(
spec,
status,
@ -106,30 +82,19 @@ def update_managed_secret(
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')
labels = spec.get('labels')
custom_annotations = spec.get('annotations')
custom_secret_type = spec.get('secretType')
if not custom_secret_type:
custom_secret_type = 'Opaque'
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):
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, namespace or type changed, let's recreate it")
logger.info("Secret name or namespace changed, let's recreate it")
delete_managed_secret(
old_config['spec'],
name,
@ -150,36 +115,21 @@ def update_managed_secret(
"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_kv(logger, id, 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)
name=secret_name, annotations=annotations)
secret = create_kv(secret, secret_json_object, content_def)
try:
api.replace_namespaced_secret(
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 as e:
except BaseException:
logger.warn(
f"Could not update secret {secret_namespace}/{secret_name}!")
logger.warn(
f"Exception: {e}"
)
@kopf.on.delete('bitwarden-secret.lerentis.uploadfilter24.eu')

View File

@ -1,15 +1,10 @@
from utils.utils import get_secret_from_bitwarden, get_attachment, parse_fields_scope, parse_login_scope
import json
from utils.utils import get_secret_from_bitwarden, parse_fields_scope, parse_login_scope
class BitwardenLookupHandler:
def __init__(self, logger) -> None:
self.logger = logger
def bitwarden_lookup(self, id, scope, field):
if scope == "attachment":
return get_attachment(self.logger, id, field)
_secret_json = get_secret_from_bitwarden(self.logger, id)
def bitwarden_lookup(id, scope, field):
_secret_json = get_secret_from_bitwarden(None, id)
if scope == "login":
return parse_login_scope(_secret_json, field)
if scope == "fields":

View File

@ -3,101 +3,39 @@ import base64
import kubernetes
import json
from utils.utils import unlock_bw, bw_sync_interval
from lookups.bitwarden_lookup import BitwardenLookupHandler
from utils.utils import unlock_bw
from lookups.bitwarden_lookup import bitwarden_lookup
from jinja2 import Environment, BaseLoader
def render_template(logger, template):
lookup_func_dict = {
"bitwarden_lookup": bitwarden_lookup,
}
def render_template(template):
jinja_template = Environment(loader=BaseLoader()).from_string(template)
jinja_template.globals.update({
"bitwarden_lookup": BitwardenLookupHandler(logger).bitwarden_lookup,
})
jinja_template.globals.update(lookup_func_dict)
return jinja_template.render()
def create_template_secret(logger, secret, filename, template):
def create_template_secret(secret, filename, template):
secret.type = "Opaque"
secret.data = {}
secret.data[filename] = str(
base64.b64encode(
render_template(logger, template).encode("utf-8")),
"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")),
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):
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')
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')
unlock_bw(logger)
@ -107,127 +45,20 @@ def create_beta7_secret(spec, name, namespace, logger, body, **kwargs):
"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_secret(logger, secret, filename, template)
name=secret_name, annotations=annotations)
secret = create_template_secret(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(
obj = api.create_namespaced_secret(
secret_namespace, secret
)
logger.info(f"Secret {secret_namespace}/{secret_name} has been created")
def update_beta7_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')
labels = spec.get('labels')
custom_annotations = spec.get('annotations')
custom_secret_type = spec.get('secretType')
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_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,
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.update('bitwarden-template.lerentis.uploadfilter24.eu')
@kopf.timer('bitwarden-template.lerentis.uploadfilter24.eu', interval=bw_sync_interval)
@kopf.timer('bitwarden-template.lerentis.uploadfilter24.eu', interval=900)
def update_managed_secret(
spec,
status,
@ -238,36 +69,23 @@ def update_managed_secret(
**kwargs):
template = spec.get('template')
if template is not None:
update_beta7_secret(spec, status, name, namespace, logger, body, **kwargs)
filename = spec.get('filename')
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):
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")
@ -288,37 +106,21 @@ def update_managed_secret(
"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)
name=secret_name, annotations=annotations)
secret = create_template_secret(secret, filename, template)
try:
api.replace_namespaced_secret(
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 as e:
except BaseException:
logger.warn(
f"Could not update secret {secret_namespace}/{secret_name}!")
logger.warn(
f"Exception: {e}"
)
@kopf.on.delete('bitwarden-template.lerentis.uploadfilter24.eu')

View File

@ -1,48 +1,16 @@
import os
import json
import subprocess
import distutils
bw_sync_interval = float(os.environ.get(
'BW_SYNC_INTERVAL', 900))
class BitwardenCommandException(Exception):
pass
def get_secret_from_bitwarden(logger, id, force_sync=False):
sync_bw(logger, force=force_sync)
def get_secret_from_bitwarden(logger, id):
return command_wrapper(logger, command=f"get item {id}")
def sync_bw(logger, force=False):
def _sync(logger):
status_output = command_wrapper(logger, command=f"sync")
logger.info(f"Sync successful {status_output}")
return
if force:
_sync(logger)
return
global_force_sync = bool(distutils.util.strtobool(
os.environ.get('BW_FORCE_SYNC', "false")))
if global_force_sync:
logger.debug("Running forced sync")
status_output = _sync(logger)
logger.info(f"Sync successful {status_output}")
else:
logger.debug("Running scheduled sync")
status_output = _sync(logger)
logger.info(f"Sync successful {status_output}")
def get_attachment(logger, id, name):
return command_wrapper(logger, command=f"get attachment {name} --itemid {id}", raw=True)
def unlock_bw(logger):
status_output = command_wrapper(logger, "status", False)
status = status_output['data']['template']['status']
@ -54,25 +22,19 @@ def unlock_bw(logger):
logger.info("Signin successful. Session exported")
def command_wrapper(logger, command, use_success: bool = True, raw: bool = False):
def command_wrapper(logger, command, use_success: bool = True):
system_env = dict(os.environ)
response_flag = "--raw" if raw else "--response"
sp = subprocess.Popen(
[f"bw {response_flag} {command}"],
[f"bw --response {command}"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
close_fds=True,
shell=True,
env=system_env)
out, err = sp.communicate()
if err:
logger.warn(err)
return None
if raw:
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 "DEBUG" in system_env:
logger.info(resp)
if resp["success"] != None and (not use_success or (use_success and resp["success"] == True)):
return resp
logger.warn(resp)