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

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

@ -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
Просмотреть файл

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

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

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