Merge pull request #45 from nicoangelo/feature/attachments

This commit is contained in:
Tobias Trabelsi 2023-10-16 22:58:02 +02:00 committed by GitHub
commit fc37a12737
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 52 additions and 37 deletions

View File

@ -129,7 +129,7 @@ type: dockerconfigjson
## BitwardenTemplate ## BitwardenTemplate
One of the more freely defined types that can be used with this operator you can just pass a whole template: 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:
```yaml ```yaml
--- ---
@ -145,11 +145,11 @@ spec:
--- ---
api: api:
enabled: True enabled: True
key: {{ bitwarden_lookup("A Secret ID from bitwarden", "login or fields", "name of a field in bitwarden") }} key: {{ bitwarden_lookup("A Secret ID from bitwarden", "login or fields or attachment", "name of a field in bitwarden") }}
allowCrossOrigin: false allowCrossOrigin: false
apps: apps:
"some.app.identifier:some_version": "some.app.identifier:some_version":
pubkey: {{ bitwarden_lookup("A Secret ID from bitwarden", "login or fields", "name of a field in bitwarden") }} pubkey: {{ bitwarden_lookup("A Secret ID from bitwarden", "login or fields or attachment", "name of a field in bitwarden") }}
enabled: true enabled: true
``` ```
@ -169,7 +169,15 @@ metadata:
type: Opaque 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. 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.
## Configurations parameters ## Configurations parameters

View File

@ -4,9 +4,9 @@ description: Deploy the Bitwarden CRD Operator
type: application type: application
version: "v0.9.0" version: "v0.10.0"
appVersion: "0.8.0" appVersion: "0.9.0"
keywords: keywords:
- operator - operator
@ -89,18 +89,14 @@ annotations:
allowCrossOrigin: false allowCrossOrigin: false
apps: apps:
"some.app.identifier:some_version": "some.app.identifier:some_version":
pubkey: {{ bitwarden_lookup("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "fields", "public_key") }} pubkey: {{ bitwarden_lookup("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "attachment", "public_key") }}
enabled: true enabled: true
artifacthub.io/license: MIT artifacthub.io/license: MIT
artifacthub.io/operator: "true" artifacthub.io/operator: "true"
artifacthub.io/containsSecurityUpdates: "false" artifacthub.io/containsSecurityUpdates: "false"
artifacthub.io/changes: | artifacthub.io/changes: |
- kind: changed
description: "Unified scheduled none crd related operations (bw sync and login)"
- kind: added - kind: added
description: "Added relogin interval which can be finetuned with env `BW_RELOGIN_INTERVAL`. defaults to 3600 seconds" description: "Added attachment lookup to bitwarden_lookup in BitwardenTemplate CRD"
- kind: chanced
description: "Updated python to 3.11.6-r0"
artifacthub.io/images: | artifacthub.io/images: |
- name: bitwarden-crd-operator - name: bitwarden-crd-operator
image: ghcr.io/lerentis/bitwarden-crd-operator:0.8.0 image: ghcr.io/lerentis/bitwarden-crd-operator:0.9.0

View File

@ -1,11 +1,16 @@
import json from utils.utils import get_secret_from_bitwarden, get_attachment, parse_fields_scope, parse_login_scope
from utils.utils import get_secret_from_bitwarden, parse_fields_scope, parse_login_scope
def bitwarden_lookup(id, scope, field): class BitwardenLookupHandler:
_secret_json = get_secret_from_bitwarden(None, id)
if scope == "login": def __init__(self, logger) -> None:
return parse_login_scope(_secret_json, field) self.logger = logger
if scope == "fields":
return parse_fields_scope(_secret_json, field) 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)
if scope == "login":
return parse_login_scope(_secret_json, field)
if scope == "fields":
return parse_fields_scope(_secret_json, field)

View File

@ -4,27 +4,24 @@ import kubernetes
import json import json
from utils.utils import unlock_bw, bw_sync_interval from utils.utils import unlock_bw, bw_sync_interval
from lookups.bitwarden_lookup import bitwarden_lookup from lookups.bitwarden_lookup import BitwardenLookupHandler
from jinja2 import Environment, BaseLoader from jinja2 import Environment, BaseLoader
lookup_func_dict = { def render_template(logger, template):
"bitwarden_lookup": bitwarden_lookup,
}
def render_template(template):
jinja_template = Environment(loader=BaseLoader()).from_string(template) jinja_template = Environment(loader=BaseLoader()).from_string(template)
jinja_template.globals.update(lookup_func_dict) jinja_template.globals.update({
"bitwarden_lookup": BitwardenLookupHandler(logger).bitwarden_lookup,
})
return jinja_template.render() return jinja_template.render()
def create_template_secret(secret, filename, template): def create_template_secret(logger, secret, filename, template):
secret.type = "Opaque" secret.type = "Opaque"
secret.data = {} secret.data = {}
secret.data[filename] = str( secret.data[filename] = str(
base64.b64encode( base64.b64encode(
render_template(template).encode("utf-8")), render_template(logger, template).encode("utf-8")),
"utf-8") "utf-8")
return secret return secret
@ -48,7 +45,7 @@ def create_managed_secret(spec, name, namespace, logger, body, **kwargs):
secret = kubernetes.client.V1Secret() secret = kubernetes.client.V1Secret()
secret.metadata = kubernetes.client.V1ObjectMeta( secret.metadata = kubernetes.client.V1ObjectMeta(
name=secret_name, annotations=annotations) name=secret_name, annotations=annotations)
secret = create_template_secret(secret, filename, template) secret = create_template_secret(logger, secret, filename, template)
obj = api.create_namespaced_secret( obj = api.create_namespaced_secret(
secret_namespace, secret secret_namespace, secret
@ -109,7 +106,7 @@ def update_managed_secret(
secret = kubernetes.client.V1Secret() secret = kubernetes.client.V1Secret()
secret.metadata = kubernetes.client.V1ObjectMeta( secret.metadata = kubernetes.client.V1ObjectMeta(
name=secret_name, annotations=annotations) name=secret_name, annotations=annotations)
secret = create_template_secret(secret, filename, template) secret = create_template_secret(logger, secret, filename, template)
try: try:
obj = api.replace_namespaced_secret( obj = api.replace_namespaced_secret(

View File

@ -39,6 +39,10 @@ def sync_bw(logger, force=False):
logger.info(f"Sync successful {status_output}") 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): def unlock_bw(logger):
status_output = command_wrapper(logger, "status", False) status_output = command_wrapper(logger, "status", False)
status = status_output['data']['template']['status'] status = status_output['data']['template']['status']
@ -50,17 +54,22 @@ def unlock_bw(logger):
logger.info("Signin successful. Session exported") logger.info("Signin successful. Session exported")
def command_wrapper(logger, command, use_success: bool = True): def command_wrapper(logger, command, use_success: bool = True, raw: bool = False):
system_env = dict(os.environ) system_env = dict(os.environ)
response_flag = "--raw" if raw else "--response"
sp = subprocess.Popen( sp = subprocess.Popen(
[f"bw --response {command}"], [f"bw {response_flag} {command}"],
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
close_fds=True, close_fds=True,
shell=True, shell=True,
env=system_env) env=system_env)
out, err = sp.communicate() out, err = sp.communicate()
if err:
logger.warn(err)
return None
if raw:
return out.decode(encoding='UTF-8')
if "DEBUG" in system_env: if "DEBUG" in system_env:
logger.info(out.decode(encoding='UTF-8')) logger.info(out.decode(encoding='UTF-8'))
resp = json.loads(out.decode(encoding='UTF-8')) resp = json.loads(out.decode(encoding='UTF-8'))