Skip to content

Instantly share code, notes, and snippets.

@diverdane
Last active January 12, 2022 11:59
Show Gist options
  • Save diverdane/3a935d83d9f580e0e353d7015c513a67 to your computer and use it in GitHub Desktop.
Save diverdane/3a935d83d9f580e0e353d7015c513a67 to your computer and use it in GitHub Desktop.
Secrets Configuration/Mappings for Conjur Integrations

Overview

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

Secrets Provider for K8s: Secrets Map

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
$

Secrets Provider for K8s: Proposed ConfigMap for Write-to-File

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"

Secretless Broker Configuration (secretless.yml)

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).

Example: Postgres

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

Example: HTTP

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.*

Example: From kubernetes-conjur-demo

---
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

Authn-k8s + Summon (secrets.yml)

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.

Authn-k8s + Conjur Client Library

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).

Conclusion

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)
  • Context/Metadata included in Secrets.yml:

    • Tags (e.g. var or file)

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment