design/m1.1_search_and_replace_design.md
# DRAFT: Solution Design - Kubernetes Developer Experience M1.1 - Config File Search-and-Replace
## Table of Contents
- [Useful Links](#useful-links)
- [Background](#background)
+ [Objective](#objective)
+ [The Challenges](#the-challenges)
+ [Competitive Solutions](#competitive-solutions)
+ [Alternatives to Consider](#alternatives-to-consider)
- [Solution](#solution)
+ [User Experience](#user-experience)
+ [Performance Metrics](#performance-metrics)
+ [Upgrading Existing Secrets Provider Deployments](#upgrading-existing-secrets-provider-deployments)
+ [Project Scope and Limitations](#project-scope-and-limitations)
- [Design](#design)
+ [Flow Diagram](#flow-diagram)
+ [Workflow](#workflow)
- [Performance](#performance)
- [Backwards Compatibility](#backwards-compatibility)
- [Affected Components](#affected-components)
- [Test Plan](#test-plan)
+ [Test environments](#test-environments)
+ [Test assumptions](#test-assumptions)
+ [Out of scope](#out-of-scope)
+ [Test prerequisites](#test-prerequisites)
+ [Test Cases](#test-cases)
- [Logs](#logs)
- [Documentation](#documentation)
- [Version update](#version-update)
- [Security](#security)
+ [Preventing Leaking of Sensitive Information in Error Logs](#preventing-leaking-of-sensitive-information-in-error-logs)
- [Audit](#audit)
- [Development Tasks](#development-tasks)
- [Definition of Done](#definition-of-done)
+ [Future Work](#future-work)
- [Solution Review](#solution-review)
- [Appendix](#appendix)
## Useful Links
<table>
<thead>
<tr class="header">
<th>Name</th>
<th>Link</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>M1 PRD: Conjur Developer Experience for K8s</td>
<td><p>Sharepoint (private)</p></td>
</tr>
<tr class="even">
<td>Aha Card</td>
<td><p>SCR-E-76 (private)</p></td>
</tr>
<tr class="odd">
<td>M1 Feature Doc</td>
<td>Sharepoint (private)</td>
</tr>
<tr class="even">
<td>M1 Solution Design Doc</td>
<td><a href="https://github.com/cyberark/secrets-provider-for-k8s/blob/main/design/m1_push_to_file_design.md">link</a></td>
</tr>
<tr class="odd">
<td>Kubernetes Documentation:Annotations</td>
<td><a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/">link</a></td>
</tr>
<tr class="even">
<td>Exposing Pod Annotations to Containers</td>
<td><a href="https://kubernetes.io/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information/">link</a></td>
</tr>
<tr class="odd">
<td>Secrets Provider Documentation</td>
<td><a href="https://docs.cyberark.com/Product-Doc/OnlineHelp/AAM-DAP/11.2/en/Content/Integrations/Kubernetes_deployApplicationsConjur-k8s-Secrets.htm?tocpath=Integrations%7COpenShift%252C%20Kubernetes%252C%20and%20GKE%7CDeploy%20Applications%7C_____3">link</a></td>
</tr>
</tbody>
</table>
## Background
### Objective
The [Milestone 1 (M1) Push-to-File](https://github.com/cyberark/secrets-provider-for-k8s/blob/main/design/m1_push_to_file_design.md)
initiative enhanced the Secrets Provider init container integration by adding
the capability of **creating and writing** "secrets" files directly into an
application container's file system using shared volumes.
As a followup to the initial M1 milestone, the M1.1 milestone proposes
to provide the Secrets Provider init container with the capability of
**modifying** existing application configuration files using a
search-and-replace mechanism to insert secrets that have been retrieved
from Conjur into the configuration file.
The intent is to provide a more declarative method for personas such as
developers or DevOps to specify how to apply Conjur secrets to configuration
files. This should help simplify the process of applying or upgrading environment-specific
secrets or configuration (e.g. secrets to be used for development vs. secrets
for production) to those configuration files.
### The Challenges
#### Kubernetes Creates Empty Directories for Shared Volumes
The primary challenge to this design is to determine a convenient way for
making the original (or baseline) configuration file available to the
Secrets Provider init container so that the Secrets Provider can make
modifications.
Unfortunately, it's not possible for an application container to allow access
to the original configuration file to the Secrets Provider by simply using a
[Kubernetes shared volume](https://kubernetes.io/docs/concepts/storage/volumes/#emptydir),
even if Kubernetes [subPaths](https://kubernetes.io/docs/concepts/storage/volumes/#using-subpath)
are used to minimize the granularity of sharing down to the per-file level.
The problem is that Kubernetes uses `emptyDir` volumes to provide file sharing
between containers of a Pod, and as the name implies, Kubernetes creates
an empty directory at the `mountPath` used for the shared volume by each
container. Once the `emptyDir` volume has been mounted, any pre-existing files
that had been in a container's file system at that `mountPath` are no longer
visible.
A good blog that describes this challenge can be found
[here](https://www.fairwinds.com/blog/kubernetes-emptydir-not-the-same-as-dockers-volumes-from).
#### Kubernetes ConfigMap and Downward API Volumes are Read-Only
In alternative solutions that are considered below, it's important to keep
in mind that Kubernetes volumes that are used to mount ConfigMap data or
Downward API metadata are created with read-only access. This is intentional,
and is a security measure (e.g. so that processes running in the container
can't change the intended configuration to elevate privilege).
This means that if we use ConfigMaps or Annotations to make the original
configuration file available to the Secrets Provider, we can't simply modify
the configuration file and write the modified file back to the same volume.
### Competitive Solutions
#### Hashicorp: Create/Modify Config File Using Templates
The [Hashicorp Vault Agent Container](https://learn.hashicorp.com/tutorials/vault/kubernetes-sidecar?in=vault/kubernetes)
also allows configuration files to be created and modified using templates,
as described
[here](https://learn.hashicorp.com/tutorials/vault/kubernetes-sidecar?in=vault/kubernetes#apply-a-template-to-the-injected-secrets).
Again, this is not a search-and-replace option, since the structure of the
configuration file is defined via a template.
#### Hashicorp: Mount Secrets through Container Storage Interface (CSI)
Hashicorp supports a
[Kubernetes Container Storage Interace (CSI) driver](https://learn.hashicorp.com/tutorials/vault/kubernetes-secret-store-driver?in=vault/kubernetes)
for integrating applications with their Vault.
Strictly speaking, this is also not a search-and-replace option, since
secrets/files would be injected into application file systems in their
entirety (without modification).
It may be possible, although probably overly complicated, to use a CSI
driver to inject an original configuration file into a Secrets Provider
init container, which can then be modified and written to a volume shared
by an application container.
### Alternatives to Consider
#### How to Make the Original Configuration File Available to Secrets Provider?
As described in the
[Using a ConfigMap to Share the Original Config File](#using-a-configmap-to-share-the-original-config-file)
section below, this design recommends using a ConfigMap to expose the
the original config file to the Secrets Provider, at least for the initial
release of the M1.1 Search-and-Replace feature.
Here are some of the options that were considered for making the original
config file available to the Secrets Provider init container:
| Method | Description |
|--------|-------------|
| Share via ConfigMap | A developer/DevOps would create a custom ConfigMap that contains the original, baseline config file. The Secrets Provider would read the original config file, make modifications, and write the modified file to a volume shared with the application. |
| Share via Pod Annotations | A developer/DevOps would apply a special Pod Annotation that contains the original config file as the Annotation value. The Secrets Provider would read the original config file via Downward API volume, make modifications, and write the modified file to a volume shared with the application. |
| Store original file as secret in Conjur | A copy of the original, unmodified configuration file could be stored as a secret in Conjur. The Secrets Provider would retrieve the original config file from Conjur, make modifications, and write the modified file to a volume shared with the application. |
| Store copy of original file online | A copy of the original, unmodified configuration file could be stored in online storage (e.g. AWS S3, Google Cloud Storage, etc.) The Secrets Provider would retrieve the online, original config file, make modifications, and write the modified file to a volume shared with the application. |
| Share via custom init container | A developer/DevOps would need to create a custom init container that contains the original config file. At run time, this init container would copy the config file to a volume shared with the Secrets Provider. The Secrets Provider would make modifications and write the modified file to another volume shared with the application. |
| Modify the application to copy the config file to shared volume | A developer/DevOps would need to modify the application image or deployment manifest so that the application container copies the original config file from its file system to a shared volume at startup. This would require that the Secrets Provider be run as a sidecar container rather than an init container. |
| Use a `kubectl` sidecar container to copy file from/to application container | An additional sidecar container could be developed that is capable of using `kubectl cp ...` (or the Kubernetes API) to read the original config file from the application container. This needs to be run as a sidecar since it requires the application container to be running when the sidecar reads the original config file. This would also require that the Secrets Provider be run as a sidecar container, and that the operations of the `kubectl` sidecar, the Secrets Provider sidecar, and the application container be synchronized to run in sequence. |
Of these options, the approach of using a ConfigMap for sharing the original
config file was chosen because it is probably the simplest to use from a
customer's perspective. Compared to the approach of using Annotations, this
approach has the advantage that it avoids cluttering the resulting Deployment
manifest with config file content. The (relatively small) downside is that
developers/DevOps personas are required to define one extra ConfigMap `volume`
and `volumeMount` in their application manifests for exposing the original
config file.
#### What Syntax to Use for Search-and-Replace Specifications?
In addition to exposing the original config file content via a ConfigMap,
this design recommends using Pod Annotations for configuring the
Search-and-Replace feature (see the [Pod Annotations](#pod-annotations)
section below).
Rather than introducing a new syntax (or language) to be used for specifying
search-and-replace operations to be performed on a config file, it will be
much easier to use this feature if the search-and-replace operations can
be specified using a well-known, industry standard syntax.
For example, let's say we have an original config file that is a basic,
`.ini` file:
```
[database]
server=192.0.2.62
port=143
cert="<Database certificate goes here>"
```
And let's say that we want the following search-and-replace operations
to be performed to customize this config file:
- Replace the database server IP address with an IP address retrieved as a Conjur secret.
- Replace the database server port number with a port number retrieved as a Conjur secret.
- Replace the certificate value with a certificate has been retrieved as a
Conjur secret that needs to be base64 decoded before being inserted into
the config file.
Here are several options that were considered for providing users with a
familiar or well-known syntax for specifying the search-and-replace operations
to perform on the configuration file:
| Option | Example |
|--------|---------|
| Use `sed` substitution syntax | <pre><code>conjur.org/config-file-modifications.payroll: |</code><br /><code> - s/server=.\*/server={{.db-server}}/</code><br /><code> - s/port=.\*/port={{.db-port}}/</code><br /> - s/cert=.\*/cert="{{.db-cert | b64dec}}"/</code></pre> |
| Use `awk` substitution syntax | <pre><code>conjur.org/config-file-modifications.payroll: |</code><br /><code> - sub(/server=.\*/,server={{.db-server}})</code><br /><code> - sub(/port=.\*/,port={{.db-port}})</code><br /><code> - sub(/cert=.\*/,cert="{{.db-cert | b64dec}})"</code></pre> |
| Use match/replace `regex` expression pairs | <pre><code>conjur.org/config-file-modifications.payroll: |</code><br /><code> - match: "server=.\*"</code><br /><code> replace: "server={{.db-server}}"</code><br /><code> - match: "port=.\*"</code><br /><code> replace: "port={{.db-port}}"</code><br /><code> - match: "cert=.\*"</code><br /><code> replace: "cert="{{.db-cert | b64dec}}"</code></pre> |
Some notes on these options:
- Each of the examples uses "replacement templates" that are represented
as [Go templates](https://pkg.go.dev/text/template). For example:
- `{{.db-server}}`: Replace this field with the value retrieved from Conjur
for the secret with the alias `db-server`.
- `{{.db-port}}`: Replace this field with the value retrieved from Conjur
for the secret with the alias `db-port`.
- `{{.db-cert | b64dec}}`: Replace this field with the base64-decoded
value retrieved from Conjur for the secret with the alias `db-cert`.
- The syntax for both `sed` and `awk` conveniently use a single string
to specify each search-and-replace operation.
- Both `sed` and `awk` syntax can incorporate `regex` matching.
- Each of these syntax options can be extended to include support for
regex capture groups. (See
[this blog](https://www.ryanchapin.com/using-sed-with-regex-capture-groups/)
for an example of how regex capture groups can be used with `sed`.)
## Solution
### User Experience
#### Using a ConfigMap to Share the Original Config File
The initial design for M1.1 Search-and-Replace will support using a ConfigMap
to provide the Secrets Provider with a copy of the original configuration file
(for reasons described in the section
[How to Make the Original Configuration File Available to Secrets Provider?](#how-to-make-the-original-configuration-file-available-to-secrets-provider)
above).
##### Example ConfigMap for a Simple, Original Config File
Here is an example ConfigMap that provides a simple config file:
```
apiVersion: v1
kind: ConfigMap
metadata:
name: payroll-ini-configmap
data:
# Sample application .ini file
payroll.ini: |
; last modified 1 April 2019
[owner]
name=John Doe
organization=Acme Widgets Inc.
[database]
server=192.0.2.62
port=143
file=payroll.dat
cert="<Database certificate goes here>"
```
##### Example Volume Mounts for the Secrets Provider
For the M1.1 Search-and-Replace feature, the Secrets Provider init container
will require two `volumeMount` definitions in its corresponding Deployment
manifest:
- One `volumeMount` for reading the original config file (via a ConfigMap `volume`)
- One `volumeMount` for writing the modified config file (via an emptyDir `volume`)
Here are example `volumeMount` definitions for the Secrets Provider init
container.
_**NOTE: The paths used in the `mountPath` definitions for Search-and-Replace
must be written exactly as shown. The Secrets Provider is hard-coded to
look for the original config file and will write the modified config file
to these respective directories. The names used for the `volumeMounts` are
arbitrary. **_
```
volumeMounts:
- name: payroll-config-orig
mountPath: /conjur/config-file-src
- name: payroll-config
mountPath: /conjur/config-file-dest
```
##### Example Volume Mount for the Application Container
For the M1.1 Search-and-Replace feature, the application will require a
`volumeMount` definition for specifying a path from which the application will
be expected to read the modified config file. The path used for the
`mountPath` definition is application-specific.
Here is an example:
```
volumeMounts:
- name: payroll-config
mountPath: /etc/payroll
```
##### Example Volume Definitions
For the M1.1 Search-and-Replace feature, the application will require two
`volume` definitions in its corresponding Deployment manifest:
- One `configMap` `volume` for providing the original config file
- One `emptyDir` `volume` for sharing the modified config file
For example:
```
volumes:
- name: payroll-config-orig
configMap:
name: payroll-ini-configmap
- name: payroll-config
emptyDir:
medium: Memory
```
#### Using `sed` Substitution Syntax for Search-and-Replace Specifications
The M1.1 Search-and-Replace feature will support the `sed` substitution
command syntax for specifying search-and-replace operations to be performed
on a config file. This will allow each search-and-replace specification to be
conveniently defined with single substitute command string. This syntax also
lends itself to support of regex capture groups. Details are provided below.
#### Pod Annotations
The M1.1 Search-and-Replace feature is configured via Pod Annotations that are
applied to application Deployments. Pod Annotations are specified on a
per-secret-group basis as described in the
[M1 Solution Design Document](https://github.com/cyberark/secrets-provider-for-k8s/blob/main/design/m1_push_to_file_design.md).
The Pod Annotations that support configuration file search-and-replace
functionality are as follows:
| Annotation | Description | Details |
|------------------------------------------------|-------------|---------|
| conjur.org/conjur-secrets.{secret-group} | List of secrets to be retrieved from Conjur. This Annotation is not new for this design, and is described in the M1 Solution Design Document. | See [M1 Supported Annotations](https://github.com/cyberark/secrets-provider-for-k8s/blob/main/design/m1_push_to_file_design.md#supported-annotations) |
| conjur.org/config-file-src-path.{secret-group} | Relative filepath for Secrets Provider to read the original configuration file. This filepath should be relative to the `volumeMount` point for the `configMap` volume used to expose the original config file. (See [Example Volume Mounts for the Secrets Provider](#example-volume-mounts-for-the-secrets-provider)) | See [Annotation for Configuring the Source Filepath](#annotation-for-configuring-the-source-filepath) |
| conjur.org/config-file-dest-path.{secret-group} | Relative filepath for the application container to read the modified configuration file. This filepath is relative to the application container's `volumeMount` point for the `emptyDir` volume used to share the modified config file. (See [Example Volume Mount for the Application Container](#example-volume-mount-for-the-application-container)). | See [Annotation for Configuring the Destination Filepath](#annotation-for-configuring-the-destination-filepath) |
| conjur.org/config-file-modifications.{secret-group} | List of search-and-replace operations to be performed on the config file. | See [Annotation for Configuring File Modifications](#annotation-for-configuring-file-modifications) |
#### Example Search-and-Replace Pod Annotations
For illustration purposes, here is an example of how the M1.1
Search-and-Replace feature can be configured via Pod Annotations.
Let's suppose the configuration file to be modified is a simple `.ini`
file:
```
; last modified 1 April 2019
[owner]
name=John Doe
organization=Acme Widgets Inc.
[database]
server=192.0.2.62
port=143
file=payroll.dat
cert="<Database certificate goes here>"
```
Furthermore, let's suppose that we want the following search-and-replace
operations to be performed to customize this config file:
- Replace the last modified date with the current date.
- Replace the following fields with secrets retrieved from Conjur:
- Owner name
- Owner organization
- Database server IP address
- Database server port
- (Future support) Database certificate (stored as a base64-encoded string
on Conjur)
Here is an example of a Kubernetes Deployment patch YAML file that can
be used to configure the search-and-replace operations described above:
```
spec:
template:
metadata:
annotations:
conjur.org/conjur-secrets.payroll: |
- owner-name: prod/payroll/owner/name
- owner-org: prod/payroll/owner/org
- db-server: prod/payroll/db/server
- db-port: prod/payroll/db/port
conjur.org/config-file-src-path.payroll: payroll.ini
conjur.org/config-file-dest-path.payroll: payroll.ini
conjur.org/config-file-modifications.payroll: |
- s/name=.*/name={{.owner-name}}/
- s/organization=.*/organization={{.owner-org}}/
- s/server=.*/server={{.db-server}}/
- s/port=.*/name={{.db-port}}/
- s/cert=.*/cert="{{.db-cert | b64dec}}"/
```
#### Annotation for Configuring the Source Filepath
The relative filepath from which the Secrets Provider should read the original
config file is configured using the following Pod Annotation (see example
above). This filepath should be relative to the `volumeMount` point for the
`configMap` volume used to expose the original config file. (See
[Example Volume Mounts for the Secrets Provider](#example-volume-mounts-for-the-secrets-provider)):
```
conjur.org/config-file-src-filepath.<secret-group>: |
<config-file-content>
```
#### Annotation for Configuring the Destination Filepath
The relative filepath from which the application container should read the
modified config file is configured using the following Pod Annotation (see
example above). This filepath is relative to the application container's
`volumeMount` point for the `emptyDir` volume used to share the modified
config file. (See
[Example Volume Mount for the Application Container](#example-volume-mount-for-the-application-container)):
```
conjur.org/config-file-dest-filepath.<secret-group>: <dest-full-filepath>
```
#### Annotation for Configuring File Modifications
The search-and-replace operations to be performed on the baseline config
file are configured using the following Pod Annotation (see example above):
```
conjur.org/config-file-modifications.<secret-group>: |
<list-of-sed-substitute-commands>
```
Each `sed` substitute command in the list is of the form:
```
s/<match-pattern>/<replacement-pattern>/
```
Some notes:
- The Secrets Provider is expected to iterate through this list,
performing the search-and-replace operation specified on each entry in the
list in sequence.
- Unless the `sed` global `g` flag is used, substitute commands will only
modify the **first** occurrence of the matched pattern. If the global
`g` flag is used, then all occurrences of the matched pattern will
be modified.
- Logically speaking, the Secrets Provider executes each substitute command
equivalent to running the following `sed` command:
```
sed -i <sed-substitute-command> <configFile>
```
(Note that the implementation of this execution may or may not use execution
of the actual `sed` command via `os.Exec()`. That is, the design may
involve a pure Golang implementation for `sed`-like functionality.)
- As displayed in the examples above, the "replacement pattern" defined for
each `sed` substitute command can make use of one or more "replacement
templates" to indicate which and how Conjur secrets values should be
retrieved and inserted. Replacement templates are described in more in the
following section.
- At some point in the future, the `sed` substitute commands will support
the use of regex capture groups. (See the
[(FUTURE SUPPORT) Regex Capture Groups](#future-support-regex-capture-groups)
section below.)
##### Replacement Templates
As described in the previous section, the replacement pattern defined for
each `sed` substitute command can make use of one or more "replacement
templates" to indicate which and how Conjur secrets values should be
retrieved and inserted. Replacement templates are represented as
Go templates.
Using Go template syntax for replacement templates keeps this syntax
consistent with the syntax used for the
[M1 custom template support](https://github.com/cyberark/secrets-provider-for-k8s/blob/main/design/m1_push_to_file_design.md#example-custom-templates),
and it allows the M1.1 Search-and-Replace implementation to reuse the
at least a portion of the Go template processing that was developed
for M1.
Examples of replacement templates include:
| Example | Description |
|---------|-------------|
| <code>{{date}}</code> | (FUTURE SUPPORT) Insert the current date |
| <code>{{.db-url}}</code> | Insert the value retrieved from Conjur for the secret with alias `db-url` |
| <code>{{.db-cert | b64dec}}</code> | Insert the base64-decoded value retrieved from Conjur for the secret with alias `db-cert` |
Some notes:
- Tokens that begin with `.` represent the secret aliases for values retrieved
from Conjur.
- In the future, replacement templates may also include support for
[Go template functions](https://pkg.go.dev/text/template#hdr-Functions).
The supported Go template functions may include:
- Standard (built-in) Go template functions
- A select subset of functions from the
[Sprig template function library](http://masterminds.github.io/sprig/).
- Custom functions created specifically for M1/M1.1 features
Some examples of template functions that are under consideration:
| Template Function | Description |
|-------------------|-------------|
| date | (FUTURE SUPPORT) Insert the current date |
| b64enc | (FUTURE SUPPORT) Base64 encode the value before insertion |
| b64dec | (FUTURE SUPPORT) Base64 decode the value before insertion |
| quote | (FUTURE SUPPORT) Surround the value in quotes before insertion |
| indent <N> | (FUTURE SUPPORT) Indent the line by N spaces before insertion |
##### Escaping/Un-Escaping any Pre-Existing Curly Braces in Baseline Config File
Since the search-and-replace syntax makes use of `{{` and `}}` double-curly
braces to signify a replacement template, care must be taken to preserve any
pre-existing curly braces in the original (baseline) config file. This can
be achieved by having the Secrets Provider perform the following
as part of its search-and-replace workflow:
- Escaping curly braces in the baseline file **BEFORE** executing all
search-and-replace operations on the config file. This can be done by
inserting a `\` backslash in front of each `{` or `}` curly brace.
- Un-escaping any escaped curly braces in the modified config file **AFTER**
all replacement templates have been parsed and executed. This can be done by
removing any `\` backslash characters that precede any `{` or
`}` curly braces.
##### (FUTURE SUPPORT) Regex Capture Groups
In the future, the M1 Search-and-Replace feature may support the use of
regex capture groups to specify how specific fields that are matched
in the `sed` substitute command can be inserted into the config file.
See [this blog](https://www.ryanchapin.com/using-sed-with-regex-capture-groups/)
for an example of how regex capture groups can be used with `sed`.
#### Avoiding Leaking of Sensitive Secrets Data in Container Logs
When config files are rendered using secrets values that have been
retrieved from Conjur, we need to be careful that those sensitive secrets
values are not exposed in container logs as part of error messages
when rendering errors occur. This may require a test run of Go replacement
template execution using "dummy" Conjur secret values in order
to provide detailed error messages to the user while avoiding an leaking
of sensitive secret values.
### Performance Metrics
<TBD>
### Upgrading Existing Secrets Provider Deployments
<TBD>
### Project Scope and Limitations
<TBD>
#### Out of Scope
<TBD>
## Design
### Flow Diagram
The flow diagram for the M1.1 Search-and-Replace feature is the same as it
was for the M1 Push-to-File design. For the M1.1 Search-and-Replace feature,
the "Pod Annotations from Manifest" source in this diagram may also include
the new Annotations described in the [Pod Annotations](#pod-annotations)
section above, including the Annotation that provides the Secrets Provider
with the baseline config file.
![Secrets Provider Push to File Flow Diagram](./m1_push_to_file_flow.png)
### Workflow
The workflow that the Secrets Provider will execute for the M1
Search-and-Replace feature is as follows:
- At startup, parse all Pod Annotations (same as in M1 design)
- After all Annotations are parse, retrieve secrets from Conjur for
each secrets group (same as in M1 design)
- Iterate through the configuration for each secret group (same as in M1 design):
- If the Annotations for this secret group include the source config file and
the destination filepath for config file search-and-replace, then:
- If the Annotations for this secret group include config file
modifications, then:
- Escape all pre-existing curly braces in the baseline config file by
inserting a backslash in front of each curly brace.
- Iterate through all entries in the modification list:
- Perform the `sed` substitution.
- After `sed` substitutions have been performed for all entries in
the modification list, iterate through all replacement templates
in the resulting modified config file:
- Execute a "test run" of the Go replacement template execution
using "dummy" secrets values, logging any template execution errors
and exiting Secrets Provider execution if errors are encountered.
(This "test run" allows for detailed errors to be displayed
without risk of leaking sensitive secrets values.)
- Execute the Go replacement template using actual secret values
retrieved from Conjur, while suppressing raw template execution
errors. If errors do occur, display a sanitized error message
and exit Secrets Provider execution.
- Un-escape any escaped curly braces (i.e. those with a preceding
backslash) in the modified config file.
- Write the modified (or unmodified, if modifications were not configured)
config file to the destination filepath.
## Performance
<TBD>
## Backwards Compatibility
## Affected Components
<TBD>
## Test Plan
<TBD>
### Test environments
### Test assumptions
### Out of scope
### Test prerequisites
### Test Cases
#### Unit Tests
#### Error Handling / Recovery / Supportability Tests
#### E2E tests
#### Security testing
#### Performance testing
## Logs
<TBD>
## Documentation
<TBD>
## Version update
<TBD>
## Security
### Preventing Leaking of Sensitive Secrets Values in Error Logs
<TBD>
### Code Injection Threat Analysis
<TBD - Explain why this isn't possible, and how we'll test for this in UT>
## Audit
No changes to Conjur audit behavior are required for this feature.
## Development Tasks
<TBD>
## Definition of Done
<TBD>
### Future Work
<TBD>
## Solution Review
<table>
<thead>
<tr class="header">
<th><strong>Persona</strong></th>
<th><strong>Name</strong></th>
<th><strong>Design Approval</strong></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Team Leader</td>
<td>Dane LeBlanc</td>
<td><ul>
<p> </p>
</ul></td>
</tr>
<tr class="even">
<td>Product Owner</td>
<td>Alex Kalish</td>
<td><ul>
<p> </p>
</ul></td>
</tr>
<tr class="odd">
<td>System Architect</td>
<td>Rafi Schwarz</td>
<td><ul>
<p> </p>
</ul></td>
</tr>
<tr class="even">
<td>Security Architect</td>
<td>Andy Tinkham</td>
<td><ul>
<p> </p>
</ul></td>
</tr>
<tr class="odd">
<td>QA Architect</td>
<td>Andy Tinkham</td>
<td><ul>
<p> </p>
</ul></td>
</tr>
</tbody>
</table>
## Appendix