timoni
timoni copied to clipboard
[proposal] Encryption of Timoni values
Context
The following is a proposal for supporting the encryption of Timoni values (based on prior art, see: https://github.com/phoban01/cue-sops).
The proposal was first mooted in #71.
Proposal
Timoni enables the encryption of sensitive values via built-in SOPS support.
Given the following config definition:
#Config: {
api: {
url: "https//api.github.com/user"
token: string
}
}
A values file providing an API key is marked as sensitive using the @secret()
annotation:
# api-values.cue
values: {
api: token: "gh_personalaccesstoken" @secret()
}
The plaintext value can then be encrypted in-place:
timoni encrypt -f api-values.cue
This will produce the following result:
# api-values.cue
values: {
api: token: "ENC[AES256_GCM,data:0SeH+BIX6SwJBsgwLmDOJHU7,iv:Fx1bpRKrz4wKztuEXMfa0KuRqLcOu9ZLT8OYdH+i58c=,tag:IoDhNZpGnGhqmDllgUVdUg==,type:str]" @secret()
}
// DO NOT EDIT: auto-generated by timoni
sops: {
kms: null
gcp_kms: null
azure_kv: null
hc_vault: null
age: [{
recipient: "age1ethasxep4zkax64yfx35rn2t4yeul4254w764l9gtasvn2rwpv7s733dq7"
enc: """
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQMk43bjFuUytFNTlIclNW
Y1RnNWEwc2FGOUd4VW5NODNwdEVKOXlJd2k4ClNubDN0Qktuck5IVnN6ZjZBOTEz
OFhNRUc3aUs1Y09DQTF6OTlWRU9ZQ00KLS0tIGdGTUlZWUJyVkZKZXdvMzZhV294
c0E5bHVkSHc0MkhFUnhiODFlbzV5SE0KlNEhfwHl/VDZzfkpGb2/s7KbTFRA4U/K
u5OM5P2YTvpSkmVbdVLLcX7eFHVyLZOukarFXEZ65rq9baMO0lJ3Vg==
-----END AGE ENCRYPTED FILE-----
"""
}]
lastmodified: "2023-04-01T12:00:00Z"
mac: "ENC[AES256_GCM,data:heUT68PAirogTfcV+4pR8RNjx+d3cEE+Zn5e97xNy2wJvwZ4ecxnxItDj60E71aTK80UxCxkWkfjg2ZGKscPCMKoAXBkli6y/ab0e0+9uulvqjbd51m7mzGo/DMt65Ab7C6hq6S/VuI9JvvR7OVdgpvrliQzlCx2VENYNG6/r/0=,iv:gPvKgisLoTuOEIMNQgwY3zhPUEDkjJrRTyGWEEMr1ww=,tag:P8OlN/XDfWZqo6ZIchwbzw==,type:str]"
pgp: null
encrypted_regex: "token|SECRET"
version: "3.7.3"
}
Timoni will decrypt values before applying an instance to the cluster:
timoni -n default apply gh-app \
oci://ghcr.io/phoban01/modules/gh-app \
--values api-values.cue
Timoni can also decrypt a file in-place:
timoni decrypt -f api-values.cue
Encrypt multiple fields
To encrypt all fields in a file (optionally matching field names via regex), it is possible to use a global annotation:
# api-values.cue
@secret(include="[regex-pattern]", exclude="[regex-pattern]")
values: {
api: token: "gh_personalaccesstoken"
}
SOPS configuration
It is possible to specify the encryption service used by SOPs per field:
# api-values.cue
values: {
age: "supersecret" @secret(age="age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw")
pgp: "supersecret" @secret(pgp_fp="85D77543B3D624B63CEA9E6DBC17301B491B3F21")
aws: "supersecret" @secret(aws_kms="arn:aws:kms:us-east-1:656532927350:key/920aff2e-c5f1-4040-943a-047fa387b27e")
aws: "supersecret" @secret(aws_encryption_context="Environment:production,Role:web-server")
gcp: "supersecret" @secret(gcp_kms="projects/my-project/locations/global/keyRings/sops/cryptoKeys/sops-key")
azure: "supersecret" @secret(azure_kv="https://sops.vault.azure.net/keys/sops-key/some-string")
hashicorp_vault: "supersecret" @secret(hc_vault_transit="https://vault-server:8200/v1/sops/keys/firstkey")
}
It is also possible to define encryption service providers globally using the @sops()
annotation. Providers can subsequently be referenced using labels:
# api-values.cue
@sops(label="aws-master", aws_kms="arn:aws:kms:us-east-1:656532927350:key/920aff2e-c5f1-4040-943a-047fa387b27e")
@sops(label="hashi-vault", hc_vault_transit="https://vault-server:8200/v1/sops/keys/firstkey")
values: {
aws: "supersecret" @secret(label="aws-master")
hashicorp_vault: "supersecret" @secret(label"aws-master")
}
If a single @sops()
annotation is present then labels can be omitted and the specified service will be used to encrypt all values:
# api-values.cue
@sops(aws_kms="arn:aws:kms:us-east-1:656532927350:key/920aff2e-c5f1-4040-943a-047fa387b27e")
values: {
aws: "supersecret" @secret()
hashicorp_vault: "supersecret" @secret()
}
Bundles
Values provided in a Bundle may also be encrypted. Encryption services can be defined as part of the bundle spec
and then referenced by name in the @secret
annotation.
#Bundle: {
apiVersion: string
encryption: [{
name: string
type: age | pgp_fp | aws_kms | aws_encryption_context | gcp_kms | azure_kv | hc_vault_transit
value: string
}]
instances: [string]: {
module: {
url: string
digest?: string
version: *"latest" | string
}
namespace: string
values: {
api_key: "123457890abcdefg" @secret(name=string)
}
}
}
To encrypt a bundle use the bundle encrypt subcommand:
timoni bundle encrypt -f bundle.cue
Sensitive values will be automatically decrypted when the bundle is applied:
timoni bundle apply -f bundle.cue
Timoni bundle diffs will decrypt both previous and current sensitive values and display the diff in cleartext:
timoni bundle apply --dry-run --diff -f bundle.cue