We support several methods for integrating applications with Conjur in a Kubernetes setting. Each integration uses a different representation to map Conjur (or other provider) content to application-specific secrets. The integrations being considered here:
- Secrets Provider for K8s
- Secrets Provider for K8s with Write-to-File (Proposed design)
- Secretless Broker
- Authn-k8s client + Summon
- Authn-k8s client + Conjur Client Library
For the Secrets Provider, the mapping of Conjur secrets to application secrets is done as part of a stringData
field within a Kubernetes secret manifest:
---
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
DBName: bXlhcHBEQg==
stringData:
conjur-map: |-
username: db_credentials/username
password: db_credentials/password
Note: The difference between the data
field and the stringData
field in Kubernetes secrets manifests is
that data
is expected to used base64-encoded values, whereas stringData
values are written in plain text
(but are base64-encoded by Kubernetes when the Secret is created).
Limitation: The Secrets Provider supports fetching up to 50 DAP secrets where the variable paths of the secrets are, on average, 100 characters. To handle more than 50 DAP secrets, you can set up multiple Secrets Providers.
An application pod that consumes the secrets that are populated by the Secrets Provider e.g. by using a volume mount:
---
apiVersion: v1
kind: Pod
metadata:
name: alpine
labels:
name: alpine
spec:
containers:
- name: alpine
image: alpine
command: ["sh", "-c", "tail -f /dev/null"]
volumeMounts:
- name: db-creds
mountPath: "/etc/foo"
volumes:
- name: db-creds
secret:
secretName: db-credentials
defaultMode: 256
In this example, the secrets will be created in a file /etc/foo/conjur-map
:
$ kubectl exec alpine -- sh -c "cat /etc/foo/conjur-map"
username: zappa
password: foobar
$
Reference: cyberark/secrets-provider-for-k8s#250
apiVersion: v1
kind: ConfigMap
metadata:
name: my_config_map
data:
group-name: |-
username: db_credentials/username
password: db_credentials/password
folder-path: "/secrets"
other-group-name: |-
username1: other_credentials/username
password1: other_credentials/password
folder-path: "/mysecrets"
The notable differences between this mapping and the standard secrets mapping for the Secrets Provider for K8s as described in the previous section:
- The mapping is defined in a ConfigMap (
data
section), rather than in Secret (stringData
section) - Secrets are separated into groups
- Each group of secrets has a designated "folder-path"
Reference:
https://docs.secretless.io/Latest/en/Content/Get%20Started/configuration.htm
Each individual service definition provides Secretless Broker with the information it needs to connect to a given target service. Specifically, Secretless needs to know:
- The protocol used by the target service.
- Where to listen for new connection requests.
- Where to get credentials for incoming connections.
- The location of the target service (e.g., where to send requests).
version: "2"
services:
postgres-db:
connector: pg
listenOn: tcp://0.0.0.0:5432
credentials:
host: postgres.my-service.internal
port: 5432
password:
from: conjur
get: id-of-secret-in-conjur
username:
from: env
get: username
config: # this section usually blank
optionalStuff: foo
version: "2"
services:
http_basic_auth:
connector: basic_auth
listenOn: tcp://0.0.0.0:8081
credentials:
username:
from: env
get: BASIC_AUTH_USERNAME
password:
from: env
get: BASIC_AUTH_PASSWORD
config:
authenticateURLsMatching:
- ^http\:\/\/quickstart\/
- ^http\:\/\/localhost.*
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test-app-secretless-config
namespace: app-test
data:
secretless.yml: |
version: "2"
services:
test-app-pg:
protocol: pg
listenOn: tcp://0.0.0.0:5432
credentials:
address:
from: conjur
get: test-secretless-app-db/url
username:
from: conjur
get: test-secretless-app-db/username
password:
from: conjur
get: test-secretless-app-db/password
sslmode: require
test-app-mysql:
protocol: mysql
listenOn: tcp://0.0.0.0:3306
credentials:
host:
from: conjur
get: test-secretless-app-db/host
port:
from: conjur
get: test-secretless-app-db/port
username:
from: conjur
get: test-secretless-app-db/username
password:
from: conjur
get: test-secretless-app-db/password
sslmode: require
When using conjur-authn-k8s-client (either as sidecar or as init container) along with Summon (integrated into the application container):
-
The authn-k8s Client retrieves a host API token, and writes it to a fixed location:
/run/conjur/access-token
(see https://github.com/cyberark/conjur-authn-k8s-client#conjur) -
The application container requires a
secrets.yml
file to be incorporated into the Docker image (see https://github.com/conjurdemos/kubernetes-conjur-demo/blob/master/test_app_summon/Dockerfile#L28-L29) -
The format of the secrets.yml file is as follows (see https://cyberark.github.io/summon/#secrets.yml)
The format is basic YAML with an optional tag. Each line looks like this:
<key>: !<tag> <secret>
key is the name of the environment variable you wish to set. tag sets a context for interpretation:
- !var the value of key is set to the the secret’s value, resolved by a provider given secret.
- !file writes the literal value of secret to a memory-mapped temporary file and sets the value of key to the file’s path.
- !var:file is a combination of the two. It will use a provider to fetch the value of a secret identified by secret, write it to a temp file and set key to the temp file path.
- If there is no tag, is treated as a literal string and set as the value of key. In this scenario, the value in the should not actually be a secret, but rather a piece of metadata which is associated with secrets.
Here is an example:
AWS_ACCESS_KEY_ID: !var aws/$environment/iam/user/robot/access_key_id AWS_SECRET_ACCESS_KEY: !var aws/$environment/iam/user/robot/secret_access_key AWS_REGION: us-east-1 SSL_CERT: !var:file ssl/certs/private
Here
$environment
is an example of a substitution variable, given as an flag argument when running summon.
We do not have any recommendations for how customers can map their Conjur secrets to credentials that their application is using.
For example, if someone is using the conjur-api-python3 API, they may use the get()
method to retrieve a secret
(see https://github.com/cyberark/conjur-api-python3#currently-supported-client-methods).
get(variable_id)
Gets a variable value based on its ID. Variable is binary data that should be
decoded to your system's encoding (e.g. get(variable_id).decode('utf-8').
Their application code would then consume this secret. We don't have a prescribed notion of how they might want to provide a mapping of Conjur variables to their internal, application specific secrets/credentials. Perhaps this might be done using a Python dictionary (for the Python API) or a Golang map (for Go API).
We don't really have a common standard for representing mapping of secrets from Conjur (or other providers) content to application secrets/credentials. It would be difficult to consolidate the different mapping representations that we have, since in each integration, there is unique context, or metadata that goes along with each key/value secret representation.
To explain this further, let's assume that at the core of each secrets mapping, there is a list of secrets key/value pairs, which can be logically represented with:
<key>: <value>
Where <key>
is the name of the desired application cred/secret, and <value>
is the
location e.g. in Conjur to retrieve the secret value.
The secrets mappings of each of the integrations include a form of <key>: <value>
,
but each includes supportive context or metadata to go along with each mapping.
-
Context/Metadata included in Secrets Provider for k8s mapping:
- Kubernetes secret name
- Destination file containing secrets (typically
conjur-map
)
-
Context/Metadata included in Secrets Provider for K8s with Write-to-File
- Group name(s)
- Folder path for each group
-
Context/Metadata included in Secretless Broker mapping (secretless.yml):
- Provider (e.g.
conjur
,keychain
,env
, etc)
- Provider (e.g.
-
Context/Metadata included in Secrets.yml:
- Tags (e.g.
var
orfile
)
- Tags (e.g.
The one place where it might help to standardize would be with secretless.yml
.
Here, we might be able to eliminate the get:
key. For example, this:
test-app-mysql:
protocol: mysql
listenOn: tcp://0.0.0.0:3306
credentials:
host:
from: conjur
get: test-secretless-app-db/host
port:
from: conjur
get: test-secretless-app-db/port
could be represented more succinctly as:
test-app-mysql:
protocol: mysql
listenOn: tcp://0.0.0.0:3306
credentials:
from: conjur
host: test-secretless-app-db/host
port: test-secretless-app-db/port