Open ChrisJBurns opened 2 years ago
Hi, even am looking for a way to programmatically do all the stuff that can be done via sops cli. Do we have any references for golang examples which showcase this ability?
Thanks
I'm not entirely positive that there is a way of programmatically doing the same in Go as a sops -e -i file.yaml
without actually using the sops
binary itself and calling it in the Go code.
after some digging through the source, this is an example to perform encryption in place using go, hope it helps whoever come across this in the future
main.go
import (
"fmt"
"filippo.io/age"
"github.com/getsops/sops/v3"
"github.com/getsops/sops/v3/aes"
keysource "github.com/getsops/sops/v3/age"
"github.com/getsops/sops/v3/cmd/sops/common"
"github.com/getsops/sops/v3/keys"
"github.com/getsops/sops/v3/keyservice"
"github.com/getsops/sops/v3/stores/json"
)
func main() {
identity, err := age.GenerateX25519Identity()
if err != nil {
panic(err)
}
fmt.Println(identity.String())
fmt.Println(identity.Recipient().String())
if err != nil {
panic(err)
}
store := json.Store{}
branches, err := store.LoadPlainFile([]byte(`{"foo": "bar"}`))
if err != nil {
panic(err)
}
fmt.Println(branches)
masterKey, err := keysource.MasterKeyFromRecipient(identity.Recipient().String())
if err != nil {
panic(err)
}
tree := sops.Tree{
Branches: branches,
Metadata: sops.Metadata{
KeyGroups: []sops.KeyGroup{
[]keys.MasterKey{masterKey},
},
UnencryptedSuffix: "_unencrypted",
},
}
dataKey, errs := tree.GenerateDataKeyWithKeyServices(
[]keyservice.KeyServiceClient{keyservice.NewLocalClient()},
)
if errs != nil {
panic(errs)
}
common.EncryptTree(common.EncryptTreeOpts{
DataKey: dataKey,
Tree: &tree,
Cipher: aes.NewCipher(),
})
result, err := store.EmitEncryptedFile(tree)
if err != nil {
panic(err)
}
fmt.Print(string(result))
}
If anyone is looking for a programmatic decrypt, here's an extended example. Would love a better way besides setting an env variable with the private key...
package main
import (
"fmt"
"os"
"filippo.io/age"
"github.com/getsops/sops/v3"
"github.com/getsops/sops/v3/aes"
keysource "github.com/getsops/sops/v3/age"
"github.com/getsops/sops/v3/cmd/sops/common"
"github.com/getsops/sops/v3/keys"
"github.com/getsops/sops/v3/keyservice"
sopsjson "github.com/getsops/sops/v3/stores/json"
)
func main() {
identity, err := age.GenerateX25519Identity()
if err != nil {
panic(err)
}
fmt.Println(identity.String())
fmt.Println(identity.Recipient().String())
store := sopsjson.Store{}
branches, err := store.LoadPlainFile([]byte(`{"foo": "bar"}`))
if err != nil {
panic(err)
}
fmt.Println(branches)
masterKey, err := keysource.MasterKeyFromRecipient(identity.Recipient().String())
if err != nil {
panic(err)
}
tree := sops.Tree{
Branches: branches,
Metadata: sops.Metadata{
KeyGroups: []sops.KeyGroup{
[]keys.MasterKey{masterKey},
},
UnencryptedSuffix: "_unencrypted",
},
}
dataKey, errs := tree.GenerateDataKeyWithKeyServices(
[]keyservice.KeyServiceClient{keyservice.NewLocalClient()},
)
if errs != nil {
panic(errs)
}
common.EncryptTree(common.EncryptTreeOpts{
DataKey: dataKey,
Tree: &tree,
Cipher: aes.NewCipher(),
})
result, err := store.EmitEncryptedFile(tree)
if err != nil {
panic(err)
}
fmt.Print(string(result))
if decrypted, err := decryptJSON(string(result), identity.String()); err != nil {
panic(err)
} else {
fmt.Println(decrypted)
}
}
func decryptJSON(encryptedData, privateKey string) (string, error) {
id, err := age.ParseX25519Identity(privateKey)
if err != nil {
return "", fmt.Errorf("failed to parse private key: %w", err)
}
store := sopsjson.Store{}
tree, err := store.LoadEncryptedFile([]byte(encryptedData))
if err != nil {
return "", fmt.Errorf("failed to load encrypted JSON: %w", err)
}
os.Setenv("SOPS_AGE_KEY", id.String())
defer os.Unsetenv("SOPS_AGE_KEY")
if _, err := common.DecryptTree(common.DecryptTreeOpts{
Cipher: aes.NewCipher(),
Tree: &tree,
KeyServices: []keyservice.KeyServiceClient{keyservice.NewLocalClient()},
}); err != nil {
return "", fmt.Errorf("failed to decrypt tree: %w", err)
}
decryptedData, err := store.EmitPlainFile(tree.Branches)
if err != nil {
return "", fmt.Errorf("failed to emit plain file: %w", err)
}
return string(decryptedData), nil
}
Currently, as far as I'm aware there is no way of encrypting a file in-place using SOPS via Golang.
I'm essentially looking for an API to be exposed that does the equivalent of the following command:
sops -age=age1ykphy2fuc0rmtewtpml69670s9dydkneum384tsj6z480lljmvqqx4kj8u --encrypt --encrypted-regex '^(data|stringData)$' --in-place secrets.yaml
The above command edits a
secrets.yaml
file in place which after encryption will contain the following:Is it possible for something like this to be exposed via an API?
Something like the following (although I may be way off with the internals of how some of the methods work, but I hope it paints a good picture of what I'm looking for)