# Bitwarden Secrets Manager Provider This section describes how to set up the Bitwarden Secrets Manager provider for External Secrets Operator (ESO). !!! note [Bitwarden Secrets Manager](https://bitwarden.com/products/secrets-manager/) enables developers, DevOps, and cybersecurity teams to centrally store, manage, and deploy secrets at scale. This is different from [Bitwarden Password Manager](https://bitwarden.com/products/personal/). To integrate with Bitwarden **Password Manager**, reference the [example documentation](../examples/bitwarden.md). ## Prerequisites In order for the Bitwarden provider to work, we need a second service. This service is the [Bitwarden SDK Server](https://github.com/external-secrets/bitwarden-sdk-server). The Bitwarden SDK is Rust based and requires CGO enabled. In order to not restrict the capabilities of ESO, and the image size ( the bitwarden Rust SDK libraries are over 150MB in size ) it has been decided to create a soft wrapper around the SDK that runs as a separate service providing ESO with a light REST API to pull secrets through. ### Bitwarden SDK server The server itself can be installed together with ESO. The ESO Helm Chart packages this service as a dependency. The Bitwarden SDK Server's full name is hardcoded to bitwarden-sdk-server. This is so that the exposed service URL gets a determinable endpoint. In order to install the service install ESO with the following helm directive: ``` helm install external-secrets \ external-secrets/external-secrets \ -n external-secrets \ --create-namespace \ --set bitwarden-sdk-server.enabled=true ``` #### Certificate The Bitwarden SDK Server _NEEDS_ to run as an HTTPS service. That means that any installation that wants to communicate with the Bitwarden provider will need to generate a certificate. The best approach for that is to use cert-manager. It's easy to set up and can generate a certificate that the store can use to connect with the server. For a sample set up look at the bitwarden sdk server's test setup. It contains a self-signed certificate issuer for cert-manager. ## External secret store With that out of the way, let's take a look at how a secret store would look like. ```yaml {% include 'bitwarden-secrets-manager-secret-store.yaml' %} ``` The api url and identity url are optional. The secret should contain the token for the Machine account for bitwarden. !!! note Make sure that the machine account has Read-Write access to the Project that the secrets are in. !!! note A secret store is organization/project dependent. Meaning a 1 store == 1 organization/project. This is so that we ensure that no other project's secrets can be modified accidentally _or_ intentionally. ## External Secrets There are two ways to fetch secrets from the provider. ### Find by UUID In order to fetch a secret by using its UUID simply provide that as remote key in the external secrets like this: ```yaml apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: bitwarden spec: refreshInterval: 1h0m0s secretStoreRef: # This name must match the metadata.name in the `SecretStore` name: bitwarden-secretsmanager kind: SecretStore data: - secretKey: test remoteRef: key: "339062b8-a5a1-4303-bf1d-b1920146a622" ``` ### Find by Name To find a secret using its name, we need a bit more information. Mainly, these are the rules to find a secret: - if the key is a UUID, the secret is fetched directly by that ID. - if the key is not a UUID, it is treated as a secret name. The provider lists the organization's secrets and matches by name within the `projectID` and `organizationID` configured on the `SecretStore`. The project and organization come from the store, not from the `ExternalSecret`. - if exactly one secret matches that name in that project, its value is returned. - if more than one secret with the same name exists in that project, the provider errors. ```yaml apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: bitwarden spec: refreshInterval: 1h0m0s secretStoreRef: # This name must match the metadata.name in the `SecretStore` name: bitwarden-secretsmanager kind: SecretStore data: - secretKey: test remoteRef: key: "secret-name" ``` ### DataFrom `dataFrom.find` returns every secret in the organization, keyed by secret ID: ```yaml dataFrom: - find: conversionStrategy: Default decodingStrategy: None ``` !!! warning The provider does not currently filter by the `find` selector: the `name.regexp`, `tags`, and `path` fields are ignored and every secret in the organization is returned (see [issue #6550](https://github.com/external-secrets/external-secrets/issues/6550)). Use a `rewrite` rule, or individual `data` entries, if you need a subset. Note that the secrets in the map will end up something like this: ``` $ kubectl get secret secret-to-be-created -o jsonpath='{.data}'|jq { "2989464a-03c2-4ced-9fe2-b34400aca42d": "bG9jYWxob3N0OjEyMzQ1", "98c18ddb-314e-463c-97c3-b34400ac6593": "dWFzZXJuYW1lMQ==", "c917a790-76bc-49ca-b303-b34400ac8035": "UGFzc1dvcmQx", } ``` The finder uses the ID of the key instead of the name because in Bitwarden, having the same key/name for a secret inside the same project is a _VALID_ option. Meaning, potentially, a secret could overwrite another secret in the secret data map. Hence, the ID of the secret is used when listing all secrets. This is inconvenient because now we can hardly refer to these secrets anymore from code. Hence, it is advised to use a rewrite rule with templates or to avoid using dataFrom field. ## Push Secret Pushing a secret is also implemented. Pushing a secret requires even more restrictions because Bitwarden Secrets Manager allows creating the same secret with the same key multiple times. In order to avoid overwriting, or potentially, returning the wrong secret, we restrict push secret with the following rules: - name, projectID, organizationID and value AND NOTE equal, we won't push it again. - name, projectID, organizationID and ONLY the value does not equal ( INCLUDING THE NOTE ) we update - any of the above isn't true, we create the secret ( this means that it will create a secret in a separate project ) ```yaml apiVersion: external-secrets.io/v1alpha1 kind: PushSecret metadata: name: pushsecret-bitwarden # Customisable spec: refreshInterval: 1h0m0s # Refresh interval for which push secret will reconcile secretStoreRefs: # A list of secret stores to push secrets to - name: bitwarden-secretsmanager kind: SecretStore selector: secret: name: my-secret # Source Kubernetes secret to be pushed data: - match: secretKey: key # Source Kubernetes secret key to be pushed remoteRef: remoteKey: remote-key-name # Remote reference (where the secret is going to be pushed) metadata: note: "Note of the secret to add." ```