Skip to content

Commit 681a386

Browse files
authored
Merge pull request #1273 from Ph0tonic/main
2 parents 2443097 + 42018ef commit 681a386

File tree

12 files changed

+361
-29
lines changed

12 files changed

+361
-29
lines changed

README.rst

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1130,15 +1130,15 @@ Below is an example of publishing to Vault (using token auth with a local dev in
11301130
Important information on types
11311131
------------------------------
11321132
1133-
YAML and JSON type extensions
1134-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1133+
YAML, JSON, ENV and INI type extensions
1134+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11351135
11361136
SOPS uses the file extension to decide which encryption method to use on the file
11371137
content. ``YAML``, ``JSON``, ``ENV``, and ``INI`` files are treated as trees of data, and key/values are
11381138
extracted from the files to only encrypt the leaf values. The tree structure is also
11391139
used to check the integrity of the file.
11401140
1141-
Therefore, if a file is encrypted using a specific format, it need to be decrypted
1141+
Therefore, if a file is encrypted using a specific format, it needs to be decrypted
11421142
in the same format. The easiest way to achieve this is to conserve the original file
11431143
extension after encrypting a file. For example:
11441144
@@ -1162,8 +1162,39 @@ When operating on stdin, use the ``--input-type`` and ``--output-type`` flags as
11621162
11631163
$ cat myfile.json | sops --input-type json --output-type json -d /dev/stdin
11641164
1165+
JSON and JSON_binary indentation
1166+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1167+
1168+
SOPS indents ``JSON`` files by default using one ``tab``. However, you can change
1169+
this default behaviour to use ``spaces`` by either using the additional ``--indent=2`` CLI option or
1170+
by configuring ``.sops.yaml`` with the code below.
1171+
1172+
The special value ``0`` disables indentation, and ``-1`` uses a single tab.
1173+
1174+
.. code:: yaml
1175+
1176+
stores:
1177+
json:
1178+
indent: 2
1179+
json_binary:
1180+
indent: 2
1181+
1182+
YAML indentation
1183+
~~~~~~~~~~~~~~~~
1184+
1185+
SOPS indents ``YAML`` files by default using 4 spaces. However, you can change
1186+
this default behaviour by either using the additional ``--indent=2`` CLI option or
1187+
by configuring ``.sops.yaml`` with:
1188+
1189+
.. code:: yaml
1190+
1191+
stores:
1192+
yaml:
1193+
indent: 2
1194+
11651195
YAML anchors
11661196
~~~~~~~~~~~~
1197+
11671198
SOPS only supports a subset of ``YAML``'s many types. Encrypting YAML files that
11681199
contain strings, numbers and booleans will work fine, but files that contain anchors
11691200
will not work, because the anchors redefine the structure of the file at load time.

cmd/sops/common/common.go

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/getsops/sops/v3"
1111
"github.com/getsops/sops/v3/cmd/sops/codes"
1212
. "github.com/getsops/sops/v3/cmd/sops/formats"
13+
"github.com/getsops/sops/v3/config"
1314
"github.com/getsops/sops/v3/keys"
1415
"github.com/getsops/sops/v3/keyservice"
1516
"github.com/getsops/sops/v3/kms"
@@ -35,26 +36,26 @@ type Store interface {
3536
ExampleFileEmitter
3637
}
3738

38-
type storeConstructor = func() Store
39+
type storeConstructor = func(*config.StoresConfig) Store
3940

40-
func newBinaryStore() Store {
41-
return &json.BinaryStore{}
41+
func newBinaryStore(c *config.StoresConfig) Store {
42+
return json.NewBinaryStore(&c.JSONBinary)
4243
}
4344

44-
func newDotenvStore() Store {
45-
return &dotenv.Store{}
45+
func newDotenvStore(c *config.StoresConfig) Store {
46+
return dotenv.NewStore(&c.Dotenv)
4647
}
4748

48-
func newIniStore() Store {
49-
return &ini.Store{}
49+
func newIniStore(c *config.StoresConfig) Store {
50+
return ini.NewStore(&c.INI)
5051
}
5152

52-
func newJsonStore() Store {
53-
return &json.Store{}
53+
func newJsonStore(c *config.StoresConfig) Store {
54+
return json.NewStore(&c.JSON)
5455
}
5556

56-
func newYamlStore() Store {
57-
return &yaml.Store{}
57+
func newYamlStore(c *config.StoresConfig) Store {
58+
return yaml.NewStore(&c.YAML)
5859
}
5960

6061
var storeConstructors = map[Format]storeConstructor{
@@ -153,27 +154,27 @@ func NewExitError(i interface{}, exitCode int) *cli.ExitError {
153154

154155
// StoreForFormat returns the correct format-specific implementation
155156
// of the Store interface given the format.
156-
func StoreForFormat(format Format) Store {
157+
func StoreForFormat(format Format, c *config.StoresConfig) Store {
157158
storeConst, found := storeConstructors[format]
158159
if !found {
159160
storeConst = storeConstructors[Binary] // default
160161
}
161-
return storeConst()
162+
return storeConst(c)
162163
}
163164

164165
// DefaultStoreForPath returns the correct format-specific implementation
165166
// of the Store interface given the path to a file
166-
func DefaultStoreForPath(path string) Store {
167+
func DefaultStoreForPath(c *config.StoresConfig, path string) Store {
167168
format := FormatForPath(path)
168-
return StoreForFormat(format)
169+
return StoreForFormat(format, c)
169170
}
170171

171172
// DefaultStoreForPathOrFormat returns the correct format-specific implementation
172173
// of the Store interface given the formatString if specified, or the path to a file.
173174
// This is to support the cli, where both are provided.
174-
func DefaultStoreForPathOrFormat(path, format string) Store {
175+
func DefaultStoreForPathOrFormat(c *config.StoresConfig, path string, format string) Store {
175176
formatFmt := FormatForPathOrString(path, format)
176-
return StoreForFormat(formatFmt)
177+
return StoreForFormat(formatFmt, c)
177178
}
178179

179180
// KMS_ENC_CTX_BUG_FIXED_VERSION represents the SOPS version in which the

cmd/sops/main.go

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,10 @@ func main() {
704704
Name: "shamir-secret-sharing-threshold",
705705
Usage: "the number of master keys required to retrieve the data key with shamir",
706706
},
707+
cli.IntFlag{
708+
Name: "indent",
709+
Usage: "the number of spaces to indent YAML or JSON encoded file for encryption",
710+
},
707711
cli.BoolFlag{
708712
Name: "verbose",
709713
Usage: "Enable verbose logging output",
@@ -1065,12 +1069,32 @@ func keyservices(c *cli.Context) (svcs []keyservice.KeyServiceClient) {
10651069
return
10661070
}
10671071

1072+
func loadStoresConfig(context *cli.Context, path string) (*config.StoresConfig, error) {
1073+
var configPath string
1074+
if context.String("config") != "" {
1075+
configPath = context.String("config")
1076+
} else {
1077+
// Ignore config not found errors returned from FindConfigFile since the config file is not mandatory
1078+
configPath, _ = config.FindConfigFile(".")
1079+
}
1080+
return config.LoadStoresConfig(configPath)
1081+
}
1082+
10681083
func inputStore(context *cli.Context, path string) common.Store {
1069-
return common.DefaultStoreForPathOrFormat(path, context.String("input-type"))
1084+
storesConf, _ := loadStoresConfig(context, path)
1085+
return common.DefaultStoreForPathOrFormat(storesConf, path, context.String("input-type"))
10701086
}
10711087

10721088
func outputStore(context *cli.Context, path string) common.Store {
1073-
return common.DefaultStoreForPathOrFormat(path, context.String("output-type"))
1089+
storesConf, _ := loadStoresConfig(context, path)
1090+
if context.IsSet("indent") {
1091+
indent := context.Int("indent")
1092+
storesConf.YAML.Indent = indent
1093+
storesConf.JSON.Indent = indent
1094+
storesConf.JSONBinary.Indent = indent
1095+
}
1096+
1097+
return common.DefaultStoreForPathOrFormat(storesConf, path, context.String("output-type"))
10741098
}
10751099

10761100
func parseTreePath(arg string) ([]interface{}, error) {

cmd/sops/subcommand/updatekeys/updatekeys.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@ func UpdateKeys(opts Opts) error {
4040
}
4141

4242
func updateFile(opts Opts) error {
43-
store := common.DefaultStoreForPathOrFormat(opts.InputPath, opts.InputType)
43+
sc, err := config.LoadStoresConfig(opts.ConfigPath)
44+
if err != nil {
45+
return err
46+
}
47+
store := common.DefaultStoreForPath(sc, opts.InputPath)
4448
log.Printf("Syncing keys for file %s", opts.InputPath)
4549
tree, err := common.LoadEncryptedFile(store, opts.InputPath)
4650
if err != nil {

config/config.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,34 @@ func FindConfigFile(start string) (string, error) {
6363
return "", fmt.Errorf("Config file not found")
6464
}
6565

66+
type DotenvStoreConfig struct{}
67+
68+
type INIStoreConfig struct{}
69+
70+
type JSONStoreConfig struct {
71+
Indent int `yaml:"indent"`
72+
}
73+
74+
type JSONBinaryStoreConfig struct {
75+
Indent int `yaml:"indent"`
76+
}
77+
78+
type YAMLStoreConfig struct {
79+
Indent int `yaml:"indent"`
80+
}
81+
82+
type StoresConfig struct {
83+
Dotenv DotenvStoreConfig `yaml:"dotenv"`
84+
INI INIStoreConfig `yaml:"ini"`
85+
JSONBinary JSONBinaryStoreConfig `yaml:"json_binary"`
86+
JSON JSONStoreConfig `yaml:"json"`
87+
YAML YAMLStoreConfig `yaml:"yaml"`
88+
}
89+
6690
type configFile struct {
6791
CreationRules []creationRule `yaml:"creation_rules"`
6892
DestinationRules []destinationRule `yaml:"destination_rules"`
93+
Stores StoresConfig `yaml:"stores"`
6994
}
7095

7196
type keyGroup struct {
@@ -126,6 +151,13 @@ type creationRule struct {
126151
MACOnlyEncrypted bool `yaml:"mac_only_encrypted"`
127152
}
128153

154+
func NewStoresConfig() *StoresConfig {
155+
storesConfig := &StoresConfig{}
156+
storesConfig.JSON.Indent = -1
157+
storesConfig.JSONBinary.Indent = -1
158+
return storesConfig
159+
}
160+
129161
// Load loads a sops config file into a temporary struct
130162
func (f *configFile) load(bytes []byte) error {
131163
err := yaml.Unmarshal(bytes, f)
@@ -229,6 +261,7 @@ func loadConfigFile(confPath string) (*configFile, error) {
229261
return nil, fmt.Errorf("could not read config file: %s", err)
230262
}
231263
conf := &configFile{}
264+
conf.Stores = *NewStoresConfig()
232265
err = conf.load(confBytes)
233266
if err != nil {
234267
return nil, fmt.Errorf("error loading config: %s", err)
@@ -386,3 +419,11 @@ func LoadDestinationRuleForFile(confPath string, filePath string, kmsEncryptionC
386419
}
387420
return parseDestinationRuleForFile(conf, filePath, kmsEncryptionContext)
388421
}
422+
423+
func LoadStoresConfig(confPath string) (*StoresConfig, error) {
424+
conf, err := loadConfigFile(confPath)
425+
if err != nil {
426+
return nil, err
427+
}
428+
return &conf.Stores, nil
429+
}

decrypt/decrypt.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/getsops/sops/v3/aes"
1313
"github.com/getsops/sops/v3/cmd/sops/common"
1414
. "github.com/getsops/sops/v3/cmd/sops/formats" // Re-export
15+
"github.com/getsops/sops/v3/config"
1516
)
1617

1718
// File is a wrapper around Data that reads a local encrypted
@@ -32,7 +33,7 @@ func File(path, format string) (cleartext []byte, err error) {
3233
// decrypts the data and returns its cleartext in an []byte.
3334
func DataWithFormat(data []byte, format Format) (cleartext []byte, err error) {
3435

35-
store := common.StoreForFormat(format)
36+
store := common.StoreForFormat(format, config.NewStoresConfig())
3637

3738
// Load SOPS file and access the data key
3839
tree, err := store.LoadEncryptedFile(data)

stores/dotenv/store.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99

1010
"github.com/getsops/sops/v3"
11+
"github.com/getsops/sops/v3/config"
1112
"github.com/getsops/sops/v3/stores"
1213
)
1314

@@ -16,6 +17,11 @@ const SopsPrefix = "sops_"
1617

1718
// Store handles storage of dotenv data
1819
type Store struct {
20+
config config.DotenvStoreConfig
21+
}
22+
23+
func NewStore(c *config.DotenvStoreConfig) *Store {
24+
return &Store{config: *c}
1925
}
2026

2127
// LoadEncryptedFile loads an encrypted file's bytes onto a sops.Tree runtime object

stores/ini/store.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@ import (
99
"strings"
1010

1111
"github.com/getsops/sops/v3"
12+
"github.com/getsops/sops/v3/config"
1213
"github.com/getsops/sops/v3/stores"
1314
"gopkg.in/ini.v1"
1415
)
1516

1617
// Store handles storage of ini data.
1718
type Store struct {
19+
config *config.INIStoreConfig
20+
}
21+
22+
func NewStore(c *config.INIStoreConfig) *Store {
23+
return &Store{config: c}
1824
}
1925

2026
func (store Store) encodeTree(branches sops.TreeBranches) ([]byte, error) {

stores/json/store.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,32 @@ import (
66
"errors"
77
"fmt"
88
"io"
9+
"strings"
910

1011
"github.com/getsops/sops/v3"
12+
"github.com/getsops/sops/v3/config"
1113
"github.com/getsops/sops/v3/stores"
1214
)
1315

1416
// Store handles storage of JSON data.
1517
type Store struct {
18+
config config.JSONStoreConfig
19+
}
20+
21+
func NewStore(c *config.JSONStoreConfig) *Store {
22+
return &Store{config: *c}
1623
}
1724

1825
// BinaryStore handles storage of binary data in a JSON envelope.
1926
type BinaryStore struct {
20-
store Store
27+
store Store
28+
config config.JSONBinaryStoreConfig
29+
}
30+
31+
func NewBinaryStore(c *config.JSONBinaryStoreConfig) *BinaryStore {
32+
return &BinaryStore{config: *c, store: *NewStore(&config.JSONStoreConfig{
33+
Indent: c.Indent,
34+
})}
2135
}
2236

2337
// LoadEncryptedFile loads an encrypted json file onto a sops.Tree object
@@ -237,7 +251,13 @@ func (store Store) treeBranchFromJSON(in []byte) (sops.TreeBranch, error) {
237251

238252
func (store Store) reindentJSON(in []byte) ([]byte, error) {
239253
var out bytes.Buffer
240-
err := json.Indent(&out, in, "", "\t")
254+
indent := "\t"
255+
if store.config.Indent > -1 {
256+
indent = strings.Repeat(" ", store.config.Indent)
257+
} else if store.config.Indent < -1 {
258+
return nil, errors.New("JSON Indentation parameter smaller than -1 is not accepted")
259+
}
260+
err := json.Indent(&out, in, "", indent)
241261
return out.Bytes(), err
242262
}
243263

0 commit comments

Comments
 (0)