format type: add dotenv and ini

Signed-off-by: lucasew <lucas59356@gmail.com>
This commit is contained in:
lucasew 2023-01-16 09:51:41 -03:00
parent e18eefd2b1
commit eb09a61dc9
9 changed files with 125 additions and 28 deletions

View file

@ -24,6 +24,7 @@ import (
"github.com/mozilla-services/yaml"
"go.mozilla.org/sops/v3/decrypt"
"golang.org/x/sys/unix"
"github.com/joho/godotenv"
)
type secret struct {
@ -73,8 +74,23 @@ const (
Yaml FormatType = "yaml"
Json FormatType = "json"
Binary FormatType = "binary"
Dotenv FormatType = "dotenv"
Ini FormatType = "ini"
)
func IsValidFormat(format string) bool {
switch format {
case string(Yaml),
string(Json),
string(Binary),
string(Dotenv),
string(Ini):
return true
default:
return false
}
}
func (f *FormatType) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
@ -84,7 +100,7 @@ func (f *FormatType) UnmarshalJSON(b []byte) error {
switch t {
case "":
*f = Yaml
case Yaml, Json, Binary:
case Yaml, Json, Binary, Dotenv, Ini:
*f = t
}
@ -270,23 +286,26 @@ func decryptSecret(s *secret, sourceFiles map[string]plainData) error {
if err != nil {
return fmt.Errorf("Failed to decrypt '%s': %w", s.SopsFile, err)
}
if s.Format == Binary {
switch s.Format {
case Binary, Dotenv, Ini:
sourceFile.binary = plain
} else {
if s.Format == Yaml {
if err := yaml.Unmarshal(plain, &sourceFile.data); err != nil {
return fmt.Errorf("Cannot parse yaml of '%s': %w", s.SopsFile, err)
}
} else {
if err := json.Unmarshal(plain, &sourceFile.data); err != nil {
return fmt.Errorf("Cannot parse json of '%s': %w", s.SopsFile, err)
}
case Yaml:
if err := yaml.Unmarshal(plain, &sourceFile.data); err != nil {
return fmt.Errorf("Cannot parse yaml of '%s': %w", s.SopsFile, err)
}
case Json:
if err := json.Unmarshal(plain, &sourceFile.data); err != nil {
return fmt.Errorf("Cannot parse json of '%s': %w", s.SopsFile, err)
}
default:
return fmt.Errorf("Secret of type %s in %s is not supported", s.Format, s.SopsFile)
}
}
if s.Format == Binary {
switch s.Format {
case Binary, Dotenv, Ini:
s.value = sourceFile.binary
} else {
case Yaml, Json:
strVal, err := recurseSecretKey(sourceFile.data, s.Key)
if err != nil {
return fmt.Errorf("secret %s in %s is not valid: %w", s.Name, s.SopsFile, err)
@ -410,19 +429,30 @@ func (app *appContext) loadSopsFile(s *secret) (*secretFile, error) {
}
var keys map[string]interface{}
if s.Format == Binary {
switch s.Format {
case Binary:
if err := json.Unmarshal(cipherText, &keys); err != nil {
return nil, fmt.Errorf("Cannot parse json of '%s': %w", s.SopsFile, err)
}
return &secretFile{cipherText: cipherText, firstSecret: s}, nil
}
if s.Format == Yaml {
case Yaml:
if err := yaml.Unmarshal(cipherText, &keys); err != nil {
return nil, fmt.Errorf("Cannot parse yaml of '%s': %w", s.SopsFile, err)
}
} else if err := json.Unmarshal(cipherText, &keys); err != nil {
return nil, fmt.Errorf("Cannot parse json of '%s': %w", s.SopsFile, err)
case Dotenv:
env, err := godotenv.Unmarshal(string(cipherText))
if err != nil {
return nil, fmt.Errorf("Cannot parse dotenv of '%s': %w", s.SopsFile, err)
}
keys = map[string]interface{}{}
for k, v := range env {
keys[k] = v
}
case Json:
if err := json.Unmarshal(cipherText, &keys); err != nil {
return nil, fmt.Errorf("Cannot parse json of '%s': %w", s.SopsFile, err)
}
}
return &secretFile{
@ -439,7 +469,7 @@ func (app *appContext) validateSopsFile(s *secret, file *secretFile) error {
s.Name, s.SopsFile, s.Format,
file.firstSecret.Format, file.firstSecret.Name)
}
if app.checkMode != Manifest && s.Format != Binary {
if app.checkMode != Manifest && (!(s.Format == Binary || s.Format == Dotenv || s.Format == Ini )) {
_, err := recurseSecretKey(file.keys, s.Key)
if err != nil {
return fmt.Errorf("secret %s in %s is not valid: %w", s.Name, s.SopsFile, err)
@ -485,7 +515,7 @@ func (app *appContext) validateSecret(secret *secret) error {
secret.Format = "yaml"
}
if secret.Format != "yaml" && secret.Format != "json" && secret.Format != "binary" {
if !IsValidFormat(string(secret.Format)) {
return fmt.Errorf("Unsupported format %s for secret %s", secret.Format, secret.Name)
}

View file

@ -14,6 +14,7 @@ import (
"reflect"
"runtime"
"strconv"
"strings"
"syscall"
"testing"
)
@ -110,7 +111,7 @@ func testGPG(t *testing.T) {
ReloadUnits: []string{"affected-reload-service"},
}
var jsonSecret, binarySecret secret
var jsonSecret, binarySecret, dotenvSecret, iniSecret secret
// should not create a symlink
jsonSecret = yamlSecret
jsonSecret.Name = "test2"
@ -127,8 +128,25 @@ func testGPG(t *testing.T) {
binarySecret.SopsFile = path.Join(assets, "secrets.bin")
binarySecret.Path = path.Join(testdir.secretsPath, "test3")
dotenvSecret = yamlSecret
dotenvSecret.Name = "test4"
dotenvSecret.Owner = "root"
dotenvSecret.Group = "root"
dotenvSecret.Format = "dotenv"
dotenvSecret.SopsFile = path.Join(assets, "secrets.env")
dotenvSecret.Path = path.Join(testdir.secretsPath, "test4")
iniSecret = yamlSecret
iniSecret.Name = "test5"
iniSecret.Owner = "root"
iniSecret.Group = "root"
iniSecret.Format = "ini"
iniSecret.SopsFile = path.Join(assets, "secrets.ini")
iniSecret.Path = path.Join(testdir.secretsPath, "test5")
manifest := manifest{
Secrets: []secret{yamlSecret, jsonSecret, binarySecret},
Secrets: []secret{yamlSecret, jsonSecret, binarySecret, dotenvSecret, iniSecret},
SecretsMountPoint: testdir.secretsPath,
SymlinkPath: testdir.symlinkPath,
GnupgHome: gpgHome,
@ -321,3 +339,17 @@ func TestValidateManifest(t *testing.T) {
ok(t, installSecrets([]string{"sops-install-secrets", "-check-mode=manifest", path}))
ok(t, installSecrets([]string{"sops-install-secrets", "-check-mode=sopsfile", path}))
}
func TestIsValidFormat(t *testing.T) {
generateCase := func(input string, mustBe bool) {
result := IsValidFormat(input)
if result != mustBe {
t.Errorf("input %s must return %v but returned %v", input, mustBe, result)
}
}
for _, format := range []string{string(Yaml), string(Json), string(Binary), string(Dotenv)} {
generateCase(format, true)
generateCase(strings.Title(format), false)
generateCase(strings.ToUpper(format), false)
}
}

View file

@ -0,0 +1,15 @@
#ENC[AES256_GCM,data:TfdJsqJ9p/3tnClpyPQbfvbmYUmjryiSGA==,iv:YXiEYlAdzco3hZ7T+X6dOUb17ByZeyGXlimfD+yaTa0=,tag:67TgtX4Zn6Ft7ww+J5AjTQ==,type:comment]
hello=ENC[AES256_GCM,data:Y8BU+riqE9DInBi1iALx8iNG2Z5iAGFOgNhZg/wFtwqcLXFBTP1vouIewDdWfQ==,iv:dPftz1CxGYi81lSUbg0iRhXpFP4blmyRn5qKPnWUF0k=,tag:VzKiYYhNDSJsZ8q1A+EpMg==,type:str]
example_key=ENC[AES256_GCM,data:C8+6JSN7MbRpizcF9A==,iv:ODsEI46iuAT81Q/8r83tCfKpU9x2zJ3rzV4FhJmj+Xs=,tag:Mc4l4Kvzg0VvmzcvU3w1tA==,type:str]
example_multiline=ENC[AES256_GCM,data:M6QISEHpqpyUVC0=,iv:uow+EKFgOuSv84hCqtox4r8nvVRFC11xG7or7iMdNkg=,tag:AC5aoWP1LgALV1/YPZMplg==,type:str]
never_gonna=ENC[AES256_GCM,data:PrjZGWkgmmrT6Ms=,iv:ZaSHmgdKf0EUFehl+z4Xj2ouiw6T17xhqwsCGP8fdgQ=,tag:5YRX6obm4G9RUQIqECMf5A==,type:str]
sops_pgp__list_1__map_enc=-----BEGIN PGP MESSAGE-----\n\nhQGMA3ulPRkZxd/UAQwAtsL/Gdj32m81J5k3q4Vz+ev5B+zF/53hB+3FuJtUlWez\naNxQs6RxGC1JtlluMX0syzz8yoAspnfbKxPylMf/A81dhqnMVpZyktBtavb6K07E\nl2gOjgkq6SfOzqeVQdxjvi4VoZ86+KueQCPlALQWxVMGlCMjhGwd7HLWFbJt6O2G\nNCNa7HBABYUDQf8lND+7YtBKX4KyxJviQGpdlOvx7Xkw7hafYMxA79Jp7uLso5d4\n+IKhidKqR2ZWhNgcgKCesRS4hIAzqMmBiSNYHuM23Pe+jPzOvbADWPJEj/rV3qvo\ni8ehS/cGB4rRnLdfW6frQpC+cVKhxuZTJmoBXFazIB15fz32iuDZ8FBiJJXYrVJT\nIphOJt9NcnpoK4M5mQvKB6otZfZRfQYjbJ4Q7n5u4AD/TvMByRgG2B7XkaEXRbrx\ngNuSpaLtwTYpqzq/AynR3h4K5D0izhoviIrhyv6Y5EskDim5ulqbcW+lPAISkohE\nhqWx76/SyA0ehj9jqTrW0lgBXQDsnqgS5C0LZFLsPyDvEG0q03/eoUKSbYyVU5kh\nwy8yHcJmsDqgU+f5TjyNdXpOc+xfbGqfWmeWjSPDyx7lEZxXcBXqAVCpJo3QL9JX\n+VDTtrwqJ2Ho\n=nthm\n-----END PGP MESSAGE-----\n
sops_mac=ENC[AES256_GCM,data:onO42qeKMXa4Jl6Vt06zaoET+6WKzSGd8ak/LyXq1xABAtbVpt25tBdVKlfCD/lzDkOylexQfOBdRAqRPZ1ex3Q4ILZcjmPBkFsMbAs/7L1EzSXskWgeh7NuDXtNKsTmGp9BVcICh3O1+kECQSRZGcefUhV+HYMjRxh2Xxm8r5E=,iv:C8m3Ffbe12LSv0bsPmsxfhOjXNoJ0+75Q+sCYpqchJA=,tag:pahCk/P1Ig71xgvxVIZyPg==,type:str]
sops_version=3.7.3
sops_unencrypted_suffix=_unencrypted
sops_pgp__list_1__map_fp=2504791468B153B8A3963CC97BA53D1919C5DFD4
sops_pgp__list_0__map_fp=7FB89715AADA920D65D25E63F9BA9DEBD03F57C0
sops_pgp__list_0__map_created_at=2023-01-17T12:32:47Z
sops_lastmodified=2023-01-17T12:33:06Z
sops_pgp__list_0__map_enc=-----BEGIN PGP MESSAGE-----\n\nhQEMA/m6nevQP1fAAQf/cGIbOAVh6dy2xDztWHmOPfhBsEFJRzih25cVNbVqo6EC\nxAY31eEZpibKDhAxNKSQbUXjwpCY2Bw5iyRznvjy2kuwDxjyqbGVsNKJuLvlqZ/f\n8Pfs5xvI0A3nc4PRwm3U8n0UhrII9zMl9VB2THw7CP5ZnJy0mjEygxI7ml7k63Go\nSAukABD6QW1sIluP2Q7A6Cy7nXf8QcXI0O5cMJbQos8OOEIiRWoD33i5Uf9KNh9c\nwhNNvpfh1cMZ5StlaWlNXW3ZH/pOJWCLnmmQ/DgcR+LiMA01moykE9ewtwkXfwED\nRzNYZhFD2NKn9Y+smUQ2XaXwMeBw7wlCY7568wK3wtJeAVAEJHbFS7CXI1x3zRRL\nkEy3AI85MobyjGdWIi0+v1K3TqbABUNyg9O+6XpSkn3zPtneY1w4EgJqEvMMJoxN\nxGahyyxYhT2VDO7gPrlkITE6mo0jwyfCGjZERpQFiw==\n=X2R+\n-----END PGP MESSAGE-----\n
sops_pgp__list_1__map_created_at=2023-01-17T12:32:47Z

View file

@ -0,0 +1,18 @@
; ENC[AES256_GCM,data:Q3CfbslIuolYPK9yZIgPdgnmYSuwBG9E,iv:d51C4MXhAa0pOMSTDtSzNyxgRd3IkHPW4+tCyTpHxbY=,tag:LzXKrOg72Td9PAhb4UdKMA==,type:comment]
[Welcome!]
hello = ENC[AES256_GCM,data:NjaXxfkFK25JFGiPNJBDX0NfsZN70ltV4OCLR52PCFU880dw5NU8O2yvQQ2kUQ==,iv:eSYE2Pwu8J7Wdq0t80Dx1OKfq8B5nCkX8FqFnVqOSSM=,tag:gqf3oczxJM4V9/L48/PFAg==,type:str]
example_key = ENC[AES256_GCM,data:aYmYszjMLR+eEW0ZZw==,iv:K6qCGzVu56gjUWXtEad7QYRh/6OpytdkoyzOjp/1lMw=,tag:HqMQ0ckO7uVyOiZbOtbxkA==,type:str]
never_gonna = ENC[AES256_GCM,data:Q4xcNZE1R6tiXh4=,iv:7BgJXn3avu+7Mc9FRPqW0VtRiK7nUpA6cHfhzEWV+TA=,tag:/4YjMUA2la3/E71lgNcqIw==,type:str]
[sops]
pgp__list_0__map_created_at = 2023-01-17T13:06:50Z
mac = ENC[AES256_GCM,data:jCYesEFva/ptI23sBcOHxMhGsyF9E5w3tdsEONKtJj+7KbkG76f7e3AEQyjpURNuv6QVHwCwABJRJObl4VHXOoI/yb/AKSGSYPTM/nRYWXG8vrX1HHwFYWmQfZtm6G8bc8bhWjHh3nt6cV63VhfNB+5L3oaTdkrKfZhNrLI5ztI=,iv:VSbopP0E+ocMbsaM6jwkiG4K/H6N5JUv+3kKtU2jI6Q=,tag:TTjz2JifWiNSs8sComn5Ew==,type:str]
version = 3.7.3
pgp__list_0__map_enc = -----BEGIN PGP MESSAGE-----\n\nhQEMA/m6nevQP1fAAQf/S9ggtaO8grsEYyicoQnrM0279m0+t5d6VP2bQoelneOw\nPEZOdKnwWw939rdUz5pNMWIdEL2mHE1Yu99lmal9TmMXf0kDoryDgHtChM6UyA/u\n0mqbEoASaebwAikWJcidhoWbTLr0eqpohVr+Y0wT3MjkX4sUZzPRapIT2rv/jecm\nq357aSy0kzmKnIal+h9Bqbl3tkiMvhdILRsN1Xlp7xa3H681D1EeRfw3BbR8h4Ui\ng9m9kBVRVmyLl46C4a1etcQgvU5jFUloDIaVV3XGErdEz8bL7B5jPuH5xJCAmVOM\nrouo1n6xJGxEq1lUoWTf/wfnsCCJzpPktsikeprj+9JeAT5DsZdnHr3nmgNYEgLp\neUTTB4aoKHsjehHV+aTpJSKPtzkAkA4mEregUes4T20WOYgurg2lf9a1TYQ+W5ve\ngrI2jdYYxVkWRJvuZRW3umKKQZhx96AP7RczbWL5BA==\n=WuNZ\n-----END PGP MESSAGE-----\n
lastmodified = 2023-01-17T13:07:01Z
unencrypted_suffix = _unencrypted
pgp__list_1__map_created_at = 2023-01-17T13:06:50Z
pgp__list_1__map_enc = -----BEGIN PGP MESSAGE-----\n\nhQGMA3ulPRkZxd/UAQv9FFWTbfSQoC6OVhfIEk5+6t35rAAaJAEGyYPLDqRu0xQk\nd80jcsCmvFo8NqKQfBsC6GTsvbnAOuErIYcltKDya0mULDgskbCDQmrwF6AL0Bp9\noVnJ60tuMc72yGYgKTu7yll2DUJuas/qltvI/aA7SMFltltIqnPv7byZfH3BAJIY\nVpnNO1a9M1S7YrS8GtuLSdXfUWqpzoE2bVhsJCfQy4yxkyyMsEnObb6xTTSD2iio\nZXU50fR8JqXma1Z2XuVUmWLS7mp03iqIzwrBCIVuhfSYmy3gF36rToJ5EFgEhP6f\nB8S4xZyWN9Pp6r3keaKK4cYybKrk9rDUb1JcKiP0K/lGsX52M+IR0x0peTVp0ciq\nfYKOd9leEI8nsBpeMuvQnKhE+XHWiQBghmUCeK35O9oYJGKLrsKVXfMke7LUE5Zm\nQQ1CjvvVovAnuX/8/1+NscixBgXZXZgTqoRE84Qta+ohRZqmNGIv2VeEZx/jQxXG\nhiA5G+ylnlTZPGck8V5D0lgB++OQoiZVryFH1aVr1AHO0j2lCa3ckftLKkQ5vQIi\n1JHbPHZ1MHBut761L/RSnBmTVr3B3XW54NH78LeWS0y7z+8mXjWy7Cjl4cyst1b0\n/byCrk2EqPQP\n=LZBU\n-----END PGP MESSAGE-----\n
pgp__list_1__map_fp = 2504791468B153B8A3963CC97BA53D1919C5DFD4
pgp__list_0__map_fp = 7FB89715AADA920D65D25E63F9BA9DEBD03F57C0