# 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."
```
