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:
Boris Kreitchman 2023-11-07 19:28:34 +02:00 коммит произвёл Hidde Beydals
Родитель 30281796df
Коммит c822b55290
21 изменённых файлов: 374 добавлений и 145 удалений

Просмотреть файл

@ -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)
} }
@ -227,6 +229,7 @@ type GenericDecryptOpts struct {
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

Просмотреть файл

@ -22,6 +22,7 @@ type decryptOpts struct {
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) {
@ -41,6 +42,7 @@ func decrypt(opts decryptOpts) (decryptedFile []byte, err error) {
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

Просмотреть файл

@ -26,6 +26,7 @@ type editOpts struct {
InputPath string InputPath string
IgnoreMAC bool IgnoreMAC bool
KeyServices []keyservice.KeyServiceClient KeyServices []keyservice.KeyServiceClient
DecryptionOrder []string
ShowMasterKeys bool ShowMasterKeys bool
} }
@ -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

Просмотреть файл

@ -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,12 +173,18 @@ 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,
DecryptionOrder: order,
IgnoreMAC: c.Bool("ignore-mac"), IgnoreMAC: c.Bool("ignore-mac"),
} }
@ -241,12 +248,18 @@ 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,
DecryptionOrder: order,
IgnoreMAC: c.Bool("ignore-mac"), IgnoreMAC: c.Bool("ignore-mac"),
} }
@ -316,6 +329,10 @@ 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)
@ -326,11 +343,11 @@ func main() {
InputPath: subPath, InputPath: subPath,
Cipher: aes.NewCipher(), Cipher: aes.NewCipher(),
KeyServices: keyservices(c), KeyServices: keyservices(c),
DecryptionOrder: order,
InputStore: inputStore(c, subPath), InputStore: inputStore(c, subPath),
Interactive: !c.Bool("yes"), Interactive: !c.Bool("yes"),
OmitExtensions: c.Bool("omit-extensions"), OmitExtensions: c.Bool("omit-extensions"),
Recursive: c.Bool("recursive"), Recursive: c.Bool("recursive"),
RootPath: path,
}) })
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
@ -900,6 +926,7 @@ func main() {
Cipher: aes.NewCipher(), Cipher: aes.NewCipher(),
Extract: extract, Extract: extract,
KeyServices: svcs, KeyServices: svcs,
DecryptionOrder: order,
IgnoreMAC: c.Bool("ignore-mac"), IgnoreMAC: c.Bool("ignore-mac"),
}) })
} }
@ -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,
@ -999,6 +1027,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"),
Value: value, Value: value,
TreePath: path, TreePath: path,
@ -1015,6 +1044,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"),
ShowMasterKeys: c.Bool("show-master-keys"), ShowMasterKeys: c.Bool("show-master-keys"),
} }
@ -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,6 +20,7 @@ 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) {
@ -29,6 +30,7 @@ func rotate(opts rotateOpts) ([]byte, error) {
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,
IgnoreMac: opts.IgnoreMAC,
Tree: tree,
KeyServices: opts.KeyServices, KeyServices: opts.KeyServices,
DecryptionOrder: opts.DecryptionOrder,
}) })
if err != nil { if err != nil {
return nil, err return nil, err

Просмотреть файл

@ -18,6 +18,7 @@ type setOpts struct {
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) {
@ -40,6 +41,7 @@ func set(opts setOpts) ([]byte, error) {
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

Просмотреть файл

@ -17,6 +17,7 @@ type AddOpts struct {
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
} }

Просмотреть файл

@ -19,6 +19,7 @@ type DeleteOpts struct {
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
} }

Просмотреть файл

@ -32,6 +32,7 @@ type Opts struct {
ConfigPath string ConfigPath string
InputPath string InputPath string
KeyServices []keyservice.KeyServiceClient KeyServices []keyservice.KeyServiceClient
DecryptionOrder []string
InputStore sops.Store InputStore sops.Store
OmitExtensions bool OmitExtensions bool
Recursive bool Recursive bool
@ -85,6 +86,7 @@ func Run(opts Opts) error {
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
@ -141,6 +143,7 @@ func Run(opts Opts) error {
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

Просмотреть файл

@ -17,6 +17,7 @@ type Opts struct {
InputPath string InputPath string
GroupQuorum int GroupQuorum int
KeyServices []keyservice.KeyServiceClient KeyServices []keyservice.KeyServiceClient
DecryptionOrder []string
Interactive bool Interactive bool
ConfigPath string ConfigPath string
InputType string InputType string
@ -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
} }

Просмотреть файл

@ -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) {

Просмотреть файл

@ -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) {

55
sops.go
Просмотреть файл

@ -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.

Просмотреть файл

@ -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)
})
}