зеркало из https://github.com/getsops/sops.git
Sort masterkeys according to decryption-order
Co-authored-by: Gabriel Martinez <19713226+GMartinez-Sisti@users.noreply.github.com> Co-authored-by: Felix Fontein <felix@fontein.de> Co-authored-by: Bastien Wermeille <bastien.wermeille@gmail.com> Co-authored-by: Hidde Beydals <hiddeco@users.noreply.github.com> Signed-off-by: Boris Kreitchman <bkreitch@gmail.com>
This commit is contained in:
Родитель
30281796df
Коммит
c822b55290
|
@ -170,6 +170,11 @@ Given that, the only command a SOPS user needs is:
|
||||||
encrypted if modified, and saved back to its original location. All of these
|
encrypted if modified, and saved back to its original location. All of these
|
||||||
steps, apart from the actual editing, are transparent to the user.
|
steps, apart from the actual editing, are transparent to the user.
|
||||||
|
|
||||||
|
The order in which available decryption methods are tried can be specified with
|
||||||
|
``--decryption-order`` option or **SOPS_DECRYPTION_ORDER** environment variable
|
||||||
|
as a comma separated list. The default order is ``age,pgp``. Offline methods are
|
||||||
|
tried first and then the remaining ones.
|
||||||
|
|
||||||
Test with the dev PGP key
|
Test with the dev PGP key
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,9 @@ import (
|
||||||
|
|
||||||
"filippo.io/age"
|
"filippo.io/age"
|
||||||
"filippo.io/age/armor"
|
"filippo.io/age/armor"
|
||||||
"github.com/getsops/sops/v3/logging"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/getsops/sops/v3/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -28,6 +29,8 @@ const (
|
||||||
SopsAgeKeyUserConfigPath = "sops/age/keys.txt"
|
SopsAgeKeyUserConfigPath = "sops/age/keys.txt"
|
||||||
// On macOS, os.UserConfigDir() ignores XDG_CONFIG_HOME. So we handle that manually.
|
// On macOS, os.UserConfigDir() ignores XDG_CONFIG_HOME. So we handle that manually.
|
||||||
xdgConfigHome = "XDG_CONFIG_HOME"
|
xdgConfigHome = "XDG_CONFIG_HOME"
|
||||||
|
// KeyTypeIdentifier is the string used to identify an age MasterKey.
|
||||||
|
KeyTypeIdentifier = "age"
|
||||||
)
|
)
|
||||||
|
|
||||||
// log is the global logger for any age MasterKey.
|
// log is the global logger for any age MasterKey.
|
||||||
|
@ -225,6 +228,11 @@ func (key *MasterKey) ToMap() map[string]interface{} {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TypeToIdentifier returns the string identifier for the MasterKey type.
|
||||||
|
func (key *MasterKey) TypeToIdentifier() string {
|
||||||
|
return KeyTypeIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
func getUserConfigDir() (string, error) {
|
func getUserConfigDir() (string, error) {
|
||||||
if runtime.GOOS == "darwin" {
|
if runtime.GOOS == "darwin" {
|
||||||
if userConfigDir, ok := os.LookupEnv(xdgConfigHome); ok && userConfigDir != "" {
|
if userConfigDir, ok := os.LookupEnv(xdgConfigHome); ok && userConfigDir != "" {
|
||||||
|
|
|
@ -22,6 +22,11 @@ import (
|
||||||
"github.com/getsops/sops/v3/logging"
|
"github.com/getsops/sops/v3/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// KeyTypeIdentifier is the string used to identify an Azure Key Vault MasterKey.
|
||||||
|
KeyTypeIdentifier = "azure_kv"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// log is the global logger for any Azure Key Vault MasterKey.
|
// log is the global logger for any Azure Key Vault MasterKey.
|
||||||
log *logrus.Logger
|
log *logrus.Logger
|
||||||
|
@ -215,6 +220,11 @@ func (key MasterKey) ToMap() map[string]interface{} {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TypeToIdentifier returns the string identifier for the MasterKey type.
|
||||||
|
func (key *MasterKey) TypeToIdentifier() string {
|
||||||
|
return KeyTypeIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
// getTokenCredential returns the tokenCredential of the MasterKey, or
|
// getTokenCredential returns the tokenCredential of the MasterKey, or
|
||||||
// azidentity.NewDefaultAzureCredential.
|
// azidentity.NewDefaultAzureCredential.
|
||||||
func (key *MasterKey) getTokenCredential() (azcore.TokenCredential, error) {
|
func (key *MasterKey) getTokenCredential() (azcore.TokenCredential, error) {
|
||||||
|
|
|
@ -25,6 +25,7 @@ const (
|
||||||
NoFileSpecified int = 100
|
NoFileSpecified int = 100
|
||||||
CouldNotRetrieveKey int = 128
|
CouldNotRetrieveKey int = 128
|
||||||
NoEncryptionKeyFound int = 111
|
NoEncryptionKeyFound int = 111
|
||||||
|
DuplicateDecryptionKeyType int = 112
|
||||||
FileHasNotBeenModified int = 200
|
FileHasNotBeenModified int = 200
|
||||||
NoEditorFound int = 201
|
NoEditorFound int = 201
|
||||||
FailedToCompareVersions int = 202
|
FailedToCompareVersions int = 202
|
||||||
|
|
|
@ -72,6 +72,8 @@ type DecryptTreeOpts struct {
|
||||||
Tree *sops.Tree
|
Tree *sops.Tree
|
||||||
// KeyServices are the key services to be used for decryption of the data key
|
// KeyServices are the key services to be used for decryption of the data key
|
||||||
KeyServices []keyservice.KeyServiceClient
|
KeyServices []keyservice.KeyServiceClient
|
||||||
|
// DecryptionOrder is the order in which available decryption methods are tried
|
||||||
|
DecryptionOrder []string
|
||||||
// IgnoreMac is whether or not to ignore the Message Authentication Code included in the SOPS tree
|
// IgnoreMac is whether or not to ignore the Message Authentication Code included in the SOPS tree
|
||||||
IgnoreMac bool
|
IgnoreMac bool
|
||||||
// Cipher is the cryptographic cipher to use to decrypt the values inside the tree
|
// Cipher is the cryptographic cipher to use to decrypt the values inside the tree
|
||||||
|
@ -80,7 +82,7 @@ type DecryptTreeOpts struct {
|
||||||
|
|
||||||
// DecryptTree decrypts the tree passed in through the DecryptTreeOpts and additionally returns the decrypted data key
|
// DecryptTree decrypts the tree passed in through the DecryptTreeOpts and additionally returns the decrypted data key
|
||||||
func DecryptTree(opts DecryptTreeOpts) (dataKey []byte, err error) {
|
func DecryptTree(opts DecryptTreeOpts) (dataKey []byte, err error) {
|
||||||
dataKey, err = opts.Tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices)
|
dataKey, err = opts.Tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices, opts.DecryptionOrder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewExitError(err, codes.CouldNotRetrieveKey)
|
return nil, NewExitError(err, codes.CouldNotRetrieveKey)
|
||||||
}
|
}
|
||||||
|
@ -222,11 +224,12 @@ func GetKMSKeyWithEncryptionCtx(tree *sops.Tree) (keyGroupIndex int, keyIndex in
|
||||||
|
|
||||||
// GenericDecryptOpts represents decryption options and config
|
// GenericDecryptOpts represents decryption options and config
|
||||||
type GenericDecryptOpts struct {
|
type GenericDecryptOpts struct {
|
||||||
Cipher sops.Cipher
|
Cipher sops.Cipher
|
||||||
InputStore sops.Store
|
InputStore sops.Store
|
||||||
InputPath string
|
InputPath string
|
||||||
IgnoreMAC bool
|
IgnoreMAC bool
|
||||||
KeyServices []keyservice.KeyServiceClient
|
KeyServices []keyservice.KeyServiceClient
|
||||||
|
DecryptionOrder []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadEncryptedFileWithBugFixes is a wrapper around LoadEncryptedFile which includes
|
// LoadEncryptedFileWithBugFixes is a wrapper around LoadEncryptedFile which includes
|
||||||
|
|
|
@ -15,13 +15,14 @@ const notBinaryHint = ("This is likely not an encrypted binary file?" +
|
||||||
" If not, use --output-type to select the correct output type.")
|
" If not, use --output-type to select the correct output type.")
|
||||||
|
|
||||||
type decryptOpts struct {
|
type decryptOpts struct {
|
||||||
Cipher sops.Cipher
|
Cipher sops.Cipher
|
||||||
InputStore sops.Store
|
InputStore sops.Store
|
||||||
OutputStore sops.Store
|
OutputStore sops.Store
|
||||||
InputPath string
|
InputPath string
|
||||||
IgnoreMAC bool
|
IgnoreMAC bool
|
||||||
Extract []interface{}
|
Extract []interface{}
|
||||||
KeyServices []keyservice.KeyServiceClient
|
KeyServices []keyservice.KeyServiceClient
|
||||||
|
DecryptionOrder []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func decrypt(opts decryptOpts) (decryptedFile []byte, err error) {
|
func decrypt(opts decryptOpts) (decryptedFile []byte, err error) {
|
||||||
|
@ -37,10 +38,11 @@ func decrypt(opts decryptOpts) (decryptedFile []byte, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = common.DecryptTree(common.DecryptTreeOpts{
|
_, err = common.DecryptTree(common.DecryptTreeOpts{
|
||||||
Cipher: opts.Cipher,
|
Cipher: opts.Cipher,
|
||||||
IgnoreMac: opts.IgnoreMAC,
|
IgnoreMac: opts.IgnoreMAC,
|
||||||
Tree: tree,
|
Tree: tree,
|
||||||
KeyServices: opts.KeyServices,
|
KeyServices: opts.KeyServices,
|
||||||
|
DecryptionOrder: opts.DecryptionOrder,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -20,13 +20,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type editOpts struct {
|
type editOpts struct {
|
||||||
Cipher sops.Cipher
|
Cipher sops.Cipher
|
||||||
InputStore common.Store
|
InputStore common.Store
|
||||||
OutputStore common.Store
|
OutputStore common.Store
|
||||||
InputPath string
|
InputPath string
|
||||||
IgnoreMAC bool
|
IgnoreMAC bool
|
||||||
KeyServices []keyservice.KeyServiceClient
|
KeyServices []keyservice.KeyServiceClient
|
||||||
ShowMasterKeys bool
|
DecryptionOrder []string
|
||||||
|
ShowMasterKeys bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type editExampleOpts struct {
|
type editExampleOpts struct {
|
||||||
|
@ -96,7 +97,11 @@ func edit(opts editOpts) ([]byte, error) {
|
||||||
}
|
}
|
||||||
// Decrypt the file
|
// Decrypt the file
|
||||||
dataKey, err := common.DecryptTree(common.DecryptTreeOpts{
|
dataKey, err := common.DecryptTree(common.DecryptTreeOpts{
|
||||||
Cipher: opts.Cipher, IgnoreMac: opts.IgnoreMAC, Tree: tree, KeyServices: opts.KeyServices,
|
Cipher: opts.Cipher,
|
||||||
|
IgnoreMac: opts.IgnoreMAC,
|
||||||
|
Tree: tree,
|
||||||
|
KeyServices: opts.KeyServices,
|
||||||
|
DecryptionOrder: opts.DecryptionOrder,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
141
cmd/sops/main.go
141
cmd/sops/main.go
|
@ -1,4 +1,4 @@
|
||||||
package main //import "github.com/getsops/sops/v3/cmd/sops"
|
package main // import "github.com/getsops/sops/v3/cmd/sops"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -13,6 +13,11 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
|
||||||
"github.com/getsops/sops/v3"
|
"github.com/getsops/sops/v3"
|
||||||
"github.com/getsops/sops/v3/aes"
|
"github.com/getsops/sops/v3/aes"
|
||||||
"github.com/getsops/sops/v3/age"
|
"github.com/getsops/sops/v3/age"
|
||||||
|
@ -36,10 +41,6 @@ import (
|
||||||
"github.com/getsops/sops/v3/stores/dotenv"
|
"github.com/getsops/sops/v3/stores/dotenv"
|
||||||
"github.com/getsops/sops/v3/stores/json"
|
"github.com/getsops/sops/v3/stores/json"
|
||||||
"github.com/getsops/sops/v3/version"
|
"github.com/getsops/sops/v3/version"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var log *logrus.Logger
|
var log *logrus.Logger
|
||||||
|
@ -172,13 +173,19 @@ func main() {
|
||||||
inputStore := inputStore(c, fileName)
|
inputStore := inputStore(c, fileName)
|
||||||
|
|
||||||
svcs := keyservices(c)
|
svcs := keyservices(c)
|
||||||
|
|
||||||
|
order, err := decryptionOrder(c.String("decryption-order"))
|
||||||
|
if err != nil {
|
||||||
|
return toExitError(err)
|
||||||
|
}
|
||||||
opts := decryptOpts{
|
opts := decryptOpts{
|
||||||
OutputStore: &dotenv.Store{},
|
OutputStore: &dotenv.Store{},
|
||||||
InputStore: inputStore,
|
InputStore: inputStore,
|
||||||
InputPath: fileName,
|
InputPath: fileName,
|
||||||
Cipher: aes.NewCipher(),
|
Cipher: aes.NewCipher(),
|
||||||
KeyServices: svcs,
|
KeyServices: svcs,
|
||||||
IgnoreMAC: c.Bool("ignore-mac"),
|
DecryptionOrder: order,
|
||||||
|
IgnoreMAC: c.Bool("ignore-mac"),
|
||||||
}
|
}
|
||||||
|
|
||||||
output, err := decrypt(opts)
|
output, err := decrypt(opts)
|
||||||
|
@ -241,13 +248,19 @@ func main() {
|
||||||
outputStore := outputStore(c, fileName)
|
outputStore := outputStore(c, fileName)
|
||||||
|
|
||||||
svcs := keyservices(c)
|
svcs := keyservices(c)
|
||||||
|
|
||||||
|
order, err := decryptionOrder(c.String("decryption-order"))
|
||||||
|
if err != nil {
|
||||||
|
return toExitError(err)
|
||||||
|
}
|
||||||
opts := decryptOpts{
|
opts := decryptOpts{
|
||||||
OutputStore: outputStore,
|
OutputStore: outputStore,
|
||||||
InputStore: inputStore,
|
InputStore: inputStore,
|
||||||
InputPath: fileName,
|
InputPath: fileName,
|
||||||
Cipher: aes.NewCipher(),
|
Cipher: aes.NewCipher(),
|
||||||
KeyServices: svcs,
|
KeyServices: svcs,
|
||||||
IgnoreMAC: c.Bool("ignore-mac"),
|
DecryptionOrder: order,
|
||||||
|
IgnoreMAC: c.Bool("ignore-mac"),
|
||||||
}
|
}
|
||||||
|
|
||||||
output, err := decrypt(opts)
|
output, err := decrypt(opts)
|
||||||
|
@ -316,21 +329,25 @@ func main() {
|
||||||
if info.IsDir() && !c.Bool("recursive") {
|
if info.IsDir() && !c.Bool("recursive") {
|
||||||
return fmt.Errorf("can't operate on a directory without --recursive flag.")
|
return fmt.Errorf("can't operate on a directory without --recursive flag.")
|
||||||
}
|
}
|
||||||
|
order, err := decryptionOrder(c.String("decryption-order"))
|
||||||
|
if err != nil {
|
||||||
|
return toExitError(err)
|
||||||
|
}
|
||||||
err = filepath.Walk(path, func(subPath string, info os.FileInfo, err error) error {
|
err = filepath.Walk(path, func(subPath string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toExitError(err)
|
return toExitError(err)
|
||||||
}
|
}
|
||||||
if !info.IsDir() {
|
if !info.IsDir() {
|
||||||
err = publishcmd.Run(publishcmd.Opts{
|
err = publishcmd.Run(publishcmd.Opts{
|
||||||
ConfigPath: configPath,
|
ConfigPath: configPath,
|
||||||
InputPath: subPath,
|
InputPath: subPath,
|
||||||
Cipher: aes.NewCipher(),
|
Cipher: aes.NewCipher(),
|
||||||
KeyServices: keyservices(c),
|
KeyServices: keyservices(c),
|
||||||
InputStore: inputStore(c, subPath),
|
DecryptionOrder: order,
|
||||||
Interactive: !c.Bool("yes"),
|
InputStore: inputStore(c, subPath),
|
||||||
OmitExtensions: c.Bool("omit-extensions"),
|
Interactive: !c.Bool("yes"),
|
||||||
Recursive: c.Bool("recursive"),
|
OmitExtensions: c.Bool("omit-extensions"),
|
||||||
RootPath: path,
|
Recursive: c.Bool("recursive"),
|
||||||
})
|
})
|
||||||
if cliErr, ok := err.(*cli.ExitError); ok && cliErr != nil {
|
if cliErr, ok := err.(*cli.ExitError); ok && cliErr != nil {
|
||||||
return cliErr
|
return cliErr
|
||||||
|
@ -773,6 +790,11 @@ func main() {
|
||||||
Name: "filename-override",
|
Name: "filename-override",
|
||||||
Usage: "Use this filename instead of the provided argument for loading configuration, and for determining input type and output type",
|
Usage: "Use this filename instead of the provided argument for loading configuration, and for determining input type and output type",
|
||||||
},
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "decryption-order",
|
||||||
|
Usage: "comma separated list of decryption key types",
|
||||||
|
EnvVar: "SOPS_DECRYPTION_ORDER",
|
||||||
|
},
|
||||||
}, keyserviceFlags...)
|
}, keyserviceFlags...)
|
||||||
|
|
||||||
app.Action = func(c *cli.Context) error {
|
app.Action = func(c *cli.Context) error {
|
||||||
|
@ -859,6 +881,10 @@ func main() {
|
||||||
outputStore := outputStore(c, fileNameOverride)
|
outputStore := outputStore(c, fileNameOverride)
|
||||||
svcs := keyservices(c)
|
svcs := keyservices(c)
|
||||||
|
|
||||||
|
order, err := decryptionOrder(c.String("decryption-order"))
|
||||||
|
if err != nil {
|
||||||
|
return toExitError(err)
|
||||||
|
}
|
||||||
var output []byte
|
var output []byte
|
||||||
if c.Bool("encrypt") {
|
if c.Bool("encrypt") {
|
||||||
var groups []sops.KeyGroup
|
var groups []sops.KeyGroup
|
||||||
|
@ -894,13 +920,14 @@ func main() {
|
||||||
return common.NewExitError(fmt.Errorf("error parsing --extract path: %s", err), codes.InvalidTreePathFormat)
|
return common.NewExitError(fmt.Errorf("error parsing --extract path: %s", err), codes.InvalidTreePathFormat)
|
||||||
}
|
}
|
||||||
output, err = decrypt(decryptOpts{
|
output, err = decrypt(decryptOpts{
|
||||||
OutputStore: outputStore,
|
OutputStore: outputStore,
|
||||||
InputStore: inputStore,
|
InputStore: inputStore,
|
||||||
InputPath: fileName,
|
InputPath: fileName,
|
||||||
Cipher: aes.NewCipher(),
|
Cipher: aes.NewCipher(),
|
||||||
Extract: extract,
|
Extract: extract,
|
||||||
KeyServices: svcs,
|
KeyServices: svcs,
|
||||||
IgnoreMAC: c.Bool("ignore-mac"),
|
DecryptionOrder: order,
|
||||||
|
IgnoreMAC: c.Bool("ignore-mac"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if c.Bool("rotate") {
|
if c.Bool("rotate") {
|
||||||
|
@ -975,6 +1002,7 @@ func main() {
|
||||||
InputPath: fileName,
|
InputPath: fileName,
|
||||||
Cipher: aes.NewCipher(),
|
Cipher: aes.NewCipher(),
|
||||||
KeyServices: svcs,
|
KeyServices: svcs,
|
||||||
|
DecryptionOrder: order,
|
||||||
IgnoreMAC: c.Bool("ignore-mac"),
|
IgnoreMAC: c.Bool("ignore-mac"),
|
||||||
AddMasterKeys: addMasterKeys,
|
AddMasterKeys: addMasterKeys,
|
||||||
RemoveMasterKeys: rmMasterKeys,
|
RemoveMasterKeys: rmMasterKeys,
|
||||||
|
@ -994,14 +1022,15 @@ func main() {
|
||||||
return toExitError(err)
|
return toExitError(err)
|
||||||
}
|
}
|
||||||
output, err = set(setOpts{
|
output, err = set(setOpts{
|
||||||
OutputStore: outputStore,
|
OutputStore: outputStore,
|
||||||
InputStore: inputStore,
|
InputStore: inputStore,
|
||||||
InputPath: fileName,
|
InputPath: fileName,
|
||||||
Cipher: aes.NewCipher(),
|
Cipher: aes.NewCipher(),
|
||||||
KeyServices: svcs,
|
KeyServices: svcs,
|
||||||
IgnoreMAC: c.Bool("ignore-mac"),
|
DecryptionOrder: order,
|
||||||
Value: value,
|
IgnoreMAC: c.Bool("ignore-mac"),
|
||||||
TreePath: path,
|
Value: value,
|
||||||
|
TreePath: path,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1010,13 +1039,14 @@ func main() {
|
||||||
_, statErr := os.Stat(fileName)
|
_, statErr := os.Stat(fileName)
|
||||||
fileExists := statErr == nil
|
fileExists := statErr == nil
|
||||||
opts := editOpts{
|
opts := editOpts{
|
||||||
OutputStore: outputStore,
|
OutputStore: outputStore,
|
||||||
InputStore: inputStore,
|
InputStore: inputStore,
|
||||||
InputPath: fileName,
|
InputPath: fileName,
|
||||||
Cipher: aes.NewCipher(),
|
Cipher: aes.NewCipher(),
|
||||||
KeyServices: svcs,
|
KeyServices: svcs,
|
||||||
IgnoreMAC: c.Bool("ignore-mac"),
|
DecryptionOrder: order,
|
||||||
ShowMasterKeys: c.Bool("show-master-keys"),
|
IgnoreMAC: c.Bool("ignore-mac"),
|
||||||
|
ShowMasterKeys: c.Bool("show-master-keys"),
|
||||||
}
|
}
|
||||||
if fileExists {
|
if fileExists {
|
||||||
output, err = edit(opts)
|
output, err = edit(opts)
|
||||||
|
@ -1351,3 +1381,18 @@ func extractSetArguments(set string) (path []interface{}, valueToInsert interfac
|
||||||
}
|
}
|
||||||
return path, valueToInsert, nil
|
return path, valueToInsert, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decryptionOrder(decryptionOrder string) ([]string, error) {
|
||||||
|
if decryptionOrder == "" {
|
||||||
|
return sops.DefaultDecryptionOrder, nil
|
||||||
|
}
|
||||||
|
orderList := strings.Split(decryptionOrder, ",")
|
||||||
|
unique := make(map[string]struct{})
|
||||||
|
for _, v := range orderList {
|
||||||
|
if _, ok := unique[v]; ok {
|
||||||
|
return nil, common.NewExitError(fmt.Sprintf("Duplicate decryption key type: %s", v), codes.DuplicateDecryptionKeyType)
|
||||||
|
}
|
||||||
|
unique[v] = struct{}{}
|
||||||
|
}
|
||||||
|
return orderList, nil
|
||||||
|
}
|
||||||
|
|
|
@ -20,15 +20,17 @@ type rotateOpts struct {
|
||||||
AddMasterKeys []keys.MasterKey
|
AddMasterKeys []keys.MasterKey
|
||||||
RemoveMasterKeys []keys.MasterKey
|
RemoveMasterKeys []keys.MasterKey
|
||||||
KeyServices []keyservice.KeyServiceClient
|
KeyServices []keyservice.KeyServiceClient
|
||||||
|
DecryptionOrder []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func rotate(opts rotateOpts) ([]byte, error) {
|
func rotate(opts rotateOpts) ([]byte, error) {
|
||||||
tree, err := common.LoadEncryptedFileWithBugFixes(common.GenericDecryptOpts{
|
tree, err := common.LoadEncryptedFileWithBugFixes(common.GenericDecryptOpts{
|
||||||
Cipher: opts.Cipher,
|
Cipher: opts.Cipher,
|
||||||
InputStore: opts.InputStore,
|
InputStore: opts.InputStore,
|
||||||
InputPath: opts.InputPath,
|
InputPath: opts.InputPath,
|
||||||
IgnoreMAC: opts.IgnoreMAC,
|
IgnoreMAC: opts.IgnoreMAC,
|
||||||
KeyServices: opts.KeyServices,
|
KeyServices: opts.KeyServices,
|
||||||
|
DecryptionOrder: opts.DecryptionOrder,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -39,8 +41,11 @@ func rotate(opts rotateOpts) ([]byte, error) {
|
||||||
})
|
})
|
||||||
|
|
||||||
_, err = common.DecryptTree(common.DecryptTreeOpts{
|
_, err = common.DecryptTree(common.DecryptTreeOpts{
|
||||||
Cipher: opts.Cipher, IgnoreMac: opts.IgnoreMAC, Tree: tree,
|
Cipher: opts.Cipher,
|
||||||
KeyServices: opts.KeyServices,
|
IgnoreMac: opts.IgnoreMAC,
|
||||||
|
Tree: tree,
|
||||||
|
KeyServices: opts.KeyServices,
|
||||||
|
DecryptionOrder: opts.DecryptionOrder,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -10,14 +10,15 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type setOpts struct {
|
type setOpts struct {
|
||||||
Cipher sops.Cipher
|
Cipher sops.Cipher
|
||||||
InputStore sops.Store
|
InputStore sops.Store
|
||||||
OutputStore sops.Store
|
OutputStore sops.Store
|
||||||
InputPath string
|
InputPath string
|
||||||
IgnoreMAC bool
|
IgnoreMAC bool
|
||||||
TreePath []interface{}
|
TreePath []interface{}
|
||||||
Value interface{}
|
Value interface{}
|
||||||
KeyServices []keyservice.KeyServiceClient
|
KeyServices []keyservice.KeyServiceClient
|
||||||
|
DecryptionOrder []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func set(opts setOpts) ([]byte, error) {
|
func set(opts setOpts) ([]byte, error) {
|
||||||
|
@ -36,10 +37,11 @@ func set(opts setOpts) ([]byte, error) {
|
||||||
|
|
||||||
// Decrypt the file
|
// Decrypt the file
|
||||||
dataKey, err := common.DecryptTree(common.DecryptTreeOpts{
|
dataKey, err := common.DecryptTree(common.DecryptTreeOpts{
|
||||||
Cipher: opts.Cipher,
|
Cipher: opts.Cipher,
|
||||||
IgnoreMac: opts.IgnoreMAC,
|
IgnoreMac: opts.IgnoreMAC,
|
||||||
Tree: tree,
|
Tree: tree,
|
||||||
KeyServices: opts.KeyServices,
|
KeyServices: opts.KeyServices,
|
||||||
|
DecryptionOrder: opts.DecryptionOrder,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -10,13 +10,14 @@ import (
|
||||||
|
|
||||||
// AddOpts are the options for adding a key group to a SOPS file
|
// AddOpts are the options for adding a key group to a SOPS file
|
||||||
type AddOpts struct {
|
type AddOpts struct {
|
||||||
InputPath string
|
InputPath string
|
||||||
InputStore sops.Store
|
InputStore sops.Store
|
||||||
OutputStore sops.Store
|
OutputStore sops.Store
|
||||||
Group sops.KeyGroup
|
Group sops.KeyGroup
|
||||||
GroupThreshold int
|
GroupThreshold int
|
||||||
InPlace bool
|
InPlace bool
|
||||||
KeyServices []keyservice.KeyServiceClient
|
KeyServices []keyservice.KeyServiceClient
|
||||||
|
DecryptionOrder []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds a key group to a SOPS file
|
// Add adds a key group to a SOPS file
|
||||||
|
@ -25,7 +26,7 @@ func Add(opts AddOpts) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dataKey, err := tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices)
|
dataKey, err := tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices, opts.DecryptionOrder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,13 +12,14 @@ import (
|
||||||
|
|
||||||
// DeleteOpts are the options for deleting a key group from a SOPS file
|
// DeleteOpts are the options for deleting a key group from a SOPS file
|
||||||
type DeleteOpts struct {
|
type DeleteOpts struct {
|
||||||
InputPath string
|
InputPath string
|
||||||
InputStore sops.Store
|
InputStore sops.Store
|
||||||
OutputStore sops.Store
|
OutputStore sops.Store
|
||||||
Group uint
|
Group uint
|
||||||
GroupThreshold int
|
GroupThreshold int
|
||||||
InPlace bool
|
InPlace bool
|
||||||
KeyServices []keyservice.KeyServiceClient
|
KeyServices []keyservice.KeyServiceClient
|
||||||
|
DecryptionOrder []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes a key group from a SOPS file
|
// Delete deletes a key group from a SOPS file
|
||||||
|
@ -27,7 +28,7 @@ func Delete(opts DeleteOpts) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dataKey, err := tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices)
|
dataKey, err := tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices, opts.DecryptionOrder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,15 +27,16 @@ func init() {
|
||||||
|
|
||||||
// Opts represents publish options and config
|
// Opts represents publish options and config
|
||||||
type Opts struct {
|
type Opts struct {
|
||||||
Interactive bool
|
Interactive bool
|
||||||
Cipher sops.Cipher
|
Cipher sops.Cipher
|
||||||
ConfigPath string
|
ConfigPath string
|
||||||
InputPath string
|
InputPath string
|
||||||
KeyServices []keyservice.KeyServiceClient
|
KeyServices []keyservice.KeyServiceClient
|
||||||
InputStore sops.Store
|
DecryptionOrder []string
|
||||||
OmitExtensions bool
|
InputStore sops.Store
|
||||||
Recursive bool
|
OmitExtensions bool
|
||||||
RootPath string
|
Recursive bool
|
||||||
|
RootPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run publish operation
|
// Run publish operation
|
||||||
|
@ -81,10 +82,11 @@ func Run(opts Opts) error {
|
||||||
if len(conf.KeyGroups[0]) != 0 {
|
if len(conf.KeyGroups[0]) != 0 {
|
||||||
log.Debug("Re-encrypting tree before publishing")
|
log.Debug("Re-encrypting tree before publishing")
|
||||||
_, err = common.DecryptTree(common.DecryptTreeOpts{
|
_, err = common.DecryptTree(common.DecryptTreeOpts{
|
||||||
Cipher: opts.Cipher,
|
Cipher: opts.Cipher,
|
||||||
IgnoreMac: false,
|
IgnoreMac: false,
|
||||||
Tree: tree,
|
Tree: tree,
|
||||||
KeyServices: opts.KeyServices,
|
KeyServices: opts.KeyServices,
|
||||||
|
DecryptionOrder: opts.DecryptionOrder,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -137,10 +139,11 @@ func Run(opts Opts) error {
|
||||||
}
|
}
|
||||||
case *publish.VaultDestination:
|
case *publish.VaultDestination:
|
||||||
_, err = common.DecryptTree(common.DecryptTreeOpts{
|
_, err = common.DecryptTree(common.DecryptTreeOpts{
|
||||||
Cipher: opts.Cipher,
|
Cipher: opts.Cipher,
|
||||||
IgnoreMac: false,
|
IgnoreMac: false,
|
||||||
Tree: tree,
|
Tree: tree,
|
||||||
KeyServices: opts.KeyServices,
|
KeyServices: opts.KeyServices,
|
||||||
|
DecryptionOrder: opts.DecryptionOrder,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -14,12 +14,13 @@ import (
|
||||||
|
|
||||||
// Opts represents key operation options and config
|
// Opts represents key operation options and config
|
||||||
type Opts struct {
|
type Opts struct {
|
||||||
InputPath string
|
InputPath string
|
||||||
GroupQuorum int
|
GroupQuorum int
|
||||||
KeyServices []keyservice.KeyServiceClient
|
KeyServices []keyservice.KeyServiceClient
|
||||||
Interactive bool
|
DecryptionOrder []string
|
||||||
ConfigPath string
|
Interactive bool
|
||||||
InputType string
|
ConfigPath string
|
||||||
|
InputType string
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateKeys update the keys for a given file
|
// UpdateKeys update the keys for a given file
|
||||||
|
@ -83,7 +84,7 @@ func updateFile(opts Opts) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
key, err := tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices)
|
key, err := tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices, opts.DecryptionOrder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.NewExitError(err, codes.CouldNotRetrieveKey)
|
return common.NewExitError(err, codes.CouldNotRetrieveKey)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ const (
|
||||||
// a path to a credentials file, or directly as the variable's value in JSON
|
// a path to a credentials file, or directly as the variable's value in JSON
|
||||||
// format.
|
// format.
|
||||||
SopsGoogleCredentialsEnv = "GOOGLE_CREDENTIALS"
|
SopsGoogleCredentialsEnv = "GOOGLE_CREDENTIALS"
|
||||||
|
// KeyTypeIdentifier is the string used to identify a GCP KMS MasterKey.
|
||||||
|
KeyTypeIdentifier = "gcp_kms"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -196,6 +198,11 @@ func (key MasterKey) ToMap() map[string]interface{} {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TypeToIdentifier returns the string identifier for the MasterKey type.
|
||||||
|
func (key *MasterKey) TypeToIdentifier() string {
|
||||||
|
return KeyTypeIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
// newKMSClient returns a GCP KMS client configured with the credentialJSON
|
// newKMSClient returns a GCP KMS client configured with the credentialJSON
|
||||||
// and/or grpcConn, falling back to environmental defaults.
|
// and/or grpcConn, falling back to environmental defaults.
|
||||||
// It returns an error if the ResourceID is invalid, or if the setup of the
|
// It returns an error if the ResourceID is invalid, or if the setup of the
|
||||||
|
|
|
@ -21,6 +21,11 @@ import (
|
||||||
"github.com/getsops/sops/v3/logging"
|
"github.com/getsops/sops/v3/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// KeyTypeIdentifier is the string used to identify a Vault MasterKey.
|
||||||
|
KeyTypeIdentifier = "hc_vault"
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
log = logging.NewLogger("VAULT_TRANSIT")
|
log = logging.NewLogger("VAULT_TRANSIT")
|
||||||
}
|
}
|
||||||
|
@ -216,6 +221,11 @@ func (key MasterKey) ToMap() map[string]interface{} {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TypeToIdentifier returns the string identifier for the MasterKey type.
|
||||||
|
func (key *MasterKey) TypeToIdentifier() string {
|
||||||
|
return KeyTypeIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
// encryptPath returns the path for Encrypt requests.
|
// encryptPath returns the path for Encrypt requests.
|
||||||
func (key *MasterKey) encryptPath() string {
|
func (key *MasterKey) encryptPath() string {
|
||||||
return path.Join(key.EnginePath, "encrypt", key.KeyName)
|
return path.Join(key.EnginePath, "encrypt", key.KeyName)
|
||||||
|
|
|
@ -10,4 +10,5 @@ type MasterKey interface {
|
||||||
NeedsRotation() bool
|
NeedsRotation() bool
|
||||||
ToString() string
|
ToString() string
|
||||||
ToMap() map[string]interface{}
|
ToMap() map[string]interface{}
|
||||||
|
TypeToIdentifier() string
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ Package kms contains an implementation of the github.com/getsops/sops/v3.MasterK
|
||||||
interface that encrypts and decrypts the data key using AWS KMS with the SDK
|
interface that encrypts and decrypts the data key using AWS KMS with the SDK
|
||||||
for Go V2.
|
for Go V2.
|
||||||
*/
|
*/
|
||||||
package kms //import "github.com/getsops/sops/v3/kms"
|
package kms // import "github.com/getsops/sops/v3/kms"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -19,8 +19,9 @@ import (
|
||||||
"github.com/aws/aws-sdk-go-v2/credentials"
|
"github.com/aws/aws-sdk-go-v2/credentials"
|
||||||
"github.com/aws/aws-sdk-go-v2/service/kms"
|
"github.com/aws/aws-sdk-go-v2/service/kms"
|
||||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||||
"github.com/getsops/sops/v3/logging"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/getsops/sops/v3/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -34,6 +35,8 @@ const (
|
||||||
roleSessionNameLengthLimit = 64
|
roleSessionNameLengthLimit = 64
|
||||||
// kmsTTL is the duration after which a MasterKey requires rotation.
|
// kmsTTL is the duration after which a MasterKey requires rotation.
|
||||||
kmsTTL = time.Hour * 24 * 30 * 6
|
kmsTTL = time.Hour * 24 * 30 * 6
|
||||||
|
// KeyTypeIdentifier is the string used to identify an AWS KMS MasterKey.
|
||||||
|
KeyTypeIdentifier = "kms"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -297,6 +300,11 @@ func (key MasterKey) ToMap() map[string]interface{} {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TypeToIdentifier returns the string identifier for the MasterKey type.
|
||||||
|
func (key *MasterKey) TypeToIdentifier() string {
|
||||||
|
return KeyTypeIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
// createKMSConfig returns an AWS config with the credentialsProvider of the
|
// createKMSConfig returns an AWS config with the credentialsProvider of the
|
||||||
// MasterKey, or the default configuration sources.
|
// MasterKey, or the default configuration sources.
|
||||||
func (key MasterKey) createKMSConfig() (*aws.Config, error) {
|
func (key MasterKey) createKMSConfig() (*aws.Config, error) {
|
||||||
|
|
|
@ -4,7 +4,7 @@ interface that encrypts and decrypts the data key by first trying with the
|
||||||
github.com/ProtonMail/go-crypto/openpgp package and if that fails, by calling
|
github.com/ProtonMail/go-crypto/openpgp package and if that fails, by calling
|
||||||
the "gpg" binary.
|
the "gpg" binary.
|
||||||
*/
|
*/
|
||||||
package pgp //import "github.com/getsops/sops/v3/pgp"
|
package pgp // import "github.com/getsops/sops/v3/pgp"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -22,12 +22,15 @@ import (
|
||||||
"github.com/ProtonMail/go-crypto/openpgp"
|
"github.com/ProtonMail/go-crypto/openpgp"
|
||||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||||
gpgagent "github.com/getsops/gopgagent"
|
gpgagent "github.com/getsops/gopgagent"
|
||||||
"github.com/getsops/sops/v3/logging"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
|
|
||||||
|
"github.com/getsops/sops/v3/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// KeyTypeIdentifier is the string used to identify a PGP MasterKey.
|
||||||
|
KeyTypeIdentifier = "pgp"
|
||||||
// SopsGpgExecEnv can be set as an environment variable to overwrite the
|
// SopsGpgExecEnv can be set as an environment variable to overwrite the
|
||||||
// GnuPG binary used.
|
// GnuPG binary used.
|
||||||
SopsGpgExecEnv = "SOPS_GPG_EXEC"
|
SopsGpgExecEnv = "SOPS_GPG_EXEC"
|
||||||
|
@ -449,6 +452,11 @@ func (key MasterKey) ToMap() map[string]interface{} {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TypeToIdentifier returns the string identifier for the MasterKey type.
|
||||||
|
func (key *MasterKey) TypeToIdentifier() string {
|
||||||
|
return KeyTypeIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
// retrievePubKey attempts to retrieve the public key from the public keyring
|
// retrievePubKey attempts to retrieve the public key from the public keyring
|
||||||
// by Fingerprint.
|
// by Fingerprint.
|
||||||
func (key *MasterKey) retrievePubKey() (openpgp.Entity, error) {
|
func (key *MasterKey) retrievePubKey() (openpgp.Entity, error) {
|
||||||
|
|
57
sops.go
57
sops.go
|
@ -34,7 +34,7 @@ be recalculated and compared with the MAC stored in the document to verify that
|
||||||
fraudulent changes have been applied. The MAC covers keys and values as well as their
|
fraudulent changes have been applied. The MAC covers keys and values as well as their
|
||||||
ordering.
|
ordering.
|
||||||
*/
|
*/
|
||||||
package sops //import "github.com/getsops/sops/v3"
|
package sops // import "github.com/getsops/sops/v3"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
@ -42,22 +42,28 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
"github.com/getsops/sops/v3/age"
|
||||||
"github.com/getsops/sops/v3/audit"
|
"github.com/getsops/sops/v3/audit"
|
||||||
"github.com/getsops/sops/v3/keys"
|
"github.com/getsops/sops/v3/keys"
|
||||||
"github.com/getsops/sops/v3/keyservice"
|
"github.com/getsops/sops/v3/keyservice"
|
||||||
"github.com/getsops/sops/v3/logging"
|
"github.com/getsops/sops/v3/logging"
|
||||||
|
"github.com/getsops/sops/v3/pgp"
|
||||||
"github.com/getsops/sops/v3/shamir"
|
"github.com/getsops/sops/v3/shamir"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultUnencryptedSuffix is the default suffix a TreeItem key has to end with for sops to leave its Value unencrypted
|
// DefaultUnencryptedSuffix is the default suffix a TreeItem key has to end with for sops to leave its Value unencrypted
|
||||||
const DefaultUnencryptedSuffix = "_unencrypted"
|
const DefaultUnencryptedSuffix = "_unencrypted"
|
||||||
|
|
||||||
|
var DefaultDecryptionOrder = []string{age.KeyTypeIdentifier, pgp.KeyTypeIdentifier}
|
||||||
|
|
||||||
type sopsError string
|
type sopsError string
|
||||||
|
|
||||||
func (e sopsError) Error() string {
|
func (e sopsError) Error() string {
|
||||||
|
@ -648,7 +654,7 @@ func (m *Metadata) UpdateMasterKeys(dataKey []byte) (errs []error) {
|
||||||
|
|
||||||
// GetDataKeyWithKeyServices retrieves the data key, asking KeyServices to decrypt it with each
|
// GetDataKeyWithKeyServices retrieves the data key, asking KeyServices to decrypt it with each
|
||||||
// MasterKey in the Metadata's KeySources until one of them succeeds.
|
// MasterKey in the Metadata's KeySources until one of them succeeds.
|
||||||
func (m Metadata) GetDataKeyWithKeyServices(svcs []keyservice.KeyServiceClient) ([]byte, error) {
|
func (m Metadata) GetDataKeyWithKeyServices(svcs []keyservice.KeyServiceClient, decryptionOrder []string) ([]byte, error) {
|
||||||
if m.DataKey != nil {
|
if m.DataKey != nil {
|
||||||
return m.DataKey, nil
|
return m.DataKey, nil
|
||||||
}
|
}
|
||||||
|
@ -658,7 +664,7 @@ func (m Metadata) GetDataKeyWithKeyServices(svcs []keyservice.KeyServiceClient)
|
||||||
}
|
}
|
||||||
var parts [][]byte
|
var parts [][]byte
|
||||||
for i, group := range m.KeyGroups {
|
for i, group := range m.KeyGroups {
|
||||||
part, err := decryptKeyGroup(group, svcs)
|
part, err := decryptKeyGroup(group, svcs, decryptionOrder)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
parts = append(parts, part)
|
parts = append(parts, part)
|
||||||
}
|
}
|
||||||
|
@ -688,9 +694,13 @@ func (m Metadata) GetDataKeyWithKeyServices(svcs []keyservice.KeyServiceClient)
|
||||||
// decryptKeyGroup tries to decrypt the contents of the provided KeyGroup with
|
// decryptKeyGroup tries to decrypt the contents of the provided KeyGroup with
|
||||||
// any of the MasterKeys in the KeyGroup with any of the provided key services,
|
// any of the MasterKeys in the KeyGroup with any of the provided key services,
|
||||||
// returning as soon as one key service succeeds.
|
// returning as soon as one key service succeeds.
|
||||||
func decryptKeyGroup(group KeyGroup, svcs []keyservice.KeyServiceClient) ([]byte, error) {
|
func decryptKeyGroup(group KeyGroup, svcs []keyservice.KeyServiceClient, decryptionOrder []string) ([]byte, error) {
|
||||||
var keyErrs []error
|
var keyErrs []error
|
||||||
for _, key := range group {
|
// Sort MasterKeys in the group so we try them in specific order
|
||||||
|
// Use sorted indices to avoid group slice modification
|
||||||
|
indices := sortKeyGroupIndices(group, decryptionOrder)
|
||||||
|
for _, indexVal := range indices {
|
||||||
|
key := group[indexVal]
|
||||||
part, err := decryptKey(key, svcs)
|
part, err := decryptKey(key, svcs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
keyErrs = append(keyErrs, err)
|
keyErrs = append(keyErrs, err)
|
||||||
|
@ -701,6 +711,37 @@ func decryptKeyGroup(group KeyGroup, svcs []keyservice.KeyServiceClient) ([]byte
|
||||||
return nil, decryptKeyErrors(keyErrs)
|
return nil, decryptKeyErrors(keyErrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sortKeyGroupIndices returns indices that would sort the KeyGroup
|
||||||
|
// according to decryptionOrder
|
||||||
|
func sortKeyGroupIndices(group KeyGroup, decryptionOrder []string) []int {
|
||||||
|
priorities := make(map[string]int)
|
||||||
|
// give ordered weights
|
||||||
|
for i, v := range decryptionOrder {
|
||||||
|
priorities[v] = i
|
||||||
|
}
|
||||||
|
maxPriority := len(decryptionOrder)
|
||||||
|
// initialize indices
|
||||||
|
n := len(group)
|
||||||
|
indices := make([]int, n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
indices[i] = i
|
||||||
|
}
|
||||||
|
sort.SliceStable(indices, func(i, j int) bool {
|
||||||
|
keyTypeI := group[indices[i]].TypeToIdentifier()
|
||||||
|
keyTypeJ := group[indices[j]].TypeToIdentifier()
|
||||||
|
priorityI, ok := priorities[keyTypeI]
|
||||||
|
if !ok {
|
||||||
|
priorityI = maxPriority
|
||||||
|
}
|
||||||
|
priorityJ, ok := priorities[keyTypeJ]
|
||||||
|
if !ok {
|
||||||
|
priorityJ = maxPriority
|
||||||
|
}
|
||||||
|
return priorityI < priorityJ
|
||||||
|
})
|
||||||
|
return indices
|
||||||
|
}
|
||||||
|
|
||||||
// decryptKey tries to decrypt the contents of the provided MasterKey with any
|
// decryptKey tries to decrypt the contents of the provided MasterKey with any
|
||||||
// of the key services, returning as soon as one key service succeeds.
|
// of the key services, returning as soon as one key service succeeds.
|
||||||
func decryptKey(key keys.MasterKey, svcs []keyservice.KeyServiceClient) ([]byte, error) {
|
func decryptKey(key keys.MasterKey, svcs []keyservice.KeyServiceClient) ([]byte, error) {
|
||||||
|
@ -739,7 +780,7 @@ func decryptKey(key keys.MasterKey, svcs []keyservice.KeyServiceClient) ([]byte,
|
||||||
func (m Metadata) GetDataKey() ([]byte, error) {
|
func (m Metadata) GetDataKey() ([]byte, error) {
|
||||||
return m.GetDataKeyWithKeyServices([]keyservice.KeyServiceClient{
|
return m.GetDataKeyWithKeyServices([]keyservice.KeyServiceClient{
|
||||||
keyservice.NewLocalClient(),
|
keyservice.NewLocalClient(),
|
||||||
})
|
}, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToBytes converts a string, int, float or bool to a byte representation.
|
// ToBytes converts a string, int, float or bool to a byte representation.
|
||||||
|
|
62
sops_test.go
62
sops_test.go
|
@ -7,6 +7,10 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/getsops/sops/v3/age"
|
||||||
|
"github.com/getsops/sops/v3/hcvault"
|
||||||
|
"github.com/getsops/sops/v3/pgp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type reverseCipher struct{}
|
type reverseCipher struct{}
|
||||||
|
@ -842,3 +846,61 @@ func TestEmitAsMap(t *testing.T) {
|
||||||
assert.Equal(t, expected, data)
|
assert.Equal(t, expected, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSortKeyGroupIndices(t *testing.T) {
|
||||||
|
t.Run("default order", func(t *testing.T) {
|
||||||
|
group := KeyGroup{&hcvault.MasterKey{}, &age.MasterKey{}, &pgp.MasterKey{}}
|
||||||
|
expected := []int{1, 2, 0}
|
||||||
|
indices := sortKeyGroupIndices(group, DefaultDecryptionOrder)
|
||||||
|
assert.Equal(t, expected, indices)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("different keygroup", func(t *testing.T) {
|
||||||
|
group := KeyGroup{&hcvault.MasterKey{}, &pgp.MasterKey{}, &age.MasterKey{}}
|
||||||
|
expected := []int{2, 1, 0}
|
||||||
|
indices := sortKeyGroupIndices(group, DefaultDecryptionOrder)
|
||||||
|
assert.Equal(t, expected, indices)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("repeated key", func(t *testing.T) {
|
||||||
|
group := KeyGroup{&pgp.MasterKey{}, &hcvault.MasterKey{}, &pgp.MasterKey{}, &age.MasterKey{}}
|
||||||
|
expected := []int{3, 0, 2, 1}
|
||||||
|
indices := sortKeyGroupIndices(group, DefaultDecryptionOrder)
|
||||||
|
assert.Equal(t, expected, indices)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("full order", func(t *testing.T) {
|
||||||
|
group := KeyGroup{&hcvault.MasterKey{}, &pgp.MasterKey{}, &age.MasterKey{}}
|
||||||
|
expected := []int{1, 2, 0}
|
||||||
|
indices := sortKeyGroupIndices(group, []string{"pgp", "age", "hc_vault"})
|
||||||
|
assert.Equal(t, expected, indices)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty order", func(t *testing.T) {
|
||||||
|
group := KeyGroup{&hcvault.MasterKey{}, &pgp.MasterKey{}, &age.MasterKey{}}
|
||||||
|
expected := []int{0, 1, 2}
|
||||||
|
indices := sortKeyGroupIndices(group, []string{})
|
||||||
|
assert.Equal(t, expected, indices)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("one match", func(t *testing.T) {
|
||||||
|
group := KeyGroup{&hcvault.MasterKey{}, &pgp.MasterKey{}, &age.MasterKey{}}
|
||||||
|
expected := []int{2, 0, 1}
|
||||||
|
indices := sortKeyGroupIndices(group, []string{"azure_kv", "age"})
|
||||||
|
assert.Equal(t, expected, indices)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nonmatching order", func(t *testing.T) {
|
||||||
|
group := KeyGroup{&pgp.MasterKey{}, &hcvault.MasterKey{}, &age.MasterKey{}}
|
||||||
|
expected := []int{0, 1, 2}
|
||||||
|
indices := sortKeyGroupIndices(group, []string{"azure_kv"})
|
||||||
|
assert.Equal(t, expected, indices)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nonexistent keys", func(t *testing.T) {
|
||||||
|
group := KeyGroup{&hcvault.MasterKey{}, &pgp.MasterKey{}, &age.MasterKey{}}
|
||||||
|
expected := []int{2, 1, 0}
|
||||||
|
indices := sortKeyGroupIndices(group, []string{"dummy1", "age", "dummy2", "pgp", "dummy3"})
|
||||||
|
assert.Equal(t, expected, indices)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче