зеркало из 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
|
||||
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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -12,8 +12,9 @@ import (
|
|||
|
||||
"filippo.io/age"
|
||||
"filippo.io/age/armor"
|
||||
"github.com/getsops/sops/v3/logging"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/getsops/sops/v3/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -28,6 +29,8 @@ const (
|
|||
SopsAgeKeyUserConfigPath = "sops/age/keys.txt"
|
||||
// On macOS, os.UserConfigDir() ignores XDG_CONFIG_HOME. So we handle that manually.
|
||||
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.
|
||||
|
@ -225,6 +228,11 @@ func (key *MasterKey) ToMap() map[string]interface{} {
|
|||
return out
|
||||
}
|
||||
|
||||
// TypeToIdentifier returns the string identifier for the MasterKey type.
|
||||
func (key *MasterKey) TypeToIdentifier() string {
|
||||
return KeyTypeIdentifier
|
||||
}
|
||||
|
||||
func getUserConfigDir() (string, error) {
|
||||
if runtime.GOOS == "darwin" {
|
||||
if userConfigDir, ok := os.LookupEnv(xdgConfigHome); ok && userConfigDir != "" {
|
||||
|
|
|
@ -22,6 +22,11 @@ import (
|
|||
"github.com/getsops/sops/v3/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
// KeyTypeIdentifier is the string used to identify an Azure Key Vault MasterKey.
|
||||
KeyTypeIdentifier = "azure_kv"
|
||||
)
|
||||
|
||||
var (
|
||||
// log is the global logger for any Azure Key Vault MasterKey.
|
||||
log *logrus.Logger
|
||||
|
@ -215,6 +220,11 @@ func (key MasterKey) ToMap() map[string]interface{} {
|
|||
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
|
||||
// azidentity.NewDefaultAzureCredential.
|
||||
func (key *MasterKey) getTokenCredential() (azcore.TokenCredential, error) {
|
||||
|
|
|
@ -25,6 +25,7 @@ const (
|
|||
NoFileSpecified int = 100
|
||||
CouldNotRetrieveKey int = 128
|
||||
NoEncryptionKeyFound int = 111
|
||||
DuplicateDecryptionKeyType int = 112
|
||||
FileHasNotBeenModified int = 200
|
||||
NoEditorFound int = 201
|
||||
FailedToCompareVersions int = 202
|
||||
|
|
|
@ -72,6 +72,8 @@ type DecryptTreeOpts struct {
|
|||
Tree *sops.Tree
|
||||
// KeyServices are the key services to be used for decryption of the data key
|
||||
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 bool
|
||||
// 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
|
||||
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 {
|
||||
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
|
||||
type GenericDecryptOpts struct {
|
||||
Cipher sops.Cipher
|
||||
InputStore sops.Store
|
||||
InputPath string
|
||||
IgnoreMAC bool
|
||||
KeyServices []keyservice.KeyServiceClient
|
||||
Cipher sops.Cipher
|
||||
InputStore sops.Store
|
||||
InputPath string
|
||||
IgnoreMAC bool
|
||||
KeyServices []keyservice.KeyServiceClient
|
||||
DecryptionOrder []string
|
||||
}
|
||||
|
||||
// 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.")
|
||||
|
||||
type decryptOpts struct {
|
||||
Cipher sops.Cipher
|
||||
InputStore sops.Store
|
||||
OutputStore sops.Store
|
||||
InputPath string
|
||||
IgnoreMAC bool
|
||||
Extract []interface{}
|
||||
KeyServices []keyservice.KeyServiceClient
|
||||
Cipher sops.Cipher
|
||||
InputStore sops.Store
|
||||
OutputStore sops.Store
|
||||
InputPath string
|
||||
IgnoreMAC bool
|
||||
Extract []interface{}
|
||||
KeyServices []keyservice.KeyServiceClient
|
||||
DecryptionOrder []string
|
||||
}
|
||||
|
||||
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{
|
||||
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 {
|
||||
return nil, err
|
||||
|
|
|
@ -20,13 +20,14 @@ import (
|
|||
)
|
||||
|
||||
type editOpts struct {
|
||||
Cipher sops.Cipher
|
||||
InputStore common.Store
|
||||
OutputStore common.Store
|
||||
InputPath string
|
||||
IgnoreMAC bool
|
||||
KeyServices []keyservice.KeyServiceClient
|
||||
ShowMasterKeys bool
|
||||
Cipher sops.Cipher
|
||||
InputStore common.Store
|
||||
OutputStore common.Store
|
||||
InputPath string
|
||||
IgnoreMAC bool
|
||||
KeyServices []keyservice.KeyServiceClient
|
||||
DecryptionOrder []string
|
||||
ShowMasterKeys bool
|
||||
}
|
||||
|
||||
type editExampleOpts struct {
|
||||
|
@ -96,7 +97,11 @@ func edit(opts editOpts) ([]byte, error) {
|
|||
}
|
||||
// Decrypt the file
|
||||
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 {
|
||||
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 (
|
||||
"context"
|
||||
|
@ -13,6 +13,11 @@ import (
|
|||
"strconv"
|
||||
"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/aes"
|
||||
"github.com/getsops/sops/v3/age"
|
||||
|
@ -36,10 +41,6 @@ import (
|
|||
"github.com/getsops/sops/v3/stores/dotenv"
|
||||
"github.com/getsops/sops/v3/stores/json"
|
||||
"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
|
||||
|
@ -172,13 +173,19 @@ func main() {
|
|||
inputStore := inputStore(c, fileName)
|
||||
|
||||
svcs := keyservices(c)
|
||||
|
||||
order, err := decryptionOrder(c.String("decryption-order"))
|
||||
if err != nil {
|
||||
return toExitError(err)
|
||||
}
|
||||
opts := decryptOpts{
|
||||
OutputStore: &dotenv.Store{},
|
||||
InputStore: inputStore,
|
||||
InputPath: fileName,
|
||||
Cipher: aes.NewCipher(),
|
||||
KeyServices: svcs,
|
||||
IgnoreMAC: c.Bool("ignore-mac"),
|
||||
OutputStore: &dotenv.Store{},
|
||||
InputStore: inputStore,
|
||||
InputPath: fileName,
|
||||
Cipher: aes.NewCipher(),
|
||||
KeyServices: svcs,
|
||||
DecryptionOrder: order,
|
||||
IgnoreMAC: c.Bool("ignore-mac"),
|
||||
}
|
||||
|
||||
output, err := decrypt(opts)
|
||||
|
@ -241,13 +248,19 @@ func main() {
|
|||
outputStore := outputStore(c, fileName)
|
||||
|
||||
svcs := keyservices(c)
|
||||
|
||||
order, err := decryptionOrder(c.String("decryption-order"))
|
||||
if err != nil {
|
||||
return toExitError(err)
|
||||
}
|
||||
opts := decryptOpts{
|
||||
OutputStore: outputStore,
|
||||
InputStore: inputStore,
|
||||
InputPath: fileName,
|
||||
Cipher: aes.NewCipher(),
|
||||
KeyServices: svcs,
|
||||
IgnoreMAC: c.Bool("ignore-mac"),
|
||||
OutputStore: outputStore,
|
||||
InputStore: inputStore,
|
||||
InputPath: fileName,
|
||||
Cipher: aes.NewCipher(),
|
||||
KeyServices: svcs,
|
||||
DecryptionOrder: order,
|
||||
IgnoreMAC: c.Bool("ignore-mac"),
|
||||
}
|
||||
|
||||
output, err := decrypt(opts)
|
||||
|
@ -316,21 +329,25 @@ func main() {
|
|||
if info.IsDir() && !c.Bool("recursive") {
|
||||
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 {
|
||||
if err != nil {
|
||||
return toExitError(err)
|
||||
}
|
||||
if !info.IsDir() {
|
||||
err = publishcmd.Run(publishcmd.Opts{
|
||||
ConfigPath: configPath,
|
||||
InputPath: subPath,
|
||||
Cipher: aes.NewCipher(),
|
||||
KeyServices: keyservices(c),
|
||||
InputStore: inputStore(c, subPath),
|
||||
Interactive: !c.Bool("yes"),
|
||||
OmitExtensions: c.Bool("omit-extensions"),
|
||||
Recursive: c.Bool("recursive"),
|
||||
RootPath: path,
|
||||
ConfigPath: configPath,
|
||||
InputPath: subPath,
|
||||
Cipher: aes.NewCipher(),
|
||||
KeyServices: keyservices(c),
|
||||
DecryptionOrder: order,
|
||||
InputStore: inputStore(c, subPath),
|
||||
Interactive: !c.Bool("yes"),
|
||||
OmitExtensions: c.Bool("omit-extensions"),
|
||||
Recursive: c.Bool("recursive"),
|
||||
})
|
||||
if cliErr, ok := err.(*cli.ExitError); ok && cliErr != nil {
|
||||
return cliErr
|
||||
|
@ -773,6 +790,11 @@ func main() {
|
|||
Name: "filename-override",
|
||||
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...)
|
||||
|
||||
app.Action = func(c *cli.Context) error {
|
||||
|
@ -859,6 +881,10 @@ func main() {
|
|||
outputStore := outputStore(c, fileNameOverride)
|
||||
svcs := keyservices(c)
|
||||
|
||||
order, err := decryptionOrder(c.String("decryption-order"))
|
||||
if err != nil {
|
||||
return toExitError(err)
|
||||
}
|
||||
var output []byte
|
||||
if c.Bool("encrypt") {
|
||||
var groups []sops.KeyGroup
|
||||
|
@ -894,13 +920,14 @@ func main() {
|
|||
return common.NewExitError(fmt.Errorf("error parsing --extract path: %s", err), codes.InvalidTreePathFormat)
|
||||
}
|
||||
output, err = decrypt(decryptOpts{
|
||||
OutputStore: outputStore,
|
||||
InputStore: inputStore,
|
||||
InputPath: fileName,
|
||||
Cipher: aes.NewCipher(),
|
||||
Extract: extract,
|
||||
KeyServices: svcs,
|
||||
IgnoreMAC: c.Bool("ignore-mac"),
|
||||
OutputStore: outputStore,
|
||||
InputStore: inputStore,
|
||||
InputPath: fileName,
|
||||
Cipher: aes.NewCipher(),
|
||||
Extract: extract,
|
||||
KeyServices: svcs,
|
||||
DecryptionOrder: order,
|
||||
IgnoreMAC: c.Bool("ignore-mac"),
|
||||
})
|
||||
}
|
||||
if c.Bool("rotate") {
|
||||
|
@ -975,6 +1002,7 @@ func main() {
|
|||
InputPath: fileName,
|
||||
Cipher: aes.NewCipher(),
|
||||
KeyServices: svcs,
|
||||
DecryptionOrder: order,
|
||||
IgnoreMAC: c.Bool("ignore-mac"),
|
||||
AddMasterKeys: addMasterKeys,
|
||||
RemoveMasterKeys: rmMasterKeys,
|
||||
|
@ -994,14 +1022,15 @@ func main() {
|
|||
return toExitError(err)
|
||||
}
|
||||
output, err = set(setOpts{
|
||||
OutputStore: outputStore,
|
||||
InputStore: inputStore,
|
||||
InputPath: fileName,
|
||||
Cipher: aes.NewCipher(),
|
||||
KeyServices: svcs,
|
||||
IgnoreMAC: c.Bool("ignore-mac"),
|
||||
Value: value,
|
||||
TreePath: path,
|
||||
OutputStore: outputStore,
|
||||
InputStore: inputStore,
|
||||
InputPath: fileName,
|
||||
Cipher: aes.NewCipher(),
|
||||
KeyServices: svcs,
|
||||
DecryptionOrder: order,
|
||||
IgnoreMAC: c.Bool("ignore-mac"),
|
||||
Value: value,
|
||||
TreePath: path,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1010,13 +1039,14 @@ func main() {
|
|||
_, statErr := os.Stat(fileName)
|
||||
fileExists := statErr == nil
|
||||
opts := editOpts{
|
||||
OutputStore: outputStore,
|
||||
InputStore: inputStore,
|
||||
InputPath: fileName,
|
||||
Cipher: aes.NewCipher(),
|
||||
KeyServices: svcs,
|
||||
IgnoreMAC: c.Bool("ignore-mac"),
|
||||
ShowMasterKeys: c.Bool("show-master-keys"),
|
||||
OutputStore: outputStore,
|
||||
InputStore: inputStore,
|
||||
InputPath: fileName,
|
||||
Cipher: aes.NewCipher(),
|
||||
KeyServices: svcs,
|
||||
DecryptionOrder: order,
|
||||
IgnoreMAC: c.Bool("ignore-mac"),
|
||||
ShowMasterKeys: c.Bool("show-master-keys"),
|
||||
}
|
||||
if fileExists {
|
||||
output, err = edit(opts)
|
||||
|
@ -1351,3 +1381,18 @@ func extractSetArguments(set string) (path []interface{}, valueToInsert interfac
|
|||
}
|
||||
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
|
||||
RemoveMasterKeys []keys.MasterKey
|
||||
KeyServices []keyservice.KeyServiceClient
|
||||
DecryptionOrder []string
|
||||
}
|
||||
|
||||
func rotate(opts rotateOpts) ([]byte, error) {
|
||||
tree, err := common.LoadEncryptedFileWithBugFixes(common.GenericDecryptOpts{
|
||||
Cipher: opts.Cipher,
|
||||
InputStore: opts.InputStore,
|
||||
InputPath: opts.InputPath,
|
||||
IgnoreMAC: opts.IgnoreMAC,
|
||||
KeyServices: opts.KeyServices,
|
||||
Cipher: opts.Cipher,
|
||||
InputStore: opts.InputStore,
|
||||
InputPath: opts.InputPath,
|
||||
IgnoreMAC: opts.IgnoreMAC,
|
||||
KeyServices: opts.KeyServices,
|
||||
DecryptionOrder: opts.DecryptionOrder,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -39,8 +41,11 @@ func rotate(opts rotateOpts) ([]byte, error) {
|
|||
})
|
||||
|
||||
_, 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 {
|
||||
return nil, err
|
||||
|
|
|
@ -10,14 +10,15 @@ import (
|
|||
)
|
||||
|
||||
type setOpts struct {
|
||||
Cipher sops.Cipher
|
||||
InputStore sops.Store
|
||||
OutputStore sops.Store
|
||||
InputPath string
|
||||
IgnoreMAC bool
|
||||
TreePath []interface{}
|
||||
Value interface{}
|
||||
KeyServices []keyservice.KeyServiceClient
|
||||
Cipher sops.Cipher
|
||||
InputStore sops.Store
|
||||
OutputStore sops.Store
|
||||
InputPath string
|
||||
IgnoreMAC bool
|
||||
TreePath []interface{}
|
||||
Value interface{}
|
||||
KeyServices []keyservice.KeyServiceClient
|
||||
DecryptionOrder []string
|
||||
}
|
||||
|
||||
func set(opts setOpts) ([]byte, error) {
|
||||
|
@ -36,10 +37,11 @@ func set(opts setOpts) ([]byte, error) {
|
|||
|
||||
// Decrypt the file
|
||||
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 {
|
||||
return nil, err
|
||||
|
|
|
@ -10,13 +10,14 @@ import (
|
|||
|
||||
// AddOpts are the options for adding a key group to a SOPS file
|
||||
type AddOpts struct {
|
||||
InputPath string
|
||||
InputStore sops.Store
|
||||
OutputStore sops.Store
|
||||
Group sops.KeyGroup
|
||||
GroupThreshold int
|
||||
InPlace bool
|
||||
KeyServices []keyservice.KeyServiceClient
|
||||
InputPath string
|
||||
InputStore sops.Store
|
||||
OutputStore sops.Store
|
||||
Group sops.KeyGroup
|
||||
GroupThreshold int
|
||||
InPlace bool
|
||||
KeyServices []keyservice.KeyServiceClient
|
||||
DecryptionOrder []string
|
||||
}
|
||||
|
||||
// Add adds a key group to a SOPS file
|
||||
|
@ -25,7 +26,7 @@ func Add(opts AddOpts) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dataKey, err := tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices)
|
||||
dataKey, err := tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices, opts.DecryptionOrder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -12,13 +12,14 @@ import (
|
|||
|
||||
// DeleteOpts are the options for deleting a key group from a SOPS file
|
||||
type DeleteOpts struct {
|
||||
InputPath string
|
||||
InputStore sops.Store
|
||||
OutputStore sops.Store
|
||||
Group uint
|
||||
GroupThreshold int
|
||||
InPlace bool
|
||||
KeyServices []keyservice.KeyServiceClient
|
||||
InputPath string
|
||||
InputStore sops.Store
|
||||
OutputStore sops.Store
|
||||
Group uint
|
||||
GroupThreshold int
|
||||
InPlace bool
|
||||
KeyServices []keyservice.KeyServiceClient
|
||||
DecryptionOrder []string
|
||||
}
|
||||
|
||||
// Delete deletes a key group from a SOPS file
|
||||
|
@ -27,7 +28,7 @@ func Delete(opts DeleteOpts) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dataKey, err := tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices)
|
||||
dataKey, err := tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices, opts.DecryptionOrder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -27,15 +27,16 @@ func init() {
|
|||
|
||||
// Opts represents publish options and config
|
||||
type Opts struct {
|
||||
Interactive bool
|
||||
Cipher sops.Cipher
|
||||
ConfigPath string
|
||||
InputPath string
|
||||
KeyServices []keyservice.KeyServiceClient
|
||||
InputStore sops.Store
|
||||
OmitExtensions bool
|
||||
Recursive bool
|
||||
RootPath string
|
||||
Interactive bool
|
||||
Cipher sops.Cipher
|
||||
ConfigPath string
|
||||
InputPath string
|
||||
KeyServices []keyservice.KeyServiceClient
|
||||
DecryptionOrder []string
|
||||
InputStore sops.Store
|
||||
OmitExtensions bool
|
||||
Recursive bool
|
||||
RootPath string
|
||||
}
|
||||
|
||||
// Run publish operation
|
||||
|
@ -81,10 +82,11 @@ func Run(opts Opts) error {
|
|||
if len(conf.KeyGroups[0]) != 0 {
|
||||
log.Debug("Re-encrypting tree before publishing")
|
||||
_, err = common.DecryptTree(common.DecryptTreeOpts{
|
||||
Cipher: opts.Cipher,
|
||||
IgnoreMac: false,
|
||||
Tree: tree,
|
||||
KeyServices: opts.KeyServices,
|
||||
Cipher: opts.Cipher,
|
||||
IgnoreMac: false,
|
||||
Tree: tree,
|
||||
KeyServices: opts.KeyServices,
|
||||
DecryptionOrder: opts.DecryptionOrder,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -137,10 +139,11 @@ func Run(opts Opts) error {
|
|||
}
|
||||
case *publish.VaultDestination:
|
||||
_, err = common.DecryptTree(common.DecryptTreeOpts{
|
||||
Cipher: opts.Cipher,
|
||||
IgnoreMac: false,
|
||||
Tree: tree,
|
||||
KeyServices: opts.KeyServices,
|
||||
Cipher: opts.Cipher,
|
||||
IgnoreMac: false,
|
||||
Tree: tree,
|
||||
KeyServices: opts.KeyServices,
|
||||
DecryptionOrder: opts.DecryptionOrder,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -14,12 +14,13 @@ import (
|
|||
|
||||
// Opts represents key operation options and config
|
||||
type Opts struct {
|
||||
InputPath string
|
||||
GroupQuorum int
|
||||
KeyServices []keyservice.KeyServiceClient
|
||||
Interactive bool
|
||||
ConfigPath string
|
||||
InputType string
|
||||
InputPath string
|
||||
GroupQuorum int
|
||||
KeyServices []keyservice.KeyServiceClient
|
||||
DecryptionOrder []string
|
||||
Interactive bool
|
||||
ConfigPath string
|
||||
InputType string
|
||||
}
|
||||
|
||||
// UpdateKeys update the keys for a given file
|
||||
|
@ -83,7 +84,7 @@ func updateFile(opts Opts) error {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
key, err := tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices)
|
||||
key, err := tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices, opts.DecryptionOrder)
|
||||
if err != nil {
|
||||
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
|
||||
// format.
|
||||
SopsGoogleCredentialsEnv = "GOOGLE_CREDENTIALS"
|
||||
// KeyTypeIdentifier is the string used to identify a GCP KMS MasterKey.
|
||||
KeyTypeIdentifier = "gcp_kms"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -196,6 +198,11 @@ func (key MasterKey) ToMap() map[string]interface{} {
|
|||
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
|
||||
// and/or grpcConn, falling back to environmental defaults.
|
||||
// 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"
|
||||
)
|
||||
|
||||
const (
|
||||
// KeyTypeIdentifier is the string used to identify a Vault MasterKey.
|
||||
KeyTypeIdentifier = "hc_vault"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log = logging.NewLogger("VAULT_TRANSIT")
|
||||
}
|
||||
|
@ -216,6 +221,11 @@ func (key MasterKey) ToMap() map[string]interface{} {
|
|||
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.
|
||||
func (key *MasterKey) encryptPath() string {
|
||||
return path.Join(key.EnginePath, "encrypt", key.KeyName)
|
||||
|
|
|
@ -10,4 +10,5 @@ type MasterKey interface {
|
|||
NeedsRotation() bool
|
||||
ToString() string
|
||||
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
|
||||
for Go V2.
|
||||
*/
|
||||
package kms //import "github.com/getsops/sops/v3/kms"
|
||||
package kms // import "github.com/getsops/sops/v3/kms"
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -19,8 +19,9 @@ import (
|
|||
"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/sts"
|
||||
"github.com/getsops/sops/v3/logging"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/getsops/sops/v3/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -34,6 +35,8 @@ const (
|
|||
roleSessionNameLengthLimit = 64
|
||||
// kmsTTL is the duration after which a MasterKey requires rotation.
|
||||
kmsTTL = time.Hour * 24 * 30 * 6
|
||||
// KeyTypeIdentifier is the string used to identify an AWS KMS MasterKey.
|
||||
KeyTypeIdentifier = "kms"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -297,6 +300,11 @@ func (key MasterKey) ToMap() map[string]interface{} {
|
|||
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
|
||||
// MasterKey, or the default configuration sources.
|
||||
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
|
||||
the "gpg" binary.
|
||||
*/
|
||||
package pgp //import "github.com/getsops/sops/v3/pgp"
|
||||
package pgp // import "github.com/getsops/sops/v3/pgp"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -22,12 +22,15 @@ import (
|
|||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||
gpgagent "github.com/getsops/gopgagent"
|
||||
"github.com/getsops/sops/v3/logging"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/term"
|
||||
|
||||
"github.com/getsops/sops/v3/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
// KeyTypeIdentifier is the string used to identify a PGP MasterKey.
|
||||
KeyTypeIdentifier = "pgp"
|
||||
// SopsGpgExecEnv can be set as an environment variable to overwrite the
|
||||
// GnuPG binary used.
|
||||
SopsGpgExecEnv = "SOPS_GPG_EXEC"
|
||||
|
@ -449,6 +452,11 @@ func (key MasterKey) ToMap() map[string]interface{} {
|
|||
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
|
||||
// by Fingerprint.
|
||||
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
|
||||
ordering.
|
||||
*/
|
||||
package sops //import "github.com/getsops/sops/v3"
|
||||
package sops // import "github.com/getsops/sops/v3"
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
|
@ -42,22 +42,28 @@ import (
|
|||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"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/keys"
|
||||
"github.com/getsops/sops/v3/keyservice"
|
||||
"github.com/getsops/sops/v3/logging"
|
||||
"github.com/getsops/sops/v3/pgp"
|
||||
"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
|
||||
const DefaultUnencryptedSuffix = "_unencrypted"
|
||||
|
||||
var DefaultDecryptionOrder = []string{age.KeyTypeIdentifier, pgp.KeyTypeIdentifier}
|
||||
|
||||
type sopsError 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
|
||||
// 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 {
|
||||
return m.DataKey, nil
|
||||
}
|
||||
|
@ -658,7 +664,7 @@ func (m Metadata) GetDataKeyWithKeyServices(svcs []keyservice.KeyServiceClient)
|
|||
}
|
||||
var parts [][]byte
|
||||
for i, group := range m.KeyGroups {
|
||||
part, err := decryptKeyGroup(group, svcs)
|
||||
part, err := decryptKeyGroup(group, svcs, decryptionOrder)
|
||||
if err == nil {
|
||||
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
|
||||
// any of the MasterKeys in the KeyGroup with any of the provided key services,
|
||||
// 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
|
||||
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)
|
||||
if err != nil {
|
||||
keyErrs = append(keyErrs, err)
|
||||
|
@ -701,6 +711,37 @@ func decryptKeyGroup(group KeyGroup, svcs []keyservice.KeyServiceClient) ([]byte
|
|||
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
|
||||
// of the key services, returning as soon as one key service succeeds.
|
||||
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) {
|
||||
return m.GetDataKeyWithKeyServices([]keyservice.KeyServiceClient{
|
||||
keyservice.NewLocalClient(),
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
// 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"
|
||||
|
||||
"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{}
|
||||
|
@ -842,3 +846,61 @@ func TestEmitAsMap(t *testing.T) {
|
|||
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)
|
||||
})
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче