Skip to content

Bundle Secrets Injection

Ways of injecting secrets when deploying applications with Timoni Bundles:

  • Using runtime attributes @timoni(runtime:string:SECRET-NAME).
  • Using SOPS encrypted YAML or JSON files.

Runtime Secrets

To showcase how secrets injections works, we'll assume we are deploying an application that connects to an S3-compatible API and needs two secrets: ACCESS_KEY and SECRET_KEY.

Injecting secrets from CI secret store

When using a CI runner to deploy apps with Timoni, we can pass secrets from the runner's secret store to Timoni's Bundle.

Example of a Bundle that contains runtime attributes:

bundle: {
    apiVersion: "v1alpha1"
    name:       "my-app"
    instances: {
        "my-app-storage": {
            module: url: "oci://my-registry/timoni/modules/my-app-storage"
            namespace: "my-app"
            values: {
                endpoint:  "https://my-acc.r2.cloudflarestorage.com"
                accessKey: string @timoni(runtime:string:ACCESS_KEY)
                secretKey: string @timoni(runtime:string:SECRET_KEY)
            }
        }
    }
}

In a GitHub workflow, we can map secrets from GitHub secrets to env vars, that Timoni will use at apply-time:

export ACCESS_KEY=${{ secrets.ACCESS_KEY }}
export SECRET_KEY=${{ secrets.SECRET_KEY }}

timoni bundle apply -f bundle.cue --runtime-from-env

Injecting secrets from Kubernetes

The same secrets from the above example, can be injected from a Kubernetes Secret, assuming we're using some external-secret controller that syncs secrets from a Vault in etcd.

Example of a Timoni Bundle Runtime that fetches the secrets from the cluster:

runtime: {
    apiVersion: "v1alpha1"
    name:       "production"
    values: [
        {
            query: "k8s:v1:Secret:my-namespace:my-secret-name"
            for: {
                "ACCESS_KEY": "obj.data.r2_access_key"
                "SECRET_KEY": "obj.data.r2_secret_key"
            }
        },
    ]
}

At apply-time we pass the runtime definition and Timoni will read the secrets from the Kubernetes cluster and use them when applying the bundle:

timoni bundle apply -f bundle.cue --runtime runtime.cue

Secrets interpolation

When the secrets stored in external system do not map to a value key in the Bundle, we can use CUE's string interpolation to compose the desired value.

Assuming an application config expects a Redis URL, but the secret store contains REDIS_HOST and REDIS_PASS.

bundle: {
    apiVersion: "v1alpha1"
    name:       "podinfo"
    _secrets: {
        host:     string @timoni(runtime:string:REDIS_HOST)
        password: string @timoni(runtime:string:REDIS_PASS)
    }
    instances: {
        "podinfo-backend": {
            module: url: "oci://ghcr.io/stefanprodan/modules/podinfo"
            namespace: "podinfo"
            values: caching: {
                enabled:  true
                redisURL: "tcp://:\(_secrets.password)@\(_secrets.host):6379"
            }
        }
    }
}

In the above example, we define a CUE hidden filed _secrets, where we set the runtime secrets mappings. Then in the instance values, we use string interpolation to set the redisURL containing the secrets.

Using the build command, we can see the URL value set in the podinfo container args:

$ export REDIS_HOST=redis.svc
$ export REDIS_PASS=testpass
$ timoni bundle build -f bundle.cue --runtime-from-env | grep redis
        - --cache-server=tcp://:[email protected]:6379

SOPS secrets

When using SOPS, we can decrypt the secrets and inject those values to env vars, then use --runtime-from-env.

Another option is to extract the secret values of a Timoni Bundle to a YAML or JSON file, that we encrypt/decrypt with SOPS.

Injecting secrets from SOPS

Main bundle file bundle.main.cue:

bundle: {
    apiVersion: "v1alpha1"
    name:       "my-app"
    instances: {
        "my-app-storage": {
            module: url: "oci://my-registry/timoni/modules/my-app-storage"
            namespace: "my-app"
            values: {
                endpoint: "https://my-acc.r2.cloudflarestorage.com"
                // The secrets are omitted here!
            }
        }
    }
}

Bundle partial in YAML format bundle.secret.yaml:

bundle:
  instances:
    my-app-storage:
      values:
        accessKey: ENC[AES256_GCM,data:..]
        secretKey: ENC[AES256_GCM,data:..]

Assuming the bundle.secret.yaml file is kept encrypted with SOPS, at apply-time we can run the SOPS decryption, and pass the plain YAML to Timoni's apply command like so:

sops exec-file --filename secrets.yml bundle.secret.yaml 'timoni bundle apply -f bundle.main.cue -f {}'