diff --git a/README.rst b/README.rst index 36a6883e9..15e492ea0 100644 --- a/README.rst +++ b/README.rst @@ -273,11 +273,11 @@ Adding and removing keys ~~~~~~~~~~~~~~~~~~~~~~~~ When creating new files, ``sops`` uses the PGP, KMS and GCP KMS defined in the -command line arguments ``--kms``, ``--pgp``, ``--gcp-kms`` or ``--azure-kv``, or from -the environment variables ``SOPS_KMS_ARN``, ``SOPS_PGP_FP``, ``SOPS_GCP_KMS_IDS``, -``SOPS_AZURE_KEYVAULT_URL``. That information is stored in the file under the -``sops`` section, such that decrypting files does not require providing those -parameters again. +command line arguments ``--kms``, ``--aws-profile``, ``--pgp``, ``--gcp-kms`` or +``--azure-kv``, or from the environment variables ``SOPS_KMS_ARN``, +``SOPS_PGP_FP``, ``SOPS_GCP_KMS_IDS``, ``SOPS_AZURE_KEYVAULT_URL``. That +information is stored in the file under the ``sops`` section, such that decrypting +files does not require providing those parameters again. Master PGP and KMS keys can be added and removed from a ``sops`` file in one of two ways: by using command line flag, or by editing the file directly. @@ -285,8 +285,8 @@ two ways: by using command line flag, or by editing the file directly. Command line flag ``--add-kms``, ``--add-pgp``, ``--add-gcp-kms``, ``--add-azure-kv``, ``--rm-kms``, ``--rm-pgp``, ``--rm-gcp-kms`` and ``--rm-azure-kv`` can be used to add and remove keys from a file. -These flags use the comma separated syntax as the ``--kms``, ``--pgp``, ``--gcp-kms`` -and ``--azure-kv`` arguments when creating new files. +These flags use the comma separated syntax as the ``--kms``, ``aws-profile``, ``--pgp``, +``--gcp-kms`` and ``--azure-kv`` arguments when creating new files. .. code:: bash @@ -309,6 +309,9 @@ editing: sops: kms: - arn: arn:aws:kms:us-east-1:656532927350:key/920aff2e-c5f1-4040-943a-047fa387b27e + aws_profile: foo + +If no aws_profile is specified `default` is used for KMS. And, similarly, to add a PGP master key, we add its fingerprint: @@ -462,11 +465,13 @@ can manage the three sets of configurations for the three types of files: # KMS set A is used - path_regex: \.dev\.yaml$ kms: 'arn:aws:kms:us-west-2:927034868273:key/fe86dd69-4132-404c-ab86-4269956b4500,arn:aws:kms:us-west-2:361527076523:key/5052f06a-5d3f-489e-b86c-57201e06f31e+arn:aws:iam::361527076523:role/hiera-sops-prod' + aws_profile: foo pgp: '1022470DE3F0BC54BC6AB62DE05550BC07FB1A0A' # prod files use KMS set B in the PROD IAM - path_regex: \.prod\.yaml$ kms: 'arn:aws:kms:us-west-2:361527076523:key/5052f06a-5d3f-489e-b86c-57201e06f31e+arn:aws:iam::361527076523:role/hiera-sops-prod,arn:aws:kms:eu-central-1:361527076523:key/cb1fab90-8d17-42a1-a9d8-334968904f94+arn:aws:iam::361527076523:role/hiera-sops-prod' + aws_profile: bar pgp: '1022470DE3F0BC54BC6AB62DE05550BC07FB1A0A' # gcp files using GCP KMS @@ -485,6 +490,8 @@ found, the filename of the file being created is compared with the filename regexes of the configuration file. The first regex that matches is selected, and its KMS and PGP keys are used to encrypt the file. +If no aws_profile is specified `default` is used for KMS. + Creating a new file with the right keys is now as simple as .. code:: bash @@ -537,7 +544,9 @@ file ``my_file.yaml``: .. code:: bash - $ sops groups add --file my_file.yaml --pgp fingerprint1 --pgp fingerprint2 --pgp fingerprint3 --kms arn1 --kms arn2 --kms arn3 + $ sops groups add --file my_file.yaml --pgp fingerprint1 --pgp fingerprint2 --pgp fingerprint3 --kms arn1 --kms arn2 --kms arn3 --aws-profile bar + +If no aws-profile is specified `default` is used for KMS. Or you can delete the 1st group (group number 0, as groups are zero-indexed) from ``my_file.yaml``: @@ -561,6 +570,7 @@ like so: kms: - arn: arn1 role: role1 + aws_profile: foo context: foo: bar - arn: arn2 @@ -603,6 +613,7 @@ with ``shamir_threshold``: kms: - arn: arn1 role: role1 + aws_profile: foo context: foo: bar - arn: arn2 @@ -870,6 +881,7 @@ encrypt the file, and redirect the output to a destination file. $ export SOPS_KMS_ARN="arn:aws:kms:us-west-2:927034868273:key/fe86dd69-4132-404c-ab86-4269956b4500" $ export SOPS_PGP_FP="C9CAB0AF1165060DB58D6D6B2653B624D620786D" $ sops -e /path/to/existing/file.yaml > /path/to/new/encrypted/file.yaml + $ sops --aws-profile foo -e /path/to/existing/file.yaml > /path/to/new/encrypted/file.yaml Decrypt the file with ``-d``. diff --git a/cmd/sops/main.go b/cmd/sops/main.go index f7bc2665b..796a8f37c 100644 --- a/cmd/sops/main.go +++ b/cmd/sops/main.go @@ -160,6 +160,10 @@ func main() { Name: "kms", Usage: "the KMS ARNs the new group should contain. Can be specified more than once", }, + cli.StringFlag{ + Name: "aws-profile", + Usage: "The single aws profile to use with KMS", + }, cli.StringSliceFlag{ Name: "gcp-kms", Usage: "the GCP KMS Resource ID the new group should contain. Can be specified more than once", @@ -184,6 +188,7 @@ func main() { Action: func(c *cli.Context) error { pgpFps := c.StringSlice("pgp") kmsArns := c.StringSlice("kms") + awsProfile := c.String("aws-profile") gcpKmses := c.StringSlice("gcp-kms") azkvs := c.StringSlice("azure-kv") var group sops.KeyGroup @@ -191,7 +196,7 @@ func main() { group = append(group, pgp.NewMasterKeyFromFingerprint(fp)) } for _, arn := range kmsArns { - group = append(group, kms.NewMasterKeyFromArn(arn, kms.ParseKMSContext(c.String("encryption-context")))) + group = append(group, kms.NewMasterKeyFromArn(arn, kms.ParseKMSContext(c.String("encryption-context")), awsProfile)) } for _, kms := range gcpKmses { group = append(group, gcpkms.NewMasterKeyFromResourceID(kms)) @@ -304,6 +309,10 @@ func main() { Usage: "comma separated list of KMS ARNs", EnvVar: "SOPS_KMS_ARN", }, + cli.StringFlag{ + Name: "aws-profile", + Usage: "The single aws profile to use with KMS", + }, cli.StringFlag{ Name: "gcp-kms", Usage: "comma separated list of GCP KMS resource IDs", @@ -501,7 +510,7 @@ func main() { if c.Bool("rotate") { var addMasterKeys []keys.MasterKey kmsEncryptionContext := kms.ParseKMSContext(c.String("encryption-context")) - for _, k := range kms.MasterKeysFromArnString(c.String("add-kms"), kmsEncryptionContext) { + for _, k := range kms.MasterKeysFromArnString(c.String("add-kms"), kmsEncryptionContext, c.String("aws-profile")) { addMasterKeys = append(addMasterKeys, k) } for _, k := range pgp.MasterKeysFromFingerprintString(c.String("add-pgp")) { @@ -519,7 +528,7 @@ func main() { } var rmMasterKeys []keys.MasterKey - for _, k := range kms.MasterKeysFromArnString(c.String("rm-kms"), kmsEncryptionContext) { + for _, k := range kms.MasterKeysFromArnString(c.String("rm-kms"), kmsEncryptionContext, c.String("aws-profile")) { rmMasterKeys = append(rmMasterKeys, k) } for _, k := range pgp.MasterKeysFromFingerprintString(c.String("rm-pgp")) { @@ -749,7 +758,10 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) { return nil, common.NewExitError("Invalid KMS encryption context format", codes.ErrorInvalidKMSEncryptionContextFormat) } if c.String("kms") != "" { - for _, k := range kms.MasterKeysFromArnString(c.String("kms"), kmsEncryptionContext) { + + awsprofile := c.String("aws-profile") + + for _, k := range kms.MasterKeysFromArnString(c.String("kms"), kmsEncryptionContext, awsprofile) { kmsKeys = append(kmsKeys, k) } } diff --git a/config/config.go b/config/config.go index a729f1823..07628ef0f 100644 --- a/config/config.go +++ b/config/config.go @@ -75,9 +75,10 @@ type gcpKmsKey struct { } type kmsKey struct { - Arn string `yaml:"arn"` - Role string `yaml:"role,omitempty"` - Context map[string]*string `yaml:"context"` + Arn string `yaml:"arn"` + Role string `yaml:"role,omitempty"` + Context map[string]*string `yaml:"context"` + AwsProfile string `yaml:"aws_profile"` } type azureKVKey struct { @@ -90,6 +91,7 @@ type creationRule struct { FilenameRegex string `yaml:"filename_regex"` PathRegex string `yaml:"path_regex"` KMS string + AwsProfile string `yaml:"aws_profile"` PGP string GCPKMS string `yaml:"gcp_kms"` AzureKeyVault string `yaml:"azure_keyvault"` @@ -175,7 +177,7 @@ func loadForFileFromBytes(confBytes []byte, filePath string, kmsEncryptionContex for _, k := range pgp.MasterKeysFromFingerprintString(rule.PGP) { keyGroup = append(keyGroup, k) } - for _, k := range kms.MasterKeysFromArnString(rule.KMS, kmsEncryptionContext) { + for _, k := range kms.MasterKeysFromArnString(rule.KMS, kmsEncryptionContext, rule.AwsProfile) { keyGroup = append(keyGroup, k) } for _, k := range gcpkms.MasterKeysFromResourceIDString(rule.GCPKMS) { diff --git a/keyservice/keyservice.go b/keyservice/keyservice.go index 7d426b65a..0f9e27471 100644 --- a/keyservice/keyservice.go +++ b/keyservice/keyservice.go @@ -41,9 +41,10 @@ func KeyFromMasterKey(mk keys.MasterKey) Key { return Key{ KeyType: &Key_KmsKey{ KmsKey: &KmsKey{ - Arn: mk.Arn, - Role: mk.Role, - Context: ctx, + Arn: mk.Arn, + Role: mk.Role, + Context: ctx, + AwsProfile: mk.AwsProfile, }, }, } diff --git a/keyservice/keyservice.pb.go b/keyservice/keyservice.pb.go index b13f7552c..d39c654ab 100644 --- a/keyservice/keyservice.pb.go +++ b/keyservice/keyservice.pb.go @@ -240,9 +240,10 @@ func (m *PgpKey) GetFingerprint() string { } type KmsKey struct { - Arn string `protobuf:"bytes,1,opt,name=arn" json:"arn,omitempty"` - Role string `protobuf:"bytes,2,opt,name=role" json:"role,omitempty"` - Context map[string]string `protobuf:"bytes,3,rep,name=context" json:"context,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Arn string `protobuf:"bytes,1,opt,name=arn" json:"arn,omitempty"` + Role string `protobuf:"bytes,2,opt,name=role" json:"role,omitempty"` + Context map[string]string `protobuf:"bytes,3,rep,name=context" json:"context,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + AwsProfile string `protobuf:"bytes,4,opt,name=aws_profile" json:"aws_profile,omitempty"` } func (m *KmsKey) Reset() { *m = KmsKey{} } diff --git a/keyservice/server.go b/keyservice/server.go index e4e42b111..35849c6e9 100644 --- a/keyservice/server.go +++ b/keyservice/server.go @@ -37,6 +37,7 @@ func (ks *Server) encryptWithKms(key *KmsKey, plaintext []byte) ([]byte, error) Arn: key.Arn, Role: key.Role, EncryptionContext: ctx, + AwsProfile: key.AwsProfile, } err := kmsKey.Encrypt(plaintext) if err != nil { @@ -85,6 +86,7 @@ func (ks *Server) decryptWithKms(key *KmsKey, ciphertext []byte) ([]byte, error) Arn: key.Arn, Role: key.Role, EncryptionContext: ctx, + AwsProfile: key.AwsProfile, } kmsKey.EncryptedKey = string(ciphertext) plaintext, err := kmsKey.Decrypt() diff --git a/kms/keysource.go b/kms/keysource.go index b61445d52..d38810a7c 100644 --- a/kms/keysource.go +++ b/kms/keysource.go @@ -42,6 +42,7 @@ type MasterKey struct { EncryptedKey string CreationDate time.Time EncryptionContext map[string]*string + AwsProfile string } // EncryptedDataKey returns the encrypted data key this master key holds @@ -131,7 +132,7 @@ func NewMasterKey(arn string, role string, context map[string]*string) *MasterKe } // NewMasterKeyFromArn takes an ARN string and returns a new MasterKey for that ARN -func NewMasterKeyFromArn(arn string, context map[string]*string) *MasterKey { +func NewMasterKeyFromArn(arn string, context map[string]*string, awsprofile string) *MasterKey { k := &MasterKey{} arn = strings.Replace(arn, " ", "", -1) roleIndex := strings.Index(arn, "+arn:aws:iam::") @@ -143,17 +144,18 @@ func NewMasterKeyFromArn(arn string, context map[string]*string) *MasterKey { } k.EncryptionContext = context k.CreationDate = time.Now().UTC() + k.AwsProfile = awsprofile return k } // MasterKeysFromArnString takes a comma separated list of AWS KMS ARNs and returns a slice of new MasterKeys for those ARNs -func MasterKeysFromArnString(arn string, context map[string]*string) []*MasterKey { +func MasterKeysFromArnString(arn string, context map[string]*string, awsprofile string) []*MasterKey { var keys []*MasterKey if arn == "" { return keys } for _, s := range strings.Split(arn, ",") { - keys = append(keys, NewMasterKeyFromArn(s, context)) + keys = append(keys, NewMasterKeyFromArn(s, context, awsprofile)) } return keys } @@ -185,7 +187,7 @@ func (key MasterKey) createSession() (*session.Session, error) { if matches == nil { return nil, fmt.Errorf("No valid ARN found in %q", key.Arn) } - config := aws.Config{Region: aws.String(matches[1])} + config := aws.Config{Region: aws.String(matches[1]), Credentials: credentials.NewSharedCredentials("", key.AwsProfile)} opts := session.Options{ Config: config, AssumeRoleTokenProvider: stscreds.StdinTokenProvider, diff --git a/kms/keysource_test.go b/kms/keysource_test.go index bdb1aafd0..785389cbc 100644 --- a/kms/keysource_test.go +++ b/kms/keysource_test.go @@ -48,7 +48,7 @@ func TestKMS(t *testing.T) { func TestKMSKeySourceFromString(t *testing.T) { s := "arn:aws:kms:us-east-1:656532927350:key/920aff2e-c5f1-4040-943a-047fa387b27e+arn:aws:iam::927034868273:role/sops-dev, arn:aws:kms:ap-southeast-1:656532927350:key/9006a8aa-0fa6-4c14-930e-a2dfb916de1d" - ks := MasterKeysFromArnString(s, nil) + ks := MasterKeysFromArnString(s, nil, "foo") k1 := ks[0] k2 := ks[1] expectedArn1 := "arn:aws:kms:us-east-1:656532927350:key/920aff2e-c5f1-4040-943a-047fa387b27e" diff --git a/stores/stores.go b/stores/stores.go index 19d860a4b..d515c9ac6 100644 --- a/stores/stores.go +++ b/stores/stores.go @@ -67,6 +67,7 @@ type kmskey struct { Context map[string]*string `yaml:"context,omitempty" json:"context,omitempty"` CreatedAt string `yaml:"created_at" json:"created_at"` EncryptedDataKey string `yaml:"enc" json:"enc"` + AwsProfile string `yaml:"aws_profile" json:"aws_profile"` } type gcpkmskey struct { @@ -135,6 +136,7 @@ func kmsKeysFromGroup(group sops.KeyGroup) (keys []kmskey) { EncryptedDataKey: key.EncryptedKey, Context: key.EncryptionContext, Role: key.Role, + AwsProfile: key.AwsProfile, }) } } @@ -265,6 +267,7 @@ func (kmsKey *kmskey) toInternal() (*kms.MasterKey, error) { EncryptedKey: kmsKey.EncryptedDataKey, CreationDate: creationDate, Arn: kmsKey.Arn, + AwsProfile: kmsKey.AwsProfile, }, nil }