- Useful Links
- Background
- Solution
- Design
- Performance
- Backwards Compatibility
- Affected Components
- Test Plan
- Logs
- Documentation
- Version update
- Security
- Audit
- Development Tasks
- Definition of Done
- Solution Review
- Appendix
Name | Link |
---|---|
PRD: Conjur Developer Experience for K8s | Sharepoint (private) |
Aha Card | SCR-E-76 (private) Note: This design document covers work for "Milestone 1: Push to File" as defined in this Aha Card. |
Feature Doc | Sharepoint (private) |
Sample Manifests and Policies From Feature Spec | link |
Kubernetes Documentation:Annotations | link |
Exposing Pod Annotations to Containers | link |
Secrets Provider Documentation | link |
Secrets Provider application-mode Helm chart manifest | link |
The goal of the Milestone 1 (M1) Push-to-File initiative is to enhance the Secrets Provider init container integration with an option for writing secrets values that have been retrieved from Conjur into files that can be directly accessed by an application that is running in the same Pod.
The intent is to make it much easier for developers to integrate their applications directly with Conjur, without having to make significant changes or additions to those applications or their containers; that is, without having to add Summon to the application container or integrate a Conjur client API into the application itself.
The M1 Push-to-File feature requires a flexible way to allow developers or deployers to map secrets that are to be retrieved from Conjur to specific target file location(s). This will allow applications to consume secrets from files at expected locations, further minimizing changes to the application.
The M1 Push-to-File feature makes use of Kubernetes Pod Annotations to provide a method of flexibly configuring the mapping of secrets to desired target locations.
The M1 Push-to-File feature also adds support for configuring other Secrets Provider container/application settings using Kubernetes annotations as an alternative to using Pod environment variable settings. Allowing Secrets Provider to be more generally configured via Pod annotations provides a uniform mechanism for configuring the Secrets Provider, and should lend itself to an easier upgrade/migration to the proposed CyberArk Dynamic Sidecar Injector design.
The M1 Push-to-File feature adds the capability of setting Secrets Provider container/application configuration using Pod annotations.
When the Secrets Provider is configured for Push-to-File mode, then this configuration must be set using Pod annotations. (Configuration via Pod environment variables, i.e. legacy configuration is not supported in Push-to-File mode.)
When the Secrets Provider is configured for Kubernetes Secrets mode, then the user has the option of setting this configuration either via Pod annotations or by using Pod environment variable settings. If both a Pod Annotation and a Pod environment variable are configured for a given Secrets Provider setting, then the Pod Annotation configuration takes precedence.
QUESTION: Should we deprecate the current configuration method that uses environment variable settings?
The following annotations are supported for Secrets Provider configuration. Please refer to the Secrets Provider documentation for a description of each environment variable setting:
Annotation | Equivalent 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:
init |
conjur.org/ secrets-destination |
SECRETS_DESTINATION | Required value. Allowed values:
|
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/ debug-logging |
DEBUG | Defaults to false |
The configuration of the Secrets Provider push-to-file secrets mappings makes use of "secret groups". A secret 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).
As shown in the table below, all push-to-file annotations contain a reference
to a secret group, using the form: conjur.org/<parameter-name>.<group>
.
There is typically a one-to-one correspondence between secret groups and
files that are written to the application Pod's Conjur secrets shared volume.
The one exception to this general rule is for the plaintext
secrets file
format, for which there will be one secrets file per Conjur secret.
Annotation | Description |
---|---|
conjur.org/conjur-secrets.{secret-group} | List of secrets to be retrieved from Conjur. Each entry can be either:
|
conjur.org/conjur-secrets-policy-path.{secret-group} | Prefix to use for all Conjur variable paths for a secret group. An example is prod/backend/ . The path is assumed to be relative to root policy path. A leading / is ignored. A trailing / is optional. Defaults to the root policy path. |
conjur.org/secret-file-path.{secret-group} | Relative path for secrets 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 (for the Secrets Provider container, the mount path is /conjur/secrets/ ). (Refer to Example Kubernetes Manifests and Helm Named Templates for example Volumes and VolumeMounts.)If the conjur.org/secret-file-template.{secret-group} annotation is set, then this secret file path must also be set, and it must include a file name (i.e. must not end in / ).If the conjur.org/secret-file-template.{secret-group} annotation is not set, then 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 , then the secret file path defaults to my-app.yaml . |
conjur.org/secret-file-format.{secret-group} | Allowed values:
This setting is ignored when conjur.org/secret-file-template.{secret-group} is configured. |
conjur.org/secret-file-template.{secret-group} | Custom template for secret file contents in Golang text template format. Templates can include any of the pre-defined Golang template functions. See Custom Templates for Secret Files section below. |
The following is an example of a conjur.org/conjur-secrets.{secret-group}
Annotation that defines a flat (i.e. no aliases) list of Conjur variable paths
for a hypothetical secret group named database
:
conjur.org/conjur-secrets.database: |
- prod/backend/url
- prod/backend/port
- prod/backend/password
- prod/backend/username
For conjur-secrets
entries without aliases, the last word in the path is
used as the application secret name. So for the example above, if
no other annotations are set for the database
secret group, then Secrets
Provider will write to a file database.yaml
in the /conjur/secrets/
directory (the default value for the conjur.org/secret-file-path.{secret-group}
annotation) with contents that look like the following:
url: https://database.example.com
port: 5
password: my-secret-p@$$w0rd
username: postgres
The following is an example of a conjur.org/conjur-secrets.{secret-group}
Annotation that defines a list of Conjur variable paths, including some that
use an alias, for a hypothetical secret group named cache
:
conjur.org/conjur-secrets.cache: |
- url
- admin-password: password
- admin-username: username
conjur.org/conjur-secrets-policy-path.cache: dev/memcached/
In this example, the paths for the secrets that are retrieved from Conjur are:
- dev/memcached/url
- dev/memcached/password
- dev/memcached/username
and the names of the secrets that are written to the secrets file are:
- url
- admin-password
- admin-username
so that the cache.yaml
file that Secrets Provider writes includes:
url: https://database.example.com
admin-password: my-secret-p@$$w0rd
admin-username: admin
In addition to offering standard file formats, Push to File allows users to
define their own custom secret file templates, configured with the
conjur.org/secret-file-template.{secret-group}
annotation. These templates
adhere to Go's text template formatting. Providing a custom template will
override the use of any standard format configured with the annotation
conjur.org/secret-file-format.{secret-group}
.
Injecting Conjur secrets into custom templates requires using the custom
template function secret
. The action shown below renders the value
associated with in the secret-file:
{{ secret "<secret-alias>" }}
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.
{{ 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:
<Hello;@World!>
%3CHello%40World%21%3E
For a full list of global Go text template functions, reference the official text/template documentation.
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: Documentation should make clear that user-supplied 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.
The following is an example of using a custom template to render secret data by referencing secrets directly using the custom template function secret.
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:
db-username: admin
db-password: my$ecretP@ss!
Secrets Provider will render the following content for the file
/conjur/secrets/direct.txt
:
username | admin
password | my$ecretP@ss!
The following is an example of using a custom template to render secret data using an iterative process instead of referencing all variables directly:
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:
db-username: admin
db-password: my$ecretP@ss!
Secrets Provider will render the following content for the file
/conjur/secrets/iterative.txt
:
db-username | admin
db-password | my$ecretP@ss!
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:
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:
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
:
postgresql://my-user:[email protected]:5432/postgres??sslmode=require
The Secrets Provider will create with file permissions described in the Secret File Attributes subsection of the Security section below.
Annotations will be validated as they are being parsed by the Secrets Provider This input validation is done before any secrets values are retrieved from the Conjur instance. The Secrets Provider will restart if any validation error is detected.
For the yaml
, json
, bash
, and dotenv
secrets file formats, the
conjur.org/conjur-secrets.{secret-group}
annotation is expected to
contain a list of elements that can be a mix of:
- Conjur secret variable paths
- Key/value pairs of the form
{alias}: {Conjur secret variable path}
The Secrets Provider will validate that each Conjur secret variable path that
is included in conjur-secrets
mappings represent valid Conjur variable
paths, and that the variable name (the last word in the path) is no longer
than 126 characters.
For the yaml
, json
, bash
, or dotenv
secrets file format, the
conjur.org/conjur-secrets.{secret-group}
annotation can include key/value
pairs of the form {alias}: {Conjur secret variable path}
. The secret
alias in this case will be used as a key when writing a rendered secret
(i.e. after secret is retrieved from the Conjur instance) as a key/value
pair to an output secrets file.
The number of characters allowed for keys used in YAML, JSON, bash,
or .env
files varies based on format.
The YAML standard allows only the printable subset of the Unicode character set. (See the "Chapter 5. Characters" section of the YAML 1.2 Specification.
Of course, any key (i.e. alias) that the Secrets Provider writes to a secrets file will ultimately be consumed by the target application. The target application may be more restrictive (in comparison to the YAML specification) in the range of keys that it expects or requires in its secrets key/value pairs. However, the Secrets Provider should be flexible enough to handle applications with the most liberal range of characters allowed as YAML keys.
The Secrets Provider will validate that aliases that are included in
conjur.org/conjur-secrets.{secret-group]
mappings for YAML output file
format to contain only characters that are allowed per the YAML specification
as described above.
The YAML standard defines a maximum length of 1024 characters (see
definition of simple key).
The Secrets Provider will validate that aliases that are included in
conjur.org/conjur-secrets.{secret-group]
mappings for YAML output file
format are no longer than 1024 characters in length.
The JSON standard allows strings that are composed of any Unicode codepoint
"except "
or \
or control characters". See the string
syntax defined
on this Introducing JSON document.
Here is an example found on the Internet of how liberal the JSON standard is for allowable characters:
{
"🐶🔫": "not nice, but still valid json"
}
The Secrets Provider will validate that aliases that are included in
conjur.org/conjur-secrets.{secret-group]
mappings for JSON output file
format to contain only characters that are allowed for strings per the JSON
specification as described above.
The JSON specification does not directly impose restrictions on the length of keys, however there is a restriction on the maximum length of an entire JSON document which is 2097152 characters, which is equivalent to 4 MB of Unicode string data. (There may be stricter restrictions imposed by the Golang JSON parser). The Secrets Provider will validate that the rendered secrets file content for a JSON format secrets file does not exceed the maximum length allowed for a JSON document.
The Secrets Provider will validate that aliases that are included in
conjur.org/conjur-secrets.{secret-group]
mappings for the bash
and
dotenv
output file formats conform to the
POSIX name format,
that is, aliases must consist solely of alphanumerics (a-z, A-Z, or 0-9) and
underscores, and the first character cannot be a digit. The format can be
represented as the following regex:
[a-zA-Z_][a-zA-Z0-9_]*
When secrets 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.
The Preventing Leaking of Sensitive Information in Error Logs section below provides details as to how leaking of sensitive data is avoided while rendering secrets file content.
Because users have the option to explicitly name the output secrets file
name for each secret group (using the
conjur.org/secret-file-path.{secret-group}
annotation), it is possible that
the chosen names for two or more secret groups could be identical. For the
M1 release of the Push-to-File feature, this will be considered a fatal
misconfiguration. The Secrets Provider will check for any duplications
in secrets file names between groups after all annotations have been parsed.
In a future release, we may consider adding concurrency mechanisms to allow multiple secret groups to append to a shared target secrets file.
For secret groups that use yaml
, json
, bash
, or dotenv
formatting
for secrets files, the Secrets Provider will detect when a secret alias is
being reused in annotation(s) for a given secret group. Reuse of a secret
alias is considered a fatal misconfiguration, and will cause the Secrets
Provider to fail (abort execution).
The current Secrets Provider design supports configuration of Conjur connection information via the following Pod environment variables (please refer to the Secrets Provider Documentation for a descripion of each of these environment variables):
- CONJUR_ACCOUNT
- CONJUR_AUTHN_URL
- CONJUR_SSL_CERTIFICATE
- CONJUR_URL
The M1 Push-to-File feature provides a second option for configuring these
Conjur connection parameters using a Conjur Connection ConfigMap. The
application Deployment manifest must be configured to mount this ConfigMap
data using a mountPath
value of /conjur/connect
, as shown in the
example Helm named template
below. With this configuration, the Secrets Provider will look for
Conjur connection information in the following files:
- /conjur/connect/CONJUR_ACCOUNT
- /conjur/connect/CONJUR_AUTHN_URL
- /conjur/connect/CONJUR_SSL_CERTIFICATE
- /conjur/connect/CONJUR_URL
with each file containing the Conjur connection parameter corresponding to the file's name. Any values that are found in these files take precedence over the corresponding environment variable setting.
The current design supports exposing the Secrets Provider's Pod Name and Pod Namespace to the Secrets Provider container through Kubernetes Downward API environment variables.
The M1 Push-to-File supports a second method for exposing this Pod information
to the Secrets Provider container using a
Kubernetes Downward API shared volume.
This method requires that the application manifest use a mountPath
of
/conjur/podinfo
for the downward API volume as shown in the example
Kubernetes manifests and Helm named templates
below. With this configuration, the Pod Name and Namespace will be made
available to the Secrets Provider container at the following locations:
- /conjur/podinfo/MY_POD_NAME
- /conjur/podinfo/MY_POD_NAMESPACE
(Any values that are set in the Pod info shared volume take precedence over environment variable settings.)
As described in the M1 Feature Doc, our current guidance for taking an existing application Kubernetes manifest and modifying it to add a Conjur authentication client (e.g. Conjur authn-k8s, Secretless Broker, or Secrets Provider) init/sidecar container requires the addition of many lines of YAML configuration. Much of this configuration is fairly static (unchanging from deployment to deployment), and can be considered to be repetitive "boilerplate" configuration.
For customers who use Helm to deploy their applications, the M1 Push to File
feature will include the development of
named Helm templates,
(sometimes called a "partials" or "subtemplates") to encapsulate this
boilerplate configuration and make it available for reuse. Customers will be
able to reference this partial configuration in their manifests via simple Helm
include
statements. This will minimize the amount of lines that need to be
copy/pasted into Deployment manifests when adding Conjur authentication support.
See example in appendix. This named template will include:
- Init container image specification
- Volume mounts
The only templated field in this named template is the Docker image (repo with version tag) for the Secrets Provider container:
- image: {{ .Values.conjur.secretsProvider.image }}
This named template can be included in an application manifest by adding the following (see example Deployment manifest):
{{ include "conjur-secrets-provider.init-container" . | indent 6 }}
See example in appendix. This named template will include the Volume definitions for:
- Conjur secrets files
- Conjur connection information
- Conjur Pod info (for Kubernetes Downward API)
- Conjur error logs (for redirected standard error during secrets file rendering)
This named template can be included in an application manifest by including the following:
{{ include "conjur-secrets-provider.volumes" . | indent 8 }}
For optimum user experience, the Secrets Provider named templates described above will be published to a special Helm chart that will be created for Conjur authentication named templates such as these. This will allow customers to add the named templates to their application Helm charts as a Helm dependency.
Using a Helm dependency will eliminate the need for customers to copy/paste the named templates directly into their application Helm charts.
The Helm chart for Conjur authentication named templates can be added
as a Helm dependency to an application Helm chart by adding the following
entry to the Helm chart's Chart.yaml
file:
dependencies:
- name: conjur-named-templates
version: "<Conjur named templates chart version>"
repository: "https://cyberark.github.io/helm-charts"
For reference, Kubernetes manifests and Helm named templates for an example application that uses a Secrets Provider init container in Push-to-File mode are listed in the Appendix.
url: https://database.example.com
admin-password: p@$$w0rd
admin-username: zappa
{
"url": "https://database.example.com",
"admin-password": "p@$$w0rd",
"admin-username": "zappa"
}
export URL="https://database.example.com"
export ADMIN_PASSWORD="p@$$w0rd"
export ADMIN_USERNAME="zappa"
URL="https://database.example.com"
ADMIN_PASSWORD="p@$$w0rd"
ADMIN_USERNAME="zappa"
NOTE: Support for the plaintext
secrets file format is future work
that may be added after the Full-Featured (Certified) Community Release.
The plaintext
secrets file format is intended to provide an easy upgrade
path to push-to-file functionality for Kubernetes Deployments that currently
consume Kubernetes Secret values as files.
When the conjur.org/secret-file-format.{secret-group}
annotation is set to
plaintext
, the Secrets Provider will create a secrets file for each Conjur
secret listed in the conjur.org/conjur-secrets.{secret-group}
annotation.
The following applies to secrets files that are created by the Secrets
Provider for the plaintext
file format:
- One secret file is created per Conjur secret.
- The secret file name is the secret name/alias.
- The secret file content is the Conjur secret value.
- If the
conjur.org/secret-file-path.{secret-group}
annotation is not explicitly set, the destination directory forplaintext
secrets files defaults to/conjur/secrets/{secret-group}
. - If the
conjur.org/secret-file-path.{secret-group}
annotation is explicitly set, it must be a directory (i.e. it must end with a/
character) or Secrets Provider will fail and restart.
For example, if the following annotations are included in a Pod spec manifest:
conjur.org/conjur-secrets-policy-path.database: dev/database/
conjur.org/secret-file-path.database: dev/postgres/
conjur.org/conjur-secrets.database: |
- url
- admin-username: username
- admin-password: password
Assuming the following secrets are retrieved from Conjur:
dev/database/url: "https://postgres.example.com"
dev/database/username: "admin"
dev/database/password: "open-$e$ame"
Then the Secrets provider will create three secrets files as follows:
File Path | File Contents |
---|---|
/conjur/secrets/postgres/url |
https://postgres.example.com |
/conjur/secrets/postgres/admin-username |
admin |
/conjur/secrets/postgres/admin-password |
open-$e$ame |
NOTE: Support for a performance metrics endpoint is considered a "best effort" feature for the early GA milestone. If this functionality cannot be realized within the milestone deadlines, then Secrets Provider push-to-file performance will be measured manually, perhaps using temporary instrumention of Secrets Provider code to measure latency.
For performance monitoring, the M1 push-to-file feature will add a metrics HTTP endpoint that can be scraped by a Prometheus server. As described in the Performance section below, the following latency metrics will be supported:
- Latency (processing time) for parsing Pod annotations
- Latency for generating secrets requests and processing responses
- Latency for writing retrieved secrets to files
Performance metrics for Secrets Provider CPU and memory footprints is considered a nice-to-have feature.
At a high level, converting an existing Secrets Provider deployment to use annotation-based configuration and/or push-to-file mode:
- Inspect the existing application Deployment manifest (if available) or
use
kubectl edit
to inspect the application Deployment. - Convert the Service Provider container/Conjur environment variable settings
to the equivalent annotation-based setting. Retain the
K8S_SECRETS
setting for now. - If you are using the Secrets Provider as an init container, and you would
like to convert from K8s Secrets mode to push-to-file mode:
- Add push-to-file annotations:
- For each existing Kubernetes Secret, you may wish to create a separate secret group for push-to-file.
conjur.org/conjur-secrets.{group}
: Inspect the manifests for the existing Kubernetes Secret(s). The manifests should contain astringData
section that contains secrets key/value pairs. Map thestringData
entries to a YAML list value for conjur-secrets, using the secret names as aliases.conjur.org/secret-file-path.{group}
: Configure a target locationconjur.org/secret-file-format.{group}
: Configure a desired type, depending on how the application will consume the secrets file.
- 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
- If using Helm, delete Kubernetes Secrets manifests and do a
- Delete the
K8S_SECRETS
environment variable setting from the application Deployment (or its manifest). - 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.
- Add push-to-file annotations:
The initial implementation and testing will be limited to:
-
Authentication containers to be tested:
- Secrets Provider init container
- Use existing tests for environment-variable-based configuration.
- Add new tests for annotation-based configuration, including Push-to.File functionality.
- Secrets Provider standalone Pod
- Use existing tests for environment-variable-based configuration.
- Add new tests for annotation-based configuration, excluding Push-to.File functionality.
- Secrets Provider init container
-
Platforms:
- Kubernetes (this will be either Kubernetes-in-Docker, or GKE).
- OpenShift 4.8
-
Scale:
Scaling limits for the M1 Push-to-File feature will remain at the current limits defined for the Secrets Provider:
- Support for up to 50 DAP secrets per Secrets Provider init container, where the variable paths are, on average, 100 characters.
-
For this release, configuration will only be read upon startup.
-
For this release, push-to-file annotations are required to have a group.
-
For this release, support for custom templates will only include support for the standard, built-in Go template functions. In future releases, we will consider adding support for things like:
- Functions based on the standard
Go
strings
module. - Functions in the
sprig
template function library.
Adding support for functions in these modules would allow for use of functions such as
ToLower
,Repeat
,Replace
,TrimRight
, etc. Adding support for these functions would require explicitly including each desired function in a Go templatefuncMap
, after doing a Security analysis to assure that the function is safe. Essentially, we would be implementing an "allow list" on a per-function basis. - Functions based on the standard
Go
As shown in the flow diagram above, the Secrets Provider will need to parse all Pod annotations, and compile an array of per-group secrets mapping information. The structure below portrays the information that will need to be gathered.
Note that this structure is shown for conceptual purposes; the names of fields used in the actual code may change through the development process:
// SecretSpec specifies a secret to be retrieved from Conjur by defining
// its alias (i.e. the name of the secret from an application's perspective)
// and its variable path in Conjur.
type SecretSpec struct {
Alias string
Path string
}
// SecretGroup incorporates all of the information about a secret group
// that has been parsed from that secret group's Annotations.
type SecretGroup struct {
Name string
FilePath string
FileTemplate string
FileFormat string
PolicyPathPrefix string
FilePermissions os.FileMode
SecretSpecs []SecretSpec
}
// SecretGroups comprises secrets mapping info for all secret groups
var SecretGroups map[string]SecretGroup{}
-
When Secrets Provider container starts up, it has Pod annotations available in a file named
annotations
in a Pod info volume. Each entry in this file will be a YAML key/value pair, and can be either:- Secrets Provider container/application configuration
- Push-to-file configuration
-
Secrets Provider parses the YAML in the
annotations
file, and iterates over all entries.-
For SP container/application configuration, values are written to the SP
Config
structure. -
For Push-to-File configuration, each annotation key will be parsed and split into three fields:
-
Annotation type (e.g.
conjur-secrets
,conjur-secrets-policy-path
, etc) -
Secret group
-
Annotation value. The annotation value is a string that can be any of the following formats:
- Plain string
- YAML list of secrets
- Secrets file Golang template
-
-
If the annotation value is a YAML list of secrets, the SP will then iterate over each entry in the YAML list. Each entry is parsed into two fields:
- Conjur variable path
- Secret alias. If not provided explicitly, then the last word in the Conjur variable path is used as an alias.
-
Depending upon the annotation type, values are then written to a per-Group
SecretGroup
structure (see definition in the Per-Group Secrets Mapping section above.
-
-
After all annotations have been processed, Secrets Provider iterates through the
SecretGroup
data structures to compile a complete set of secrets (over all secret groups) to be retrieved from Conjur. The Secrets Provider then connects/authenticates with Conjur and retrieves all secret values. -
The Secrets Provider iterates again through all
SecretGroup
data structures to render and write a secret file for each group as follows:- If a secrets file Golang template has not been provided explicitly, Secrets Provider will use a "canned" Golang template based on the secrets file format (YAML, JSON, dotenv, or bash).
- Secrets Provider connects/authenticates with Conjur and retrieves all secrets for that secret group.
- Secrets Provider then renders and writes a secrets file at the configured destination file path.
From a high level, the performance of the Secrets Provider running in push-to-file mode will be a measure of how fast the Secrets Provider can perform the following as an init container:
- Parse Pod annotations
- Retrieve secrets for all secret groups from Conjur
- Write those secrets to a volume that is shared with the application container
To maximize the performance of Conjur secrets retrieval, the Secrets Provider will retrieve secrets from Conjur using batch retrievals on a per-secrets-group basis. In addition, retrieval of secrets from Conjur will be done using parallel Goroutines (with the maximum number of Goroutines allowed TBD). It is therefore expected that the throughput in terms of the rate of secrets that the Secrets Provider can retrieve from Conjur should not differ significantly for Push-to-File mode versus Kubernetes Secrets mode.
For performance monitoring, the M1 push-to-file feature will add a metrics HTTP endpoint that can be scraped by a Prometheus server. This includes support for the following latency metrics:
- Latency (processing time) for parsing Pod annotations
- Latency for generating secrets requests and processing responses
- Latency for writing retrieved secrets to files
As described in the Performance testing section below, performance will be measured using a representative configuration in terms of the number of secret groups and the number of secrets per secret group.
For backwards compatibility, the Secrets Provider must continue to support the environment-variable based configuration for container and Conjur configuration for the Kubernetes Secrets mode of operation. For push-to-file mode, only annotation-based configuration will be tested and supported.
This feature affects the Secrets Provider component, for both init/sidecar operation and standalone (application) mode. No other components will be affected.
To validate support, E2E test cases will need to be run against different versions of
- Kubernetes
- Openshift
Unit tests require
- Assertion library (stretchr/testify)
- Mocks for Conjur server, parser, secret fecher, and file writer
- Code is dependency injectable
- Environment with all the dependencies loaded. A container image is typically defined that contains all the dependencies for running the tests (Go + tooling) and any postprocessing of results (junit)
E2E tests require
- Kubernetes/Openshift cluster
- Conjur setup and ready. Policy will need to be run to define an authenticator, secrets and permissions.
- Secrets provider image is available to Kubernetes/Openshift cluster
In the following table, the entries in the "User Stories" column refer to user stories as defined in the M1 Feature Document.
Section | Given | When | Then | User Stories | |
---|---|---|---|---|---|
1 | Secrets Provider container can be configured using annotations | You provide SP config as annotations | Parsing is carried out | The internal SP config is set base on annotations | US-03 |
2 | Annotation configuration takes precedence over environment variables | You provide the annotations and environment variables | Parsing is carried out | The internal representation of the parsed configuration prioritised values from the annotations | US-03 |
3 | Parsing of Conjur secrets list | Valid "conjur-secrets" list annotation with aliases | Parsing is carried out | The internal representation matches expectations | US-05 |
4 | Parsing of Conjur secrets list | Valid "conjur-secrets" list annotation without aliases | Parsing is carried out | The internal representation matches expectations | US-05 |
5 | Parsing of Conjur secrets list | Valid "conjur-secrets" list annotation with mix of entries with/without aliases | Parsing is carried out | The internal representation matches expectations | US-05 |
6 | Parsing of Conjur secrets list | Empty list defined for "conjur-secrets" list annotation (allowed for custom template without secrets) | Parsing is carried out | No errors encountered | |
7 | Parsing of Conjur secrets path | Valid "conjur-secrets" list and "conjur-secrets-policy-path" annotations | Parsing is carried out | The internal representation matches expectations | US-06 |
8 | Parsing multiple secret group annotations | Annotations for multiple secret groups with different (including default) file formats | Parsing is carried out | The annotation are divided according to their groups in the internal representation | US-04 |
9 | Default value for relative secrets file path | "secret-file-path" annotation not configured | Parsing is carried out | The secret file is written to /conjur/secrets/{secret-group}.{secret-file-format} |
US-07 |
10 | Parsing of relative secrets file path | "secret-file-path" annotation set to subdirectory (no filename) | Parsing is carried out | The secret file is written to /conjur/secrets/{secret-file-path}/{secret-group}.{secret-file-format} |
US-07 |
11 | Parsing of relative secrets file path | "secret-file-path" annotation set to subdirectory + filename | Parsing is carried out | The secret file is written to /conjur/secrets/{secret-file-path} |
US-07 |
12 | Parsing of relative secrets file path | "secret-file-path" annotation set to subdirectory + filename with extension not matching file format | Parsing is carried out | The secret file is written to /conjur/secrets/{secret-file-path} |
US-07 |
13 | Default value for secrets file format | "secret-file-format" annotation is not set | Parsing is carried out | The default secrets file format set to yaml |
US-08 |
14 | Parsing of secrets file format | Valid "secret-file-format" annotation (yaml , json , dotenv , bash , or [for future support] plaintext ) |
Parsing is carried out | The internal representation matches expectations | US-08 |
15 | Parsing of custom secret file template | "secret-file-template" annotation referencing secrets by name (alias) | Parsing is carried out | The internal representation matches expectations | US-09 |
16 | Parsing of custom secret file template | "secret-file-template" annotation iterating over all secrets | Parsing is carried out | The internal representation matches expectations | US-09 |
17 | Parsing of custom secret file template | "secret-file-template" annotation using built-in template functions (e.g. printf or html ) |
Parsing is carried out | The internal representation matches expectations | US-09 |
18 | Parsing of custom secret file template | "secret-file-template" annotation using custom functions with nested templates | Parsing is carried out | The internal representation matches expectations | US-09 |
19 | Batch retrieval of secrets | A mock for Conjur server batch retrieval exists | Fetch secret logic is run | Returns expected payload | |
20 | Table test for generation of file formats | Some mock secret file data | File generation code is executed for various file formats | The generated files match the format | US-08 |
21 | File creation with custom secret file template | "secret-file-template" annotation referencing secrets by name (alias) | File created | The file contents match expectations | US-09 |
22 | File creation using custom secret file template | "secret-file-template" annotation iterating over all secrets | File created | The file contents match expectations | US-09 |
23 | File creation using custom secret file template | "secret-file-template" annotation using built-in template functions (e.g. printf or html ) |
File created | The file contents match expectations | US-09 |
24 | Helm test using Named Templates as Chart Dependency | SP init container and Volume named templates are published | 'helm template' is run on a sample deployment Helm chart that uses named templates as a Helm dependency | The generated deployment manifests is as expected | US-01 |
25 | Prometheus metrics endpoint testing | The SP is configured to create a sampling of secrets files | The metrics endpoint is queried for latency measurements | The latency metrics in the response are as expected | |
26 | (Future support) File generation for plaintext format using default destination directory |
Some mock secret file data | File generation code is executed for plaintext format without explicit dest directory |
The generated files are as expected | |
27 | (Future support) File generation for plaintext format using explicit destination directory |
Some mock secret file data | File generation code is executed for plaintext format with explicit dest directory |
The generated files are as expected |
Section | Given | When | Then | Error Code | Test Type (Unit, Integration, E2E) | User Story | |
---|---|---|---|---|---|---|---|
1 | Missing secrets destination | You are using SP | You do not provide the secrets destination in an annotation or environment variable | Error: Missing secrets destination. Acceptable values are "k8s" and "file". SP fails to start |
Unit | ||
2 | Invalid input file format | You are using SP in "file" mode | You have provided an unknown or unsupported type in the secret-file-format annotation | Error: Invalid output file format: "{value}" for secret group "{value}". Acceptable values are "json", "yaml", "bash", "dotenv", and "plaintext". SP fails to start |
Unit | ||
3 | Conjur variable names collision | You are using SP in "file" mode | Two or more secrets in the same Secret Group share the same name or alias. | Error: Multiple variables in the "{value}" secret group are called "{value}". Provide a unique alias for each variable in the "{value}" secret group. SP fails to start |
Unit | ||
4 | Unparseable Conjur secrets list | You are using SP in "file" mode | The "conjur-secrets" annotation is not parseable for one or more secret groups. | Error: The list of secrets for the "{value}" secret group is not formatted correctly. Error: "{value}". Verify that the annotation value is a YAML list with optional keys for each list item. SP fails to start |
Unit | ||
5 | Invalid Conjur secrets path | You are using SP in "file" mode | The "conjur-secrets-policy-path" annotation is not parseable as a full secret path prefix. | Error: The Conjur secrets path "{value}" provided for the "{value}" secret group is invalid. | Unit | US-06 | |
6 | Conjur variable path too long | You are using SP in "file" mode | The "conjur-secrets" annotation contains variable path(s) that are too long for one or more secret groups. | Error is displayed which includes all improper variable paths. SP fails to start |
Unit | ||
7 | Secret alias for YAML secrets file too long | You are using SP in "file" mode for YAML format | The "conjur-secrets" annotation contains a variable alias that is longer than 1024 characters. | Error is displayed which includes improper variable alias. SP fails to start |
Unit | ||
8 | Rendered file content for JSON secret file too long | You are using SP in "file" mode for JSON format | The rendered file content is longer than the max allowed for a JSON document (2097152 chars, equivalent to 4 MB Unicode string). | Error is displayed which includes secret group and rendered content length. SP fails to start |
Unit | ||
9 | Secret alias for bash or dotenv format with invalid characters |
You are using SP in "file" mode for bash or dotenv format |
The "conjur-secrets" annotation contains a variable alias that includes invalid characters for an environment variable name. | Error is displayed which includes improper variable alias. SP fails to start |
Unit | ||
10 | Invalid file template definition – not parseable by Go | You are using SP in "file" mode | File template for one or more secret groups cannot be used as written. | Error: The file template for the "{value}" secret group cannot be used as written. Error: "{value}". Update your template definition to address this and try again. SP fails to start |
Unit | US-09 | |
11 | Invalid file template definition – references undefined secret keys | You are using SP in "file" mode and have provided a value for the conjur.org/secret-file-template annotation |
File template for one or more secret groups references secrets that do not exist in the secret group. | Error: The file template for the "{value}" secret group references the "{value}" secret, but no such secret is defined in the secret group. Add an alias to set the correct secret name to "{value}", and try again. SP fails |
Unit | US-09 | |
12 | Unable to retrieve Conjur variables | You are using SP in "file" mode | Error: Failed to provide DAP/Conjur secrets SP fails |
CSPFK016E | Unit | ||
13 | Missing volume mounts | You are using SP in "file" mode | One or more volumes cannot be found by Secrets Provider. | Error: Unable to access volume "{value}". Error: "{value}". Ensure that the correct volumes are defined in the deployment manifest and attached to the Secrets Provider container. SP fails to start |
Unit | US-07 | |
14 | Missing Conjur variable value while rendering custom template file content | You are using SP with a custom secrets file template | One or more variables used in a custom template are not included in secrets list. | Error displays missing variable name, and must not include sensitive secrets values. SP fails |
Unit | ||
15 | Sanitize errors while rendering file content | You are using SP in "file" mode | An error is encountered while rendering secrets file content. | Errors must not include sensitive secrets values. SP fails |
Unit | US-07 | |
16 | File permissions error | You are using SP in "file" mode | Secrets Provider does not have permission to write a secrets file to the specified volume and mount path. | Error: Secrets Provider does not have permission to write the secrets file "{value}". Please check the volume mount permissions on the Secrets Provider container and try again. SP fails |
Unit | US-07 | |
17 | Out of disk space error | You are using SP in "file" mode | Secrets Provider does not have sufficient disk space to write a secrets file. | SP displays error as reported by operating system. SP fails |
Unit | US-07 | |
18 | Invalid file path provided | You are using SP in "file" mode and have provided a value for the conjur.org/secret-file-path annotation |
The provided file path is not a valid file path. | Error: You have provided an invalid file path "{value}" for the "{value}" secret group. SP fails to start |
Unit | US-07 | |
19 | File conflict between two secret groups | You are using SP in "file" mode and have defined multiple secret groups | One or more secret groups have the same ouput file. | Error: You have provided conflicting file paths; secret groups "{value}", "{value}", ... all have output file "{value}". SP fails to start |
Unit | US-07 | |
20 | Template provided but no output file defined | You are using SP in "file" mode and have provided a file template definition | You have not also provided an output file path. | Error: The output file path is missing for the "{value}" secret group. File path is required when using a custom template. SP fails to start |
Unit | ||
21 | Invalid retry annotation settings | You are using SP in "file" mode and have provided retry configuration as annotations | Retry settings re invalid. | Error is generated indicating incorrect setting(s). SP fails to start |
Unit | ||
22 | Conjur retrieval fails and retry is required | You are using SP in "file" mode and have provided retry configuration as annotations | Conjur retrievals fail file path. | Secrets Provider retries up the the retry count limit. SP fails to start |
Integration | ||
23 | (Future support) Invalid file path for plaintext format |
You are using SP in "file" mode, you are using plaintext secrets path format, and have provided a value for the conjur.org/secret-file-path annotation |
The provided file path is not a directory. | Error: The provided secret file path for the {value} secret group is not a directory. You must provide a directory for the "plaintext" secret file format. SP fails to start |
Unit |
E2E tests are expensive. We should try to limit their numbers. There are cheaper ways to validate behavior. For the push to file functionality it will likely be possible to validate the happy path in one fell swoop by using configuration tht captures all the supported annotations and template types.
- Happy path, Secret Provider runs as an init container in file mode and successfully injects files. This single test can cover all supported annotations and template types.
The sad path is where things tend to become difficult, because there's a myriad of ways in which failure can occur. A possible way to reduce the need to E2E test failures is to establish a nexus point for all failures in the code so that an error can be validated at the unit/functional testing level.
- Sad path, Secret Provider runs as an init container in file mode and fails, thereby preventing the deployment from proceeding. TBC it's possible we don't need this at all.
Security testing will include:
- Automated Vulnerability scans
As described in the Performance section above, the M1 push-to-file feature will add support for an HTTP metrics endpoint that can be used to collect the following metrics:
- Latency (processing time) for parsing Pod annotations
- Latency for generating secrets requests and processing responses
- Latency for writing retrieved secrets to files
The measurements will be made using a representative configuration in terms of the number of secret groups and the number of secrets per secret group. Details are TBD, but a representative configuration might look something like this:
- 5 secret groups
- 5 secrets / secret group
- Mix of secret value lengths (10 to 100 characters, plus some base64-encoded TLS certificates)
This profile represents about half of the 50 Conjur secrets per Secrets Provider init container scaling limit as described in the Project Scope and Limitations section below.
For "happy path" operation, the M1 Push-to-File feature will add INFO level logs to report status at least for significant checkpoints in the Push-to-File workflow:
- Start of annotation parsing
- Completion of annotation parsing
- Start of validation for each secret group
- Start of secrets retrieval for each secret group
- Start of custom template parsing (if applicable) for each secret group
- Start of file rendering for each secrets file
- Start of file writing for each secrets file
- Completion of file writing for each secrets file
For error conditions, the Secrets Provider will include ERROR-level logs. Error messages for each error condition are described in the Error Handling / Recovery / Supportability Tests table below.
Some general comments that can be made about error logging:
- Annotation values are treated as non-sensitive and will not be redacted in log messages.
- For forward compatability (i.e. future-proofing), annotation keys with
the prefix
conjur.org
but with an unexpected annotation type will be logged atInfo
level as unknown annotation type, but otherwise will be ignored. - Care will be taken to prevent leaking of sensitive secrets values during the rendering of secrets file content as described in the Preventing Leaking of Sensitive Information in Error Logs section below.
Documentation for the M1 Secrets Provider Push to File feature will include:
- Update the Secrets Provider init container configuration documentation.
- Update the Secrets Provider application container configuration documentation.
- Add new documentation to describe the push-to-file annotation configuration
- Add documentation for the process to upgrade from the Secrets Provider legacy (environment variable based) configuration to the annotation-based configuration.
The M1 Push-to-File feature will necessitate a minor version bump for the Secrets Provider. Because configuration will maintain backwards compatibility, it is not expected that a major version bump will be required.
Since the M1 Push-to-File feature involves writing sensitive information to secrets files that are shared by an application container, we need to consider whether the secrets files are being created with the appropriate access permissions.
By default, the Secrets Provider will create secrets files with the following file attributes:
Attribute | Value | Notes |
---|---|---|
User | secrets-provider |
|
Group | root |
See Why Use The root Group? |
UID | 777 |
|
GID | 0 |
See Why Use The root Group? |
Permissions | rw-rw-r-- |
See Why Use World-Readable Permissions? |
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),
as described in this
Guide to OpenShift and UIDs blog.
The Secrets Provider uses a GID of 0
for secrets files even for
non-OpenShift platforms, for simplicity.
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.
This permissions value is used to allow for other containers in a non-OpenShift platform and in the same Pod as the Secrets Provider to be run with any arbitary user UID and GID and still be able to read the secrets files, even if the UID and GID do not match those of the secrets files that have been created by the Secrets Provider.
Note that this potential for permissions/user mismatch is not a concern in OpenShift platforms. As described in this blog, OpenShift avoids the potential for UID/GID mismatches between containers in a Pod by:
- Running all containers using an arbitrary (assigned at run time), unprivileged, non-root user.
- Running all containers using a GID of
0
(a.k.a. theroot
GID). - Requiring that all Volumes to be written have permissions that allow
writing and executing by anyone on the common group (i.e. GID of
0
).
From a security perspective, making secrets files world-readable throughout
the Pod (for the sake of non-OpenShift platforms) is no more permissive than
the OpenShift approach, whereby files created in a shared Volume are
readable from all containers in the same Pod that mount that Volume, because
the containers are run with the common GID of 0
.
An alternative to creating the secrets files with world-readable permissions that had been considered is:
-
Create secrets files with permissions
rw-rw----
(octal660
). -
Document how users can run their application Pods with arbitrary UID and GID and still be able to read secrets files in shared Volumes by including PodSecuritContext definitions in their application manifests to override, among other things (see the PodSecuritContext for a complete list):
- Run-time UID for all containers in a Pod (
runAsUser
) - Run-time GID for all containers in a Pod (
runAsGroup
) - GID used for certain Volume types (including emptyDir Volumes) and all
files created in those Volumes (
fsGroup
)
For example, the following configuration can be included in an application Deployment's Pod specification to configure all containers in the Pod to run with UID of
65534
(i.e. thenobody
user) and a GID of65534
(i.e. thenobody group), with all files that are created in
emptyDirVolumes having an owner GID of
65534`:securityContext: runAsUser: 65534 runAsGroup: 65534 runAsNonRoot: true fsGroup: 65534
- Run-time UID for all containers in a Pod (
The disadvantage of this approach is that it would need to be heavily documented, and could be prone to user error.
When secrets 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.
When secrets files are being created using the yaml
, json
, bash
, or
dotenv
file formats, leaking of sensitive secrets values will be prevented
by using the following workflow for rendering each secrets file:
- Retrieve all secrets values for a secret group from Conjur, and populate those values in a secrets file information array.
- Iterate through all entries in the secrets file info array.
For each entry:
- Attempt to render the line of output for that secrets setting, redirecting
standard error to
/dev/null
. - If an error occurs, print a sanitized error to standard out, excluding the sensitive secret value, but including the secret alias and Conjur variable path.
- Attempt to render the line of output for that secrets setting, redirecting
standard error to
- If rendering is successful for all secrets values, write the rendered
output to
/conjur/secrets/{secrets-file}
.
In general, processing of a Golang template is done in two steps:
- Parse the template
- Execute the template to render output text, using a map of values to be used for rendering
For the Secrets Provider push-to-file feature, the first step does not involve handling of sensitive secrets retrieved from Conjur, so parsing errors can be included in container logs without any sanitizing.
However, the second step involves the rendering of secrets file content using sensitive secrets information that has been retrieved from Conjur. Depending upon the custom template provided by the user, it is entirely possible that the Go template execution could produce errors that include this sensitive information. To prevent these error messages from leaking into container logs while the custom template is being executed, the Secrets Provider will process custom templates in three steps:
- Parse the template
- Do a "test" execution of the template to render "dummy" secrets file
content, using a map of dummy values to use for rendering, as follows:
- Dummy values will be included in this values map for all secrets that are defined in the corresponding secrets list.
- The dummy values will be strings that contain the word "REDACTED".
- All errors that occur during the "test" template execution will be redirected to standard error with no sanitizing, so that the errors will be available in Secrets Provider container logs.
- This "test" execution will be able to detect what will probably be the most likely user misconfiguration error when custom templates are used: A custom template uses a Conjur secret that is not included in the corresponding secrets list for that secret group.
- Execute the template to render secrets file content, while redirecting
all "raw" output to
/dev/null
. If any errors occur, a non-specific error message will be generated that includes the custom template definition, but no secrets values.
The last step will hide explicit template execution errors from the user in the very rare cases where the custom template being used is sensitive to actual secrets values (for the sake of security). It is expected that in these rare cases, the user will be able to troubleshoot the failure "out of band" by processing the custom template on a local machine, using secrets values retrieved from Conjur to render secrets file content.
Some alternatives to this approach that we considered include:
- During template execution, redirect all output to a log file that
resides in an ephemeral (i.e.
emptyDir
) volume. If errors occur, a privileged user with sufficient permissions tokubectl exec ...
inside the Secrets Provider container would be able to read the template execution errors. - During template execution, log all errors at the DEBUG logging level. Template execution errors would be hidden unless the user has sufficient authorization to enable DEBUG logging level for the Secrets Provider.
- Implement another annotation that can be used (on an "opt-in" basis) to enable inclusion of possibly sensitive execution error messages in Pod/container logs.
No changes to Conjur audit behavior are required for this feature.
Development tasks for this feature are organized into tasks corresponding to three phases of development:
- Minimally-featured community release
- Minimally-featured GA release
- Full-featured GA release
- Solution design approval
- Security review approval
- Community and Integrations team ramp-up for becoming familar with existing Secrets Provider testing (unit, integration, E2E)
- Refactor SP config: Separate SP config into the following:
- Container config:
- PodName - MY_POD_NAME
- PodNamespace - MY_POD_NAMESPACE
- RetryCountLimit - RETRY_COUNT_LIMIT
- RetryIntervalSec - RETRY_INTERVAL_SEC
- StoreType – SECRETS_DESTINATION
- Kubernetes Secrets config:
- RequiredK8sSecrets – K8S_SECRETS
- Container config:
- Existing SP config options can be set by annotations
- Container config:
- PodNamespace - MY_POD_NAMESPACE (still comes from downward API)
- RetryCountLimit -
conjur.org/retry-count-limit
- RetryIntervalSec -
conjur.org/retry-interval-sec
- StoreType -
conjur.org/secrets-destination
- Kubernetes Secrets config:
- RequiredK8sSecrets –
conjur.org/k8s-secrets
- RequiredK8sSecrets –
- Container config:
- Refactor Authn Client config: Separate Authn Client config into the following:
- Container config
- Conjur config
- App identity config
- Existing Authn Client config options can be set by annotations:
- Container config
- Conjur config
- App identity config
- Define data structures for annotation parsing
- Given list of secrets with aliases, populate data structure(s)
- Add logic to parse annotation keys: Split annotation keys into:
- secret group name
- Push-to-File Annotation type
- Annotation value (can be a YAML formatted string)
- Add logic to parse annotation values:
- Parse
conjur.org/conjur-secrets.{secret-group}
(with aliases)
- Parse
- Add logic to parse annotation keys: Split annotation keys into:
- Given populated data structure, get secrets and write to YAML file
- SP upgrade process has been validated - init container, env to annotations-based config, k8s secrets to push-to-file
- SP upgrade process has been validated - job, env to annotations-based config
- Dap-wiki docs have updated info on new flows - initial community release
- Custom upgrade instructions are documented (upgrade application to use SP with annotations and push-to-file)
- Quick start for SP in file mode, secrets with aliases, YAML secrets file
- UX has been reviewed and issues have been corrected (file mode, secrets aliases, YAML secrets file)
- SP is release w/manual security scans
- Support for Prometheus metrics endpoint for latency metrics is added and tested
- Pet Store demo app can get config via input file
- Happy path e2e test with SP init & annotation-based config: K8s secrets
- Happy path e2e test with SP job & annotation-based config
- Happy path e2e test with SP init & annotation-based config: file
- Dap-wiki docs have updated info on new flows - GA release
- Basic troubleshooting
- Custom upgrade instructions are documented
- Instructions for updating annotations (e.g. restarting Pod) are documented
- Documents have clear instructions for volume mapping and file permissions
- Performance of SP with the file flow has been measured
- Final security review has been performed
- Add support for secrets without aliases
- Add support for supplying secrets path prefix
- Add support for specifying JSON file format and output to file
- Add support for specifying (non-default) output file path/name
- Add support for Bash export output
- Add support for dotenv export output
- Add support for templated file output
- Provide named Helm template for SP init container def
- Provide named Helm template for SP volumes
- Blog post
- Recorded demo
- Training session
The Definition of Done criteria for this feature are organized into tasks corresponding to three phases of development:
- Minimally-featured community release
- Minimally-featured GA release
- Full-featured GA release
- All items in the Development Tasks: Minimally-Featured Community Release section will be implemented with unit tests.
- SP upgrade process has been validated for existing users
- Dap-Wiki has been updated with info on the new flows (initial community release)
- Any custom upgrade instructions are documented
- There is a quick start environment for running SP in "file" mode locally
- The UX of the feature has been reviewed
- SP has been released with the new "file" functionality, including manual security scans
- All items in the Development Tasks: Minimally-Featured GA Release section for the will be implemented with unit tests.
- Pet store demo app supports getting its database configuration via input file.
- There is a happy path e2e test in the Secrets Provider test cases to validate using the K8s Secrets init container with annotation-based configuration
- There is a happy path e2e test in the Secrets Provider test cases to validate using the K8s Secrets Job with annotation-based configuration
- There are e2e tests in supported OpenShift versions and GKE with Secrets Provider init container in "file" mode running with:
- Conjur Open Source
- Conjur Enterprise with follower in Kubernetes
- Conjur Enterprise with follower outside Kubernetes
- Init container and Job flows have both been tested
- There is documentation collateral for the TWs
- Dap-Wiki has been updated with info on the new flows
- There is basic troubleshooting information available
- Any custom upgrade instructions are documented
- Instructions for restarting pod on annotations changes have been documented
- Documentation is clear about requirements for volume mapping and file permissions
- Performance of the SP with the "file" flow supported has been measured
- SP has been released with the new "file" functionality, including manual security scans
- All high and critical automated scanner issues and known high and critical bugs have been addressed
- All items in the Deployment Tasks: Full-Featured (Certified) Community Release section will be implemented with unit tests.
- There is a happy path e2e test in the Secrets Provider test cases to validate using annotation-based configuration without secrets aliases
- There is a happy path e2e test in the Secrets Provider test cases to validate using secrets path prefix
- There is a happy path e2e test in the Secrets Provider test cases to validate writing of JSON formatted file
- There is a happy path e2e test in the Secrets Provider test cases to validate using an output file path/name
- There is a happy path e2e test in the Secrets Provider test cases to validate writing of Bash formatted file
- There is a happy path e2e test in the Secrets Provider test cases to validate writing of dotenv formatted file
- There is a happy path e2e test to validate Helm named templates for init container def, volume mounts, and volumes
- There is a blog post describing how to transition from an existing application to using the new Secrets Provider "push to file" flow
- There is a recorded demo of the new SP "file" functionality
- The new SP "file" functionality has been shared at a training session
- Add support for plaintext export output
- Add ability to configure secret file permissions via conjur.org/secret-file-perms.{secret-group} annotation
Persona | Name | Design Approval |
---|---|---|
Team Leader | Dane LeBlanc | ✅ |
Product Owner | Alex Kalish | ✅ |
System Architect | Rafi Schwarz | ✅ |
Security Architect | Andy Tinkham | ✅ |
QA Architect | Andy Tinkham | ✅ |
Below is an example of an application Deployment manifest that uses:
- Conjur secrets annotations.
- References a partial Helm named template for a Secrets Provider init container with volume mounts.
- References a partial Helm named template for Secrets Provider volumes.
---
##########################################
### K8s Client App Deployment Manifest ###
##########################################
# This is an example deployment manifest intended to demonstrate the UX. It
# defines a fictitious app representing the customer workload that manages
# inventory through an API.
#
# Comments have been added to sections related to this integration. They often
# include the following structured tags:
# - boilerplate: yes indicates this is literal copy/paste into manifest, or,
# alternatively, included via Helm.
# - sidecar-injected: yes indicates this section will be added by sidecar
# injector in a later milestone.
apiVersion: apps/v1beta1
kind: Deployment
metadata:
labels:
app: inventory-api
name: inventory-api
spec:
replicas: 1
selector:
matchLabels:
app: inventory-api
template:
metadata:
labels:
app: inventory-api
annotations:
##########################
### Conjur Annotations ###
##########################
# Annotations are now used to both provide some basic configuration
# details (i.e. host ID) and configure exactly which secrets to pull
# from Conjur and to which files they should be written. To start, they
# need to be defined on the Pod. Later, with the admission controller,
# they can be moved to the controller resource (e.g. the deployment
# here).
# - boilderplate: no
# - sidecar-injected: no
# Host ID for authenticating with Conjur. This is traditionally named
# CONJUR_AUTHN_LOGIN in most clients, but this new name is more
# obvious.
conjur.org/authn-identity: host/conjur/authn-k8s/cluster/apps/inventory-api
# Core config for SP, instructing it how to operate.
# Could have defaults?
conjur.org/container-mode: init
conjur.org/secret-destination: file
# This maps to the existing DEBUG environment variable.
conjur.org/debug-logging: true
# Define variables for the unique group "database". For reference,
# annotations for secrets are in the form,
# "conjur.org/<parameter-name>.<group>". This is the most basic way to
# configure the secrets file. The file name is assumed using the group
# name and the default type (i.e. YAML) is used. Thus, this would
# result in a file at /opt/secrets/conjur/database.yml with contents:
# url: <value>
# port: <value>
# password: <value>
# username: <value>
# Notice that the variable name is used without the path.
conjur.org/conjur-secrets.database: |
- /policy/path/to/url
- /policy/path/to/port
- /policy/path/to/password
- /policy/path/to/username
# Define variables for the unique group "cache". This example
# demonstrates how variables can be aliased, if aplications secrets to
# be named differently than the vault. Here, "admin-password" is the
# secret name written to the file and "password" is the Conjur
# variable.
conjur.org/conjur-secrets.cache: |
- url
- admin-password: password
- admin-username: username
# For Conjur variables with very long policy branches, a path prefix
# can be provided that will apply to all variables inside the group.
# For example, "url" above will actually be found at
# "/very/long/conjur/policy/path/url".
conjur.org/conjur-secrets-policy-path.cache: /very/long/conjur/policy/path
# The file path and name can be customized from the default.
conjur.org/secret-filepath.cache: /files/cache-config.json
# Additionally, instead of outputting the default YAML format, "json"
# or "bash" can be specified as named templates. JSON would look
# like:
# {
# url: <value>
# admin-password: <value>
# admin-username: <value>
# }
# And bash (aka environment variables) would look like:
# export url="<value>"
# export admin-password="<value>"
# export admin-username="<value>
conjur.org/secret-template.cache: json
# Finally, users can provide completely customized secret file
# templates using the Golang templating language. Variables can be
# referenced via their name (or alias if provided) and are replaced
# with their value from Conjur.
conjur.org/secret-template-custom.cache: |
{
"cache": {
"url": {{ .url }},
"password": {{ .admin-password }},
"username": {{ .admin-username }},
"port": 123456
}
}
spec:
serviceAccountName: inventory-api-service-account
containers:
- image: my_company/inventory-api
imagePullPolicy: Always
name: inventory-api
ports:
- containerPort: 8080
volumeMounts:
############################
### Secrets Volume Mount ###
############################
# Mount the volume containing secrets to the app container. Developer
# can customize the "mountPath" within the volume.
# - boilerplate: no
# - sidecar-injected: yes
- mountPath: /opt/secrets/conjur
name: conjur-secrets
readOnly: true
initContainers:
#######################################
### Secrets Provider Init Container ###
#######################################
# Defines the SP init container. The example below uses a Helm named
# template via "includes" to avoid copy/paste.
# - boilerplate: yes
# - sidecar-injected: yes
{{ include "conjur-secrets-provider.init-container" . | indent 6 }}
volumes:
######################
### Secrets Volumes ##
######################
# Define the volume to which secret files will be written. Also,
# define a downward API volume from which SP will read pod info and
# annotations that contain secret nd configuration information. The
# example below uses a Helm named template via "includes" to avoid
# copy/paste.
# - boilerplate: yes
# - sidecar-injected: yes
{{ include "conjur-secrets-provider.volumes" . | indent 8 }}
Below is an example of a Helm partial named template for a Secrets Provider init container with volume mounts (as referenced in the Deployment manifest above):
{{/* Deployment manifest init containers for Secrets Provider */}}
{{- define "conjur-secrets-provider.init-container" -}}
- image: {{ .Values.conjur.secretsProvider.image }}
imagePullPolicy: Always
name: conjur-secrets-provider
volumeMounts:
- mountPath: /conjur/secrets
name: conjur-secrets
- mountPath: /conjur/connect
name: conjur-connect
- mountPath: /conjur/podinfo
name: conjur-podinfo
{{- end -}}
Below is an example of a Helm partial named template for Volumes used by a Secrets Provider init container (as referenced in the Deployment manifest above):
{{/* Deployment manifest volumes for Secrets Provider */}}
{{- define "conjur-secrets-provider.volumes" -}}
- name: conjur-secrets
emptyDir:
medium: Memory
- name: conjur-connect
configMap:
name: {{ .Values.conjur.configMaps.connect | default "conjur-connect" }}
- name: conjur-podinfo
downwardAPI:
items:
- path: annotations
fieldRef:
fieldPath: metadata.annotations
- path: MY_POD_NAME
fieldRef:
apiVersion: v1
fieldPath: metadata.name
- path: MY_POD_NAMESPACE
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
- path: MY_POD_IP
fieldRef:
fieldPath: status.podIP
{{- end -}}
Below is an example Helm chart values.yaml
file corresponding to the
manifest / Helm named tempates above:
conjur:
volumes:
secrets:
name: "conjur-secrets"
mountPath: "/conjur/secrets"
connect:
name: "conjur-connect"
mountPath: "/conjur/connect"
podinfo:
name: "conjur-podinfo"
mountPath: /conjur/podinfo"
configMaps:
connect: "conjur-connect-configmap"
secretsProvider:
name: "cyberark-secret-provider-for-k8s"
image: "cyberark/cyberark-secret-provider-for-k8s:2.0.0"
Below is an example Conjur policy corresponding to the example manifest and Helm chart named templates above:
---
### Conjur Policy ###
# This is an example set of Conjur policy intended to demonstrate the UX.
# It defines a policy with sample inventory API client workload host and
# database credentials.
- !policy
id: k8s-cluster-apps
annotations:
description: Apps and services in company cluster.
body:
- !layer k8s-apps
- &apps
- !host
id: inventory-api
annotations:
authn-k8s/namespace: my_namespace
authn-k8s/authentication-container-name: cyberark-secrets-provider-for-k8s
- !grant
role: !layer k8s-apps
members: *apps
- &database-variables
- !variable url
- !variable port
- !variable password
- !variable username
- !permit
role: !layer k8s-apps
privileges: [ read, execute ]
resources: *database-variables