This is a proof of concept tool to demonstrate encrypting secrets defined in CUE using sops.
We use Cue annotations to specify the fields we need encrypted. The program then extracts fields with attributes and passes them to the SOPS encrypted regex parameter. The Cue file is converted to JSON and encrypted by SOPS. The result is converted back to CUE and the encrypted values as substituted into the appropriate fields. Finally we insert the sops metadata object into the cuefile.
Configure an age key and sops configuration file:
AGE_KEY_NAME="sops.txt"
AGE_CONF_DIR="$HOME/.config/sops/age"
AGE_KEY_PATH=${AGE_CONF_DIR}/${AGE_KEY_NAME}
mkdir -p ${AGE_CONF_DIR}
age-keygen -o ${AGE_KEY_PATH} > /dev/null 2>&1 || true
AGE=$(awk '/public/{print $4}' ${AGE_KEY_PATH})
cat > .sops.yaml <<EOF
creation_rules:
- age: ${AGE}
EOFWe use annotations on the CUE values that we wish to encrypt:
data: {
	api_key: "this is an api key" @secret(sops) // <-- will be encrypted
}
SECRET: "this is a secret value" @secret(sops) // <-- will be encrypted
another: { // <-- will not be encrypted
	this:  100
	value: "is not encrypted"
}Save the above example to a file named secrets.cue.
Run the following to encrypt the values in secrets.cue:
go run main.go encrypt secrets.cue
Now our cue file looks like this:
data: {
	api_key: "ENC[AES256_GCM,data:0SeH+BIX6SwJBsgwLmDOJHU7,iv:Fx1bpRKrz4wKztuEXMfa0KuRqLcOu9ZLT8OYdH+i58c=,tag:IoDhNZpGnGhqmDllgUVdUg==,type:str]" @secret(sops)
}
SECRET: "ENC[AES256_GCM,data:sNFoMGJGHOZZ0liZZ7HHtY/3BpJcQQ==,iv:JA22HeW9V00WEKINOGt/y5enl+94gOmZ6TvMlCYQZl4=,tag:iqGt5iVYqvMg+onsUXjAFQ==,type:str]" @secret(sops)
another: {
	this:  100
	value: "is not encrypted"
}
// DO NOT EDIT: auto-generated by cue-sops
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:    "2022-12-21T13:33:41Z"
	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: "api_key|SECRET"
	version:         "3.7.3"
}Let's also convert this to JSON:
cue eval secrets.cue --out json > secrets.jsonTo decrypt the CUE file execute:
go run main.go decrypt secrets.cue
Our file now looks like this:
data: {
	api_key: "this is an api key" @secret(sops)
}
SECRET: "this is a secret value" @secret(sops)
another: {
	this:  100
	value: "is not encrypted"
}
Verify that we have interoperability with the sops CLI:
sops -d secrets.json- We currently can't handle values in the file that are not concrete, for example constraints.
 - Currently doesn't support nested fields very well if they share names with top-level fields.