cyberark/secrets-provider-for-k8s

View on GitHub
PUSH_TO_FILE.md

Summary

Maintainability
Test Coverage
# Secrets Provider - Push to File Mode

## Table of Contents

- [Table of Contents](#table-of-contents)
- [Overview](#overview)
- [How Push to File Works](#how-push-to-file-works)
- [Set up Secrets Provider for Push to File](#set-up-secrets-provider-for-push-to-file)
- [Reference Table of Configuration Annotations](#reference-table-of-configuration-annotations)
- [Example Common Policy Path](#example-common-policy-path)
- [Example Secret File Formats](#example-secret-file-formats)
- [Custom Templates for Secret Files](#custom-templates-for-secret-files)
- [Configuring Pod Volumes and Container Volume Mounts](#configuring-pod-volumes-and-container-volume-mounts-for-push-to-file)
- [Secret File Attributes](#secret-file-attributes)
- [Deleting Secret Files](#deleting-secret-files)
- [Decoding Base64 Encoded Secrets](#decoding-base64-encoded-secrets)
- [Upgrading Existing Secrets Provider Deployments](#upgrading-existing-secrets-provider-deployments)
- [Troubleshooting](#troubleshooting)

## Overview

The push to file feature detailed below allows Kubernetes applications to
consume Conjur secrets directly through one or more files accessed through
a shared, mounted volume. Providing secrets in this way should require zero
application changes, as reading local files is a common, platform agnostic
delivery method.

The Secrets Provider can be configured to create and write multiple files
containing Conjur secrets. Each file is configured independently as a group of
[Kubernetes Pod Annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/),
collectively referred to as a "secret group".

Using annotations for configuration is new to Secrets Provider with this
feature and provides a more idiomatic deployment experience.

## How Push to File Works

![how push to file works](./assets/how-push-to-file-works.png)

1. The Secrets Provider, deployed as a
   [Kubernetes init container](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/)
   in the same Pod as your application container, starts up and parses Pod
   annotations from a
   [Kubernetes Downward API volume](https://kubernetes.io/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information/).
   The Pod annotations are organized in secret groups, with each secret group
   indicating to the Secrets Provider:
   - The policy paths from which Conjur secrets should be retrieved.
   - The format of the secret file to be rendered for that group.
   - How retrieved Conjur secret values should be mapped to fields
     in the rendered secret file.

1. The Secrets Provider authenticates to the Conjur server using the
   Kubernetes Authenticator (`authn-k8s`).

1. The Secrets Provider reads all Conjur secrets required across all
   secret groups.

1. The Secrets Provider renders secret files for each secret group, and
   writes the resulting files to a volume that is shared with your application
   container.

1. The Secrets Provider init container runs to completion.

1. Your application container starts and consumes the secret files from
   the shared volume.

## Set up Secrets Provider for Push to File

This section describes how to set up the Secrets Provider for Kubernetes for
Push to File operation.

![push to file workflow](./assets/p2f-workflow.png)

1. <details><summary>Before you begin</summary>

   - Make sure that a Kubernetes Authenticator has been configured and enabled.
     For more information, contact your Conjur admin, or see
     [Enable Authenticators for Applications](https://docs.conjur.org/Latest/en/Content/Integrations/Kubernetes_deployApplicationCluster.htm).

     In this procedure, we will use `dev-cluster` for the Kubernetes
     Authenticator.

   - This procedure assumes you have a configured Kubernetes namespace, with a
     service account for your application. The namespace must be configured with
     the [Namespace Preparation Helm chart](https://github.com/cyberark/conjur-authn-k8s-client/tree/master/helm/conjur-config-namespace-prep#conjur-namespace-preparation-helm-chart).

     In this procedure, we will use `test-app-namespace` for the namespace,
     and `test-app-sa` for the service account.

   - This procedure assumes that you are familiar with loading K8s manifests
     into your workspace.

   </details>

1. <details><summary>Define the application as a Conjur host in policy</summary>


   **Conjur admin:** To enable the Secrets Provider for Kubernetes
   (`cyberark-secrets-provider-for-k8s init container`) to retrieve Conjur
   secrets, it first needs to authenticate to Conjur.

   - In this step, you define a Conjur host used to authenticate the
     `cyberark-secrets-provider-for-k8s` container with the Kubernetes
     Authenticator.

     The Secrets Provider for Kubernetes must be defined by its **namespace**
     and **authentication container name**, and can also be defined by its
     **service account**. These definitions are defined in the host
     annotations in the policy. For guidelines on how to define annotations, see
     [Application Identity in Kubernetes](https://docs.conjur.org/Latest/en/Content/Integrations/Kubernetes_AppIdentity.htm).

     The following policy:

     - Defines a Conjur identity for `test-app` by its namespace,
       `test-app-namespace`, authentication container name,
       `cyberark-secrets-provider-for-k8s`, as well as by its service account,
       `test-app-sa`.

     - Gives `test-app` permissions to authenticate to Conjur using the
       `dev-cluster` Kubernetes Authenticator.

     Save the policy as **apps.yml**:

     ```yaml
     - !host
       id: test-app
       annotations:
         authn-k8s/namespace: test-app-namespace
         authn-k8s/service-account: test-app-sa
         authn-k8s/authentication-container-name: cyberark-secrets-provider-for-k8s

     - !grant
       roles:
       - !group conjur/authn-k8s/dev-cluster/consumers
       members:
       - !host test-app
     ```

     __**NOTE:** The value of the host's authn-k8s/authentication-container-name
       annotation states the container name from which it authenticates to
       Conjur. When you create the application deployment manifest below,
       verify that the CyberArk Secrets Provider for Kubernetes init container
       has the same name.__

   - Load the policy file to root.

     ```sh
     conjur policy load -f apps.yml -b root
     ```

   </details>

1. <details><summary>Define variables to hold the secrets for your application,
   and grant the access to the variables</summary>

   **Conjur admin:** In this step, you define variables (secrets) to which
   the Secrets Provider for Kubernetes needs access.


   - Save the following policy as **app-secrets.yml**.

     This policy defines Conjur variables and a group that has permissions on
     the variables.

     In the following example, all members of the `consumers` group are
     granted permissions on the `username` and `password` variables:

     ```yaml
     - !policy
       id: secrets
       body:
         - !group consumers
         - &variables
           - !variable username
           - !variable password
         - !permit
           role: !group consumers
           privilege: [ read, execute ]
           resource: *variables
     - !grant
       role: !group secrets/consumers
       member: !host test-app
     ```

   - Load the policy file to root.

     ```sh
     conjur policy load -f app-secrets.yml -b root
     ```

   - Populate the variables with secrets, for example `myUser` and `MyP@ssw0rd!`:

     ```sh
     conjur variable set -i secrets/username -v myUser
     conjur variable set -i secrets/password -v MyP@ssw0rd!
     ```

1. <details><summary>Set up the application deployment manifest</summary>

   **Application developer:** In this step you set up an application
   deployment manifest that includes includes an application container,
   `myorg/test-app`, and an init container that uses the
   `cyberark/secrets-provider-for-k8s` image. The deployment manifest also
   includes Pod Annotations to configure the Secrets Provider for Kubernetes
   Push to File feature. The annotations direct the Secrets Provider to
   generate and write a secret file containing YAML key/value settings
   into a volume that is shared with the application container.

   Copy the following manifest and load it to the application namespace,
   `test-app-namespace`.

   __NOTE:__ The `mountPath` values used for the
   `cyberark-secrets-provider-for-k8s` container must appear exactly as
   shown in the manifest below, i.e.:

   - `/conjur/podinfo` for the `podinfo` volume.
   - `/conjur/secrets` for the `conjur-secrets` volume.

   ```yaml
   apiVersion: apps/v1
   kind: Deployment
   metadata:
     labels:
       app: test-app
     name: test-app
     namespace: test-app-namespace
   spec:
     replicas: 1
     selector:
       matchLabels:
         app: test-app
     template:
       metadata:
         labels:
           app: test-app
         annotations:
           conjur.org/authn-identity: host/test-app
           conjur.org/container-mode: init
           conjur.org/secrets-destination: file
           conjur.org/conjur-secrets-policy-path.test-app: secrets/
           conjur.org/conjur-secrets.test-app: |
             - admin-username: username
             - admin-password: password
           conjur.org/secret-file-path.test-app: "./credentials.yaml"
           conjur.org/secret-file-format.test-app: "yaml"
       spec:
         serviceAccountName: test-app-sa
         containers:
         - name: test-app
           image: myorg/test-app
           volumeMounts:
             - name: conjur-secrets
               mountPath: /opt/secrets/conjur
               readOnly: true
         initContainers:
         - name: cyberark-secrets-provider-for-k8s
           image: 'cyberark/secrets-provider-for-k8s:latest'
           imagePullPolicy: Never
           env:
           - name: MY_POD_NAME
             valueFrom:
               fieldRef:
                 fieldPath: metadata.name
           - name: MY_POD_NAMESPACE
             valueFrom:
               fieldRef:
                 fieldPath: metadata.namespace
           envFrom:
           - configMapRef:
               name: conjur-connect
           volumeMounts:
             - name: podinfo
               mountPath: /conjur/podinfo
             - name: conjur-secrets
               mountPath: /conjur/secrets
         volumes:
           - name: podinfo
             downwardAPI:
               items:
                 - path: "annotations"
                   fieldRef:
                     fieldPath: metadata.annotations
           - name: conjur-secrets
             emptyDir:
               medium: Memory
   ```

   The Secrets Provider will create a secret file in the `conjur-secrets`
   shared volume that will appear in the `test-app` container at location
   `/opt/secrets/conjur/credentials.yaml`, with contents as follows:

   ```yaml
   "admin-username": "myUser"
   "admin-password": "myP@ssw0rd!"
   ```

## Reference Table of Configuration Annotations

Below is a list of Annotations that are used for basic Secrets Provider configuration 
and to write the secrets to file.
All annotations begin with `conjur.org/` so they remain unique.
Push to File Annotations are organized by "secret groups". A secrets group is a logical grouping of application secrets, typically belonging to a particular component of an application deployment (e.g. all secrets related to a backend database). Each group of secrets is associated with a specific destination file.

Please refer to the
[Secrets Provider documentation](https://docs.cyberark.com/Product-Doc/OnlineHelp/AAM-DAP/Latest/en/Content/Integrations/k8s-ocp/cjr-k8s-secrets-provider-ic.htm)
for a description of each environment variable setting:

| K8s Annotation  | Equivalent<br>Environment Variable | Description, Notes                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
|-----------------------------------------|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `conjur.org/authn-identity`         | `CONJUR_AUTHN_LOGIN`  | Required value. Example: `host/conjur/authn-k8s/cluster/apps/inventory-api`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `conjur.org/container-mode`         | `CONTAINER_MODE`      | Allowed values: <ul><li>`init`</li><li>`application`</li></ul>Defaults to `init`.<br>Must be set (or default) to `init`for Push to File mode.<br>NOTE: `sidecar` is supported for [Secrets Rotation](ROTATION.md)                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `conjur.org/secrets-destination`    | `SECRETS_DESTINATION` | Allowed values: <ul><li>`file`</li><li>`k8s_secrets`</li></ul>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `conjur.org/k8s-secrets`            | `K8S_SECRETS`         | This list is ignored when `conjur.org/secrets-destination` annotation is set to **`file`**                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `conjur.org/retry-count-limit`      | `RETRY_COUNT_LIMIT`   | Defaults to 5                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
| `conjur.org/retry-interval-sec`     | `RETRY_INTERVAL_SEC`  | Defaults to 1 (sec)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `conjur.org/log-level`              | `LOG_LEVEL`           | Allowed values: <ul><li>`debug`</li><li>`info`</li><li>`warn`</li><li>`error`</li></ul>Defaults to `info`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `conjur.org/conjur-secrets.{secret-group}`      | Note\* | List of secrets to be retrieved from Conjur. Each entry can be either:<ul><li>A Conjur variable path</li><li> A key/value pairs of the form <br>`<alias>:<Conjur variable path>`<br>`[content-type: <type>]`<br>where the `alias` represents the name of the secret to be written to the secrets file and the optional `content-type` is either text or base64, defaulting to text. See [Decoding Base64 Encoded Secrets](#decoding-base64-encoded-secrets) for more information.                                                                                                                                                                                                      |
| `conjur.org/conjur-secrets-policy-path.{secret-group}` | Note\* | Defines a common Conjur policy path, assumed to be relative to the root policy.<br><br>When this annotation is set, the policy paths defined by `conjur.org/conjur-secrets.{secret-group}` are relative to this common path.<br><br>When this annotation is not set, the policy paths defined by `conjur.org/conjur-secrets.{secret-group}` are themselves relative to the root policy.<br><br>(See [Example Common Policy Path](#example-common-policy-path) for an explicit example of this relationship.)                                                                                                                                                                           |
| `conjur.org/secret-file-path.{secret-group}`    | Note\* | Relative path for secret file or directory to be written. This path is assumed to be relative to the respective mount path for the shared secrets volume for each container.<br><br>If the `conjur.org/secret-file-format.{secret-group}` is set to `template`, then this secret file path defaults to `{secret-group}.out`. For example, if the secret group name is `my-app`, the the secret file path defaults to `my-app.out`.<br><br>Otherwise, this secret file path defaults to `{secret-group}.{secret-group-file-format}`. For example, if the secret group name is `my-app`, and the secret file format is set for YAML, the the secret file path defaults to `my-app.yaml`. 
| `conjur.org/secret-file-permissions.{secret-group}`| Note\*| Explicitly defines secret file permissions. <br><br>Defaults to `-rw-r--r--` (Octal `644`)<br><br>Values must be formatted as a valid permission string _(Directory bit is optional)_. For example:<li>`-rw-rw-r--`</li><li>`rw-rw-r--`</li>Owner must have at a minimum read/write permissions (`-rw-------`)                                                                                                                                                                                                                                                                                                                                                                         
| `conjur.org/secret-file-format.{secret-group}`  | Note\* | Allowed values:<ul><li>yaml (default)</li><li>json</li><li>dotenv</li><li>bash</li><li>properties</li><li>template</li></ul><br>This annotation must be set to `template` when using custom templates.<br><br>(See [Example Secret File Formats](#example-secret-file-formats) for example output files.)                                                                                                                                                                                                                                                                                                                                                                              |
| `conjur.org/secret-file-template.{secret-group}`| Note\* | Defines a custom template in Golang text template format with which to render secret file content. See dedicated [Custom Templates for Secret Files](#custom-templates-for-secret-files) section for details.                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |

__Note*:__ These Push to File annotations do not have an equivalent
environment variable setting. The Push to File feature must be configured
using annotations.

## Example Common Policy Path

Given the relationship between `conjur.org/conjur-secrets.{secret-group}` and
`conjur.org/conjur-secrets-policy-path.{secret-group}`, the following sets of
annotations will eventually retrieve the same secrets from Conjur:

```yaml
conjur.org/conjur-secrets.db: |
  - url: policy/path/api-url
  - policy/path/username
  - policy/path/password
```

```yaml
conjur.org/conjur-secrets-policy-path.db: policy/path/
conjur.org/conjur-secrets.db: |
  - url: api-url
  - username
  - password
```

## Example Secret File Formats

### Example YAML Secret File

Here is an example YAML format secret file. This format is rendered when
the `conjur.org/secret-file-format.{secret-group}` annotation is set
to `yaml`:

```yaml
"api-url": "dev/redis/api-url"
"admin-username": "dev/redis/username"
"admin-password": "dev/redis/password"
```

### Example JSON Secret File

Here is an example JSON format secret file. This format is rendered when
the `conjur.org/secret-file-format.{secret-group}` annotation is set
to `json`:

```json
{"api-url":"dev/redis/api-url","admin-username":"dev/redis/username","admin-password":"dev/redis/password"}
```

### Example Bash Secret File

Here is an example bash format secret file. This format is rendered when
the `conjur.org/secret-file-format.{secret-group}` annotation is set
to `bash`:

```sh
export api-url="dev/redis/api-url"
export admin-username="dev/redis/username"
export admin-password="dev/redis/password"
```

### Example dotenv/properties Secret File

Here is an example dotenv format file secret file. This format is rendered when
the `conjur.org/secret-file-format.{secret-group}` annotation is set
to `dotenv` or `properties`:

```sh
api-url="dev/redis/api-url"
admin-username="dev/redis/username"
admin-password="dev/redis/password"
```

> NOTE: The only difference between dotenv and properties is the validation of the key. For dotenv, the key cannot contain '.'. For properties, the key can contain '.'.

## Custom Templates for Secret Files

In addition to offering standard file formats, Push to File allows users to
define their own custom secret file templates. These templates adhere to Go's
text template formatting. Using custom templates requires the following:

- The annotation `conjur.org/secret-file-format.{secret-group}` must be set to
  `template`.
- The annotation `conjur.org/secret-file-path.{secret-group}` must be set, and
  must specify a file name.

The custom template must be provided by only one of the methods listed below,
either by Pod annotation or by volume-mounted ConfigMap. Providing a template by
both or neither method fails before retrieving secrets from Conjur.

1. <details><summary>Pod annotation</summary>

   Custom templates can be defined with the Pod annotation
   `conjur.org/secret-file-template.{secret-group}`. The following annotations
   describe a valid secret group that uses a custom template defined in Pod
   annotations:

   ```yaml
   conjur.org/secret-group.example: |
     - admin-username: <variable-policy-path>
     - admin-password: <variable-policy-path>
   conjur.org/secret-file-format.example: "template"
   conjur.org/secret-file-template.example: |
     "database": {
       "username": {{ secret "admin-username" }},
       "password": {{ secret "admin-password" }},
     }
   ```

</details>

2. <details><summary>Volume-mounted ConfigMap</summary>

   Custom templates can be provided as fields in a ConfigMap. This feature
   requires the following:

   - The ConfigMap containing template files must be created before the Secrets
     Provider container.
   - The ConfigMap must be mounted in the Secrets
     Provider container at `/conjur/templates`.
   - Each template's key in the ConfigMap's `data` field must be formatted as
     `{secret-group}.tpl`.

   ### Creating a ConfigMap for Secret File Templates

   #### Defining Templates in a ConfigMap Manifest

   The following is an example of a ConfigMap manifest defining a custom Go
   template for a secret group `example`:

   ```yaml
   apiVersion: v1
   kind: ConfigMap
   metadata:
     name: my-custom-template
   data:
     example.tpl: |
       "database": {
         "username": {{ secret "admin-username" }},
         "password": {{ secret "admin-password" }},
       }
   ```

   If custom secret file templates are required for multiple secret groups, add
   entries to the `data` field, adhering to the `{secret-group}.tpl` key
   pattern.

   #### Creating a ConfigMap from an Existing Template File

   A ConfigMap can also be created from an existing, compatible template file,
   allowing secret file templates to be checked into version control independent
   from a K8s manifest.

   Given the following template file, `example.tpl`:

   ```go
   "database": {
     "username": {{ secret "admin-username" }},
     "password": {{ secret "admin-password" }},
   }
   ```

   Create a ConfigMap from the template files:

   ```sh
   kubectl create configmap my-custom-template --from-file=/path/to/example.tpl
   ```

   The resulting ConfigMap will be functionally identical to one created from
   the [example ConfigMap manifest](#defining-templates-in-a-configmap-manifest)
   shown above.

   If secret file templates are required for multiple secret groups, a ConfigMap
   can be created from a directory of template files, each adhering to the
   `{secret-group}.tpl` naming pattern:

   ```sh
   kubectl create configmap my-custom-template --from-file=/path/to/template/dir/
   ```

   ### Configuring Secrets Provider to Consume ConfigMap-based Templates

   The following annotations describe a valid secret group that uses the custom
   template created by either of the previously described methods:

   ```yaml
   conjur.org/secret-group.example: |
     - admin-username: <variable-policy-path>
     - admin-password: <variable-policy-path>
   conjur.org/secret-file-format.example: "template"
   ```

   In order for Secrets Provider to consume the templates, another volume needs
   to be added to the application's deployment manifest, populated with the
   contents of the ConfigMap:

   ```yaml
   volumes:
   - name: conjur-templates
     configMap:
       name: my-custom-template
   ```

   Then, mount the ConfigMap-populated volume to the Secrets Provider init
   container at `/conjur/templates`:

   ```yaml
   volumeMounts:
   - name: conjur-templates
     mountPath: /conjur/templates
   ```

   </details>

### Using Conjur Secrets in Custom Templates

Injecting Conjur secrets into custom templates requires using the built-in custom
template function `secret`. The action shown below renders the value associated
with `<secret-alias>` in the secret-file.

```go
{{ secret "<secret-alias>" }}
```

### Global Template Functions

Custom templates support global functions native to Go's `text/template`
package. The following is an example of using template function to HTML
escape/encode a secret value.

```go
{{ secret "alias" | html }}
{{ secret "alias" | urlquery }}
```

If the value retrieved from Conjur for `alias` is `"<Hello@World!>"`,
the following file content will be rendered, each HTML escaped and encoded,
respectively:

```txt
&lt;Hello;@World!&gt;
%3CHello%40World%21%3E
```

For a full list of global Go text template functions, reference the official
[`text/template` documentation](https://pkg.go.dev/text/template#hdr-Functions).

### Additional Template Functions

Custom templates also support a limited number of additional functions. Currently
supported functions are:

- `b64enc`: Base64 encode a value.
- `b64dec`: Base64 decode a value.

These can be used as follows:

```go
{{ secret "alias" | b64enc }}
{{ secret "alias" | b64dec }}
```

When using the `b64dec` function, an error will occur if the value retrieved from
Conjur is not a valid Base64 encoded string.

### Execution "Double-Pass"

To avoid leaking sensitive secret data to logs, and to ensure that a
misconfigured Push to File workflow fails fast, Push to File implements a
"double-pass" execution of custom templates. The template "first-pass" runs
before secrets are retrieved from Conjur, and validates that the provided custom
template successfully executes given `"REDACTED"` for each secret value.
Redacting secret values allows for secure, complete error logging for
malformed templates. The template "second-pass" runs when rendering secret
files, and error messages during this stage are sanitized. Custom templates that
pass the "first-pass" and fail the "second-pass" require experimenting locally
to identify bugs.

_**NOTE**: Custom templates should not branch conditionally on secret values.
This may result in a template first-pass execution that doesn't validate all
branches of the custom template._

### Example Custom Templates: Direct reference to secret values

The following is an example of using a custom template to render secret data by
referencing secrets directly using the custom template function `secret`.

```yaml
conjur.org/secret-file-path.direct-reference: ./direct.txt
conjur.org/secret-file-template.direct-reference: |
  username | {{ secret "db-username" }}
  password | {{ secret "db-password" }}
```

Assuming that the following secrets have been retrieved for secret group
`direct-reference`:

```yaml
db-username: admin
db-password: my$ecretP@ss!
```

Secrets Provider will render the following content for the file
`/conjur/secrets/direct.txt`:

```txt
username | admin
password | my$ecretP@ss!
```

### Example Custom Templates: Iterative approach

The following is an example of using a custom template to render secret data
using an iterative process instead of referencing all variables directly.

```yaml
conjur.org/secret-file-path.iterative-reference: ./iterative.txt
conjur.org/secret-file-template.iterative-reference: |
  {{- range $index, $secret := .SecretsArray -}}
  {{- $secret.Alias }} | {{ $secret.Value }}
  {{- end -}}
```

Here, `.SecretsArray` is a reference to Secret Provider's internal array of
secrets that have been retrieved from Conjur. For each entry in this array,
there is a secret `Alias` and `Value` field that can be referenced in the custom
template.

Assuming that the following secrets have been retrieved for secret group
`iterative-reference`:

```txt
db-username: admin
db-password: my$ecretP@ss!
```

Secrets Provider will render the following content for the file
`/conjur/secrets/iterative.txt`:

```txt
db-username | admin
db-password | my$ecretP@ss!
```

### Example Custom Templates: PostgreSQL connection string

The following is an example of using a custom template to render a secret file
containing a Postgres connection string. For a secret group described by the
following annotations:

```yaml
conjur.org/secret-file-path.postgres: ./pg-connection-string.txt
conjur.org/secret-file-template.postgres: |
  postgresql://{{ secret "dbuser" }}:{{ secret "dbpassword" }}@{{ secret "hostname" }}:{{ secret "hostport" }}/{{ secret "dbname" }}??sslmode=require
```

Assuming that the following secrets have been retrieved for secret group
`postgres`:

```yaml
dbuser:     "my-user"
dbpassword: "my-secret-pa$$w0rd"
dbname:     "postgres"
hostname:   "database.example.com"
hostport:   5432
```

Secrets Provider will render the following content for the file
`/conjur/secrets/pg-connection-string.txt`:

```txt
postgresql://my-user:my-secret-pa$$w0rd@database.example.com:5432/postgres??sslmode=require
```

### Example Custom Templates: Spring Boot configuration

Many Spring Boot applications use [externalized configuration](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config)
loaded at start-up, usually as an `application.yaml` or `.properties` file. For
Spring Boot applications deployed in Kubernetes, Secrets Provider's Push-to-File
feature can generate properly formatted config files, populated with secret
data, using custom templates.

This example assumes that the target Conjur server has been loaded with the
following secrets, representing a PostgreSQL database and its credentials:

- `backend/pg/url`
- `backend/pg/username`
- `backend/pg/password`

These secrets will be used by a secret group, `spring-app`, to render a Spring
Boot configuration file.

Create a file defining a Spring Boot configuration template, saved as
`spring-app.tpl`. The following represent template examples for an
`application.yml` template:

```yaml
spring:
  datasource:
    platform: postgres
    url: jdbc:{{ secret "url" }}
    username: {{ secret "username" }}
    password: {{ secret "password" }}
  jpa:
    generate-ddl: true
    hibernate:
      ddl-auto: update
```

...and an `application.properties` template:

```txt
spring.datasource.platform: postgres
spring.datasource.url: jdbc:{{ secret "url" }}
spring.datasource.username: {{ secret "username" }}
spring.datasource.password: {{ secret "password" }}
spring.jpa.generate-ddl: true
spring.jpa.hibernate.ddl-auto: update
```

Create a ConfigMap from the new template file:

```sh
kubectl create configmap spring-boot-templates \
  --from-file=/path/to/spring-app.tpl \
  --namespace test-app-namespace
```

Now that the `spring-boot-templates` ConfigMap is deployed to the desired
namespace, set up a Deployment manifest, with:

- a Secrets Provider init container configured for Push-to-File with custom
  secret file templates,
- a Spring Boot application container, configured to consume the newly-generated
  configuration file with the `--spring.config.location` flag.

Copy the following manifest and load it into the application namespace,
`test-app-namespace`. Configuration unique to this example is marked in <b>bold</b>.

<pre><code>apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: test-app
  name: test-app
  namespace: test-app-namespace
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-app
  template:
    metadata:
      labels:
        app: test-app
      annotations:
        conjur.org/authn-identity: host/test-app
        conjur.org/container-mode: init
        conjur.org/secrets-destination: file
        <b>conjur.org/conjur-secrets-policy-path.spring-app: backend/pg/
        conjur.org/conjur-secrets.spring-app: |
          - url
          - username
          - password
        conjur.org/secret-file-path.spring-app: "./application.{yaml|properties}"
        conjur.org/secret-file-format.spring-app: "template"</b>
    spec:
      serviceAccountName: test-app-sa
      containers:
      - name: test-app
        image: myorg/test-springboot-app
        <b>command: ["java", "-jar", "/app.jar", "--spring.config.location=file:/opt/secrets/conjur/application.{yaml|properties}"]</b>
        volumeMounts:
          - name: conjur-secrets
            mountPath: /opt/secrets/conjur
            readOnly: true
      initContainers:
      - name: cyberark-secrets-provider-for-k8s
        image: 'cyberark/secrets-provider-for-k8s:latest'
        imagePullPolicy: Never
        env:
        - name: MY_POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: MY_POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        envFrom:
        - configMapRef:
            name: conjur-connect
        volumeMounts:
          - name: podinfo
            mountPath: /conjur/podinfo
          - name: conjur-secrets
            mountPath: /conjur/secrets
          <b>- name: conjur-templates
            mountPath: /conjur/templates</b>
      volumes:
        - name: podinfo
          downwardAPI:
            items:
              - path: "annotations"
                fieldRef:
                  fieldPath: metadata.annotations
        - name: conjur-secrets
          emptyDir:
            medium: Memory
        <b>- name: conjur-templates
          configMap:
            name: spring-boot-templates</b>
            
</code></pre>

## Configuring Pod Volumes and Container Volume Mounts for Push to File

Enabling Push to File on your application Pod requires the addition of several Volumes and VolumeMounts.
Some example Volume/VolumeMount configurations are shown below.

- Example Pod Volumes:
  
  ```yaml
  volumes:
    - name: podinfo
      downwardAPI:
        items:
          - path: annotations
            fieldRef:
              fieldPath: metadata.annotations
    - name: conjur-secrets
      emptyDir:
        medium: Memory
  ```

- Example volume mounts for the Secrets Provider init container. The `mountPath` values must appear exactly as shown below:

  ```yaml
  volumeMounts:
    - name: podinfo
      mountPath: /conjur/podinfo
    - name: conjur-secrets
      mountPath: /conjur/secrets
  ```

- Example volume mounts for the application container. The mountPath to use is dependent upon where your application expects to find secret files:

  ```yaml
  volumeMounts:
    - name: conjur-secrets
      mountPath: <your-desired-path-to-secret-files>
  ```

## Secret File Attributes

By default, the Secrets Provider will create secrets files with the following file attributes:

|  Attribute  |       Value        | Notes  |
| ----------- | ------------------ | ------ |
| User        | `secrets-provider` |        |
| Group       | `root`             | OpenShift requires that any files/directories that are shared between containers in a Pod must use a GID of 0 (i.e. GID for the root group) The Secrets Provider uses a GID of 0 for secrets files even for non-OpenShift platforms, for simplicity.       |
| UID         | `777`              |        |
| GID         | `0`                |        |
| Permissions | `rw-rw-r--`        | As shown in the table table, the Secrets Provider will create secrets files that are world readable. This means that the files will be readable from any other container in the same Pod that mounts the Conjur secrets shared Volume.       |

The file attributes can be overridden by defining a 
[PodSecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#podsecuritycontext-v1-core) for the application Pod.

For example, to have containers run as the `nobody` user (UID 65534) and `nobody` group (GID 65534), 
and have secrets files created with the corresponding GID, the Pod SecurityContext 
would be as follows:

```yaml
    securityContext:
      runAsUser: 65534
      runAsGroup: 65534
      runAsNonRoot: true
      fsGroup: 65534
```

## Deleting Secret Files

Currently, it is recommended that applications do not delete secret files
after consuming the files. Kubernetes does not currently restart init
containers when primary (i.e. non-init) containers crash and cause
liveness or readiness probe failures. Because the Secrets Provider is run
as an init container for the Push to File feature, this means that it is
not restarted, and therefore secret files are not recreated, following
liveness or readiness failures.

## Decoding Base64 Encoded Secrets

Secrets can be stored in Conjur as base64 encoded strings. If the applications consuming the secrets cannot be modified
to decode the secrets, those secrets become unusable. Secrets Provider can decode the secrets before handing it over to
the applications for consumption.

### Decoding Base64 with Push to File

Secrets Provider in push to file mode is configured using annotations.
The `conjur.org/conjur-secrets.{secret-group}` annotation will need to be modified
to decode secret.
Add the `content-type` annotation.
Note the `alias` must also be defined with the `path`.

Given the following secrets annotation:

```yaml
conjur.org/conjur-secrets.db: |
  - url: policy/path/api-url
  - policy/path/username
  - policy/path/password
```

Update with base64 decode as below:

```yaml
conjur.org/conjur-secrets.db: |
  - url: policy/path/api-url
  - policy/path/username
  - password: policy/path/password
    content-type: base64
```

With this annotation the contents of password will be base64 decoded.
If the contents cannot be decoded, a warning is displayed in the log files
and the contents retrieved will not be decoded.


### Decoding Base64 with Kubernetes Secrets

Secrets Provider in Kubernetes Secrets mode is configured using annotations and
by configuring a kubernetes secret with a `conjur-map`.

Given the following Kubernetes Secret configuration

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: test-app-secrets-provider-k8s-secret
type: Opaque
stringData:
  conjur-map: |-
    DB_URL: test-secrets-provider-k8s-app-db/url
    DB_USERNAME: test-secrets-provider-k8s-app-db/username
    DB_PASSWORD: test-secrets-provider-k8s-app-db/password
```

To add base64 decoding to the `test-secrets-provider-k8s-app-db/password`
modify the `DB_PASSWORD` with an id: and content-type: as below.

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: test-app-secrets-provider-k8s-secret
type: Opaque
stringData:
  conjur-map: |-
    DB_URL: test-secrets-provider-k8s-app-db/url
    DB_USERNAME: test-secrets-provider-k8s-app-db/username
    DB_PASSWORD:
      id: test-secrets-provider-k8s-app-db/password
      content-type: base64
```

If the contents cannot be decoded, a warning is displayed in the log files
and the contents retrieved will not be decoded.

## Upgrading Existing Secrets Provider Deployments

At a high level, converting an existing Secrets Provider deployment to use
annotation-based configuration and/or Push to File mode can be done in two ways:

- Inspect the existing application Deployment manifest (if available) or inspect
  a live deployment with:

  ```sh
  kubectl get deployment $DEPLOYMENT_NAME -o yaml
  ```

  Update the manifest with the changes described below, and then re-apply then
  configuration:

  ```sh
  kubectl apply -f <updated-manifest-filepath>
  ```

- Edit a live deployment, applying the changes described below, with the
  interactive command:

  ```sh
  kubectl edit deployment $DEPLOYMENT_NAME
  ```

To convert Secrets Provider from K8s Secrets mode to Push to File mode, make the
following changes to the deployment:

- Convert the Service Provider container/Conjur environment variable settings
  to the equivalent annotation-based settings. Use the
  [Annotation Reference Table](#reference-table-of-configuration-annotations)
  for envvar-to-annotation mappings.
  - `conjur.org/secrets-destination: file` enables Push to File mode.
  - `conjur.org/conjur-secrets.{group}` defines Conjur policy paths previously
    defined in Kubernetes Secrets.
    - For each existing Kubernetes Secret, you may wish to create a separate
      secrets group.
    - Inspect the manifests for the existing Kubernetes Secret(s). The manifests
      should contain a `stringData` section that contains secrets key/value pairs.
      Map the `stringData` entries to a YAML list value for conjur-secrets, using
      the secret names as aliases.
    - Alternatively, for existing deployments, this mapping can be obtained with
      the command

      ```sh
      kubectl get secret $SECRET_NAME -o jsonpath={.data.conjur-map} | base64 --decode
      ```

  - `conjur.org/secret-file-path.{group}` configures a target file destination.
  - `conjur.org/secret-file-format.{group}` configures a desired file type,
    depending on how the application will consume the secrets file.
  - Remove environment variables used for Secrets Provider configuration from
    the init container (see Annotation Reference table).
  - Remove environment variables referencing Kubernetes Secrets from the
    application container.
- Add push-to-file volumes:

    ```yaml
    volumes:
      - name: podinfo
        downwardAPI:
          items:
            - path: annotations
              fieldRef:
                fieldPath: metadata.annotations
      - name: conjur-secrets
        emptyDir:
          medium: Memory
      - name: conjur-templates
        emptyDir:
          medium: Memory
    ```

- Add push-to-file volume mounts to the Secrets Provider init container:

    ```yaml
    volumeMounts:
      - name: podinfo
        mountPath: /conjur/podinfo
      - name: conjur-secrets
        mountPath: /conjur/secrets
      - name: conjur-templates
        mountPath: /conjur/templates
    ```

- Add push-to-file volume mount to the application container:

    ```yaml
    volumeMounts:
      - name: conjur-secrets
        mountPath: /conjur/secrets
    ```

- Delete existing Kubernetes Secrets or their manifests:
  - If using Helm, delete Kubernetes Secrets manifests and do a
    `helm upgrade ...`
  - Otherwise, `kubectl delete ...` the existing Kubernetes Secrets
- Modify application to consume secrets as files:
  - Modify application to consume secrets files directly, or...
  - Modify the Deployment's spec for the app container so that the
    `command` entrypoint includes sourcing of a bash-formatted secrets file.

## Troubleshooting

This section describes how to troubleshoot common Secrets Provider for Kubernetes issues.

### Enable debug logs

To enable debug logs, add the debug parameter to the application deployment manifest.

```yaml
annotations:
    conjur.org/log-level  "debug"
```

For details, see [Reference Table of Configuration Annotations](#reference-table-of-configuration-annotations)

### Display logs

To display the Secrets Provider logs in Kubernetes.

- Go to the app namespace

  ```sh
  kubectl config set-context --current --namespace=<namespace>
  ```

- Find the pod name

  ```sh
  kubectl get pods
  ```

- Display the logs

  ```sh
  kubectl logs <pod-name> -c <init-container-name>
  ```

  To display the Secrets Provider logs in Openshift.

- Go to the app namespace
  ```sh
  oc project <project-name>
  ```

- Find the pod name

  ```sh
  oc get pods
  ```

- Display the logs

  ```sh
  oc logs <pod-name> -c <init-container-name>
  ```

### Common issues displayed in the logs and resolutions

|  Issue      |       Error code   | Resolution  |
| ----------- | ------------------ | ------ |
| Secrets Provider for Kubernetes failed to update secrets in file mode | `CSPFK039E` | Check that the secret key pairs have been configured correctly.  |
| Failed to open annotations file                                       | `CSPFK041E` | The annotations file cannot be opened by the Secrets provider. Check the volume mounts and downward API configuration.  |
| Annotation 'x' does not accept value                                  | `CSPFK042E` | This annotation requires a specific type for its value. The value provided is the wrong type. |
| Annotation 'x' does not accept value                                  | `CSPFK043E` | This annotation only accepts one of a specific set of values. The value provided is the wrong value. |
| Annotation file line 'x' is malformed:                                | `CSPFK045E` | The annotation described in the message is malformed. Check the annotation has a valid key and value       |
| Secret Store Type needs to be configured                              | `CSPFK046E` | Either the conjur.org/secrets-destination annotation has not been set, or the downward API volumes have not been configured as required. See the [Configuring Pod Volumes and Container Volume Mounts](#configuring-pod-volumes-and-container-volume-mounts-for-push-to-file) section above for more information.|
| Secrets Provider in Push-to-File mode can only be configured with Pod annotations | `CSPFK047E`| To configure push-to-file the Secrets store type must have the `conjur.org/secrets-destination` with pod annotations and cannot be set with the `SECRETS_DESTINATION` environment variable. If the secrets-destination is set via annotations verify that the pod volumes and volumeMounts are configured correctly. See the [Configuring Pod Volumes and Container Volume Mounts](#configuring-pod-volumes-and-container-volume-mounts-for-push-to-file) section above for more information. |
| Secrets Provider in K8s Secrets mode requires either the 'K8S_SECRETS' environment variable or 'conjur.org/k8s-secrets' | `CSPFK048E ` | If the `secrets-destination` is set to `k8s_secrets` then the `K8S_SECRETS` environment variable or `conjur.org/k8s-secrets` needs to be configured. This does not apply to push-to-file.       |
| Failed to validate Pod annotations                                    | `CSPFK049E` | The service provider was unable to successfully parse the annotations. This could be due to a previous error. Check the logs for a specific error before this.      |
| Unable to initialize Secrets Provider: unable to create secret group collection  |`CSPFK053E` | Secrets provider could be initialized. Check futher back in the log file for any specific configuration errors. |
| Unable to initialize Secrets Provider: unrecognized Store Type        | `CSPFK054E` | The `secrets-destination` value, either defined by the `SECRETS_DESTINATION` environment variable or the `conjur.org/secrets-destination` is an invalid value. `conjur.org/secrets-destination` should be `files` or `k8s-secrets`. |

### Other Common issues resolutions

| Issue       | Resolution |
| ----------- | ------------------ |
| There are no files created and no errors in the logs | Verify there is no SECRETS_DESTINATION environmental variable set and the volumes are set up correctly. |
<!--
### Using the Helper Script to Patch the deployment
There is a script in the secrets-provider-for-k8s bin directory named 
generate-annotation-upgrade-patch.sh that can be used to generate a patch file.

The patch can be output to a file:
```
bin/generate-annotation-upgrade-patch.sh --push-to-file \$DEPLOYMENT_NAME > patch.json
```
Test patch against a live deployment:
```
kubectl patch deployment \$DEPLOYMENT_NAME --type json --patch-file patch.json --dry-run=server
```
Preview the new deployment:
```
kubectl patch deployment \$DEPLOYMENT_NAME --type json --patch-file patch.json --dry-run=server --output yaml
```
Apply patch:
```
kubectl patch deployment \$DEPLOYMENT_NAME --type json --patch-file patch.json
```
-->