1
0
Fork 0
mirror of https://github.com/Mic92/sops-nix.git synced 2024-12-15 17:50:51 +00:00

Merge pull request #262 from lucasew/feat/type-dotenv

format type: add dotenv and ini
This commit is contained in:
Jörg Thalheim 2023-02-01 21:54:15 +01:00 committed by GitHub
commit 415302126e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 125 additions and 28 deletions

View file

@ -25,7 +25,7 @@ environment variables that can be passed to sops.
- Atomic upgrades: New secrets are written to a new directory which replaces the old directory atomically.
- Rollback support: If sops files are added to the Nix store, old secrets can be rolled back. This is optional.
- Fast time-to-deploy: Unlike solutions implemented by NixOps, krops and morph, no extra steps are required to upload secrets.
- A variety of storage formats: Secrets can be stored in YAML, JSON or binary.
- A variety of storage formats: Secrets can be stored in YAML, dotenv, INI, JSON or binary.
- Minimizes configuration errors: sops files are checked against the configuration at evaluation time.
## Demo
@ -603,7 +603,7 @@ As users are not created yet, it's not possible to set an owner for these secret
## Different file formats
At the moment we support the following file formats: YAML, JSON, and binary.
At the moment we support the following file formats: YAML, JSON, INI, dotenv and binary.
sops-nix allows specifying multiple sops files in different file formats:

View file

@ -1,5 +1,5 @@
{ pkgs ? import <nixpkgs> {} }: let
vendorSha256 = "sha256-7DHISMAs7i6Yow0/ORiC8gLLfFeuSeiP8QchnJe3M+M=";
vendorSha256 = "sha256-ZAHshOBbvwOC1618bi7IqOan+YtDT7DJNsLzV/4OBSg=";
buildGoModule = if pkgs.lib.versionOlder pkgs.go.version "1.18" then pkgs.buildGo118Module else pkgs.buildGoModule;
sops-install-secrets = pkgs.callPackage ./pkgs/sops-install-secrets {

1
go.mod
View file

@ -18,6 +18,7 @@ require (
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/vault/api v1.7.2 // indirect
github.com/hashicorp/vault/sdk v0.5.2 // indirect
github.com/joho/godotenv v1.4.0 // indirect
github.com/lib/pq v1.10.6 // indirect
github.com/mozilla-services/yaml v0.0.0-20201007153854-c369669a6625
go.mozilla.org/sops/v3 v3.7.3

3
go.sum
View file

@ -403,6 +403,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@ -758,7 +760,6 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View file

@ -41,7 +41,7 @@ let
'';
};
format = mkOption {
type = types.enum ["yaml" "json" "binary"];
type = types.enum ["yaml" "json" "binary" "dotenv" "ini"];
default = cfg.defaultSopsFormat;
description = ''
File format used to decrypt the sops secret.

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