Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: feat(gcp-kms): impersonate service account #1150

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ Cargo.lock
vendor/
coverage.txt
profile.out
functional-tests/sops
12 changes: 12 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,18 @@ you can enable application default credentials using the sdk::

$ gcloud auth application-default login

If you want to `impersonate a service account <https://cloud.google.com/iam/docs/impersonating-service-accounts>`_,
you can do so with `gcp_impersonate_service_account`:

.. code:: yaml

sops:
kms:
- gcp_kms: projects/my-project/locations/global/keyRings/sops/cryptoKeys/sops-key
gcp_impersonate_service_account: [email protected]

Similarly the `--gcp-impersonate-service-account` flag can be set with the command line with any of the GCP KMS commands.

Encrypting/decrypting with GCP KMS requires a KMS ResourceID. You can use the
cloud console the get the ResourceID or you can create one using the gcloud
sdk:
Expand Down
22 changes: 16 additions & 6 deletions cmd/sops/main.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main //import "go.mozilla.org/sops/v3/cmd/sops"
package main // import "go.mozilla.org/sops/v3/cmd/sops"

import (
encodingjson "encoding/json"
Expand All @@ -15,6 +15,8 @@ import (

"github.com/sirupsen/logrus"
"github.com/urfave/cli"
"google.golang.org/grpc"

"go.mozilla.org/sops/v3"
"go.mozilla.org/sops/v3/aes"
"go.mozilla.org/sops/v3/age"
Expand All @@ -38,7 +40,6 @@ import (
"go.mozilla.org/sops/v3/stores/dotenv"
"go.mozilla.org/sops/v3/stores/json"
"go.mozilla.org/sops/v3/version"
"google.golang.org/grpc"
)

var log *logrus.Logger
Expand Down Expand Up @@ -383,6 +384,10 @@ func main() {
Name: "gcp-kms",
Usage: "the GCP KMS Resource ID the new group should contain. Can be specified more than once",
},
cli.StringSliceFlag{
Name: "gcp-impersonate-service-account",
Usage: "the GCP service account to use for requests to GCP",
},
cli.StringSliceFlag{
Name: "azure-kv",
Usage: "the Azure Key Vault key URL the new group should contain. Can be specified more than once",
Expand Down Expand Up @@ -423,7 +428,7 @@ func main() {
group = append(group, kms.NewMasterKeyFromArn(arn, kms.ParseKMSContext(c.String("encryption-context")), c.String("aws-profile")))
}
for _, kms := range gcpKmses {
group = append(group, gcpkms.NewMasterKeyFromResourceID(kms))
group = append(group, gcpkms.NewMasterKeyFromResourceID(kms, c.String("gcp-impersonate-service-account")))
}
for _, uri := range vaultURIs {
k, err := hcvault.NewMasterKeyFromURI(uri)
Expand Down Expand Up @@ -575,6 +580,11 @@ func main() {
Usage: "comma separated list of GCP KMS resource IDs",
EnvVar: "SOPS_GCP_KMS_IDS",
},
cli.StringSliceFlag{
Name: "gcp-impersonate-service-account",
Usage: "the GCP service account to use for requests to GCP",
EnvVar: "SOPS_GCP_IMPERSONATE_SERVICE_ACCOUNT",
},
cli.StringFlag{
Name: "azure-kv",
Usage: "comma separated list of Azure Key Vault URLs",
Expand Down Expand Up @@ -836,7 +846,7 @@ func main() {
for _, k := range pgp.MasterKeysFromFingerprintString(c.String("add-pgp")) {
addMasterKeys = append(addMasterKeys, k)
}
for _, k := range gcpkms.MasterKeysFromResourceIDString(c.String("add-gcp-kms")) {
for _, k := range gcpkms.MasterKeysFromResourceIDString(c.String("add-gcp-kms"), c.String("gcp-impersonate-service-account")) {
addMasterKeys = append(addMasterKeys, k)
}
azureKeys, err := azkv.MasterKeysFromURLs(c.String("add-azure-kv"))
Expand Down Expand Up @@ -868,7 +878,7 @@ func main() {
for _, k := range pgp.MasterKeysFromFingerprintString(c.String("rm-pgp")) {
rmMasterKeys = append(rmMasterKeys, k)
}
for _, k := range gcpkms.MasterKeysFromResourceIDString(c.String("rm-gcp-kms")) {
for _, k := range gcpkms.MasterKeysFromResourceIDString(c.String("rm-gcp-kms"), c.String("gcp-impersonate-service-account")) {
rmMasterKeys = append(rmMasterKeys, k)
}
azureKeys, err = azkv.MasterKeysFromURLs(c.String("rm-azure-kv"))
Expand Down Expand Up @@ -1099,7 +1109,7 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) {
}
}
if c.String("gcp-kms") != "" {
for _, k := range gcpkms.MasterKeysFromResourceIDString(c.String("gcp-kms")) {
for _, k := range gcpkms.MasterKeysFromResourceIDString(c.String("gcp-kms"), c.String("gcp-impersonate-service-account")) {
cloudKmsKeys = append(cloudKmsKeys, k)
}
}
Expand Down
43 changes: 23 additions & 20 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
Package config provides a way to find and load SOPS configuration files
*/
package config //import "go.mozilla.org/sops/v3/config"
package config // import "go.mozilla.org/sops/v3/config"

import (
"fmt"
Expand All @@ -13,6 +13,8 @@ import (
"strings"

"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"

"go.mozilla.org/sops/v3"
"go.mozilla.org/sops/v3/age"
"go.mozilla.org/sops/v3/azkv"
Expand All @@ -22,7 +24,6 @@ import (
"go.mozilla.org/sops/v3/logging"
"go.mozilla.org/sops/v3/pgp"
"go.mozilla.org/sops/v3/publish"
"gopkg.in/yaml.v3"
)

var log *logrus.Logger
Expand Down Expand Up @@ -79,7 +80,8 @@ type keyGroup struct {
}

type gcpKmsKey struct {
ResourceID string `yaml:"resource_id"`
ResourceID string `yaml:"resource_id"`
ImpersonateServiceAccount string `yaml:"gcp_impersonate_service_account"`
}

type kmsKey struct {
Expand Down Expand Up @@ -110,20 +112,21 @@ type destinationRule struct {
}

type creationRule struct {
PathRegex string `yaml:"path_regex"`
KMS string
AwsProfile string `yaml:"aws_profile"`
Age string `yaml:"age"`
PGP string
GCPKMS string `yaml:"gcp_kms"`
AzureKeyVault string `yaml:"azure_keyvault"`
VaultURI string `yaml:"hc_vault_transit_uri"`
KeyGroups []keyGroup `yaml:"key_groups"`
ShamirThreshold int `yaml:"shamir_threshold"`
UnencryptedSuffix string `yaml:"unencrypted_suffix"`
EncryptedSuffix string `yaml:"encrypted_suffix"`
UnencryptedRegex string `yaml:"unencrypted_regex"`
EncryptedRegex string `yaml:"encrypted_regex"`
PathRegex string `yaml:"path_regex"`
KMS string
AwsProfile string `yaml:"aws_profile"`
Age string `yaml:"age"`
PGP string
GCPKMS string `yaml:"gcp_kms"`
GCPImpersonateServiceAccount string `yaml:"gcp_impersonate_service_account"`
AzureKeyVault string `yaml:"azure_keyvault"`
VaultURI string `yaml:"hc_vault_transit_uri"`
KeyGroups []keyGroup `yaml:"key_groups"`
ShamirThreshold int `yaml:"shamir_threshold"`
UnencryptedSuffix string `yaml:"unencrypted_suffix"`
EncryptedSuffix string `yaml:"encrypted_suffix"`
UnencryptedRegex string `yaml:"unencrypted_regex"`
EncryptedRegex string `yaml:"encrypted_regex"`
}

// Load loads a sops config file into a temporary struct
Expand Down Expand Up @@ -168,7 +171,7 @@ func getKeyGroupsFromCreationRule(cRule *creationRule, kmsEncryptionContext map[
keyGroup = append(keyGroup, kms.NewMasterKey(k.Arn, k.Role, k.Context))
}
for _, k := range group.GCPKMS {
keyGroup = append(keyGroup, gcpkms.NewMasterKeyFromResourceID(k.ResourceID))
keyGroup = append(keyGroup, gcpkms.NewMasterKeyFromResourceID(k.ResourceID, k.ImpersonateServiceAccount))
}
for _, k := range group.AzureKV {
keyGroup = append(keyGroup, azkv.NewMasterKey(k.VaultURL, k.Key, k.Version))
Expand Down Expand Up @@ -200,7 +203,7 @@ func getKeyGroupsFromCreationRule(cRule *creationRule, kmsEncryptionContext map[
for _, k := range kms.MasterKeysFromArnString(cRule.KMS, kmsEncryptionContext, cRule.AwsProfile) {
keyGroup = append(keyGroup, k)
}
for _, k := range gcpkms.MasterKeysFromResourceIDString(cRule.GCPKMS) {
for _, k := range gcpkms.MasterKeysFromResourceIDString(cRule.GCPKMS, cRule.GCPImpersonateServiceAccount) {
keyGroup = append(keyGroup, k)
}
azureKeys, err := azkv.MasterKeysFromURLs(cRule.AzureKeyVault)
Expand Down Expand Up @@ -329,7 +332,7 @@ func parseCreationRuleForFile(conf *configFile, confPath, filePath string, kmsEn
}

// compare file path relative to path of config file
filePath = strings.TrimPrefix(filePath, configDir + string(filepath.Separator))
filePath = strings.TrimPrefix(filePath, configDir+string(filepath.Separator))

var rule *creationRule

Expand Down
28 changes: 23 additions & 5 deletions gcpkms/keysource.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gcpkms // import "go.mozilla.org/sops/v3/gcpkms"

import (
"bytes"
"context"
"encoding/base64"
"fmt"
Expand All @@ -11,6 +12,7 @@ import (

kms "cloud.google.com/go/kms/apiv1"
"github.com/sirupsen/logrus"
"google.golang.org/api/impersonate"
"google.golang.org/api/option"
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
"google.golang.org/grpc"
Expand Down Expand Up @@ -55,27 +57,32 @@ type MasterKey struct {
// Mostly useful for testing at present, to wire the client to a mock
// server.
grpcConn *grpc.ClientConn

// ImpersonateServiceAccount is the name of the service account
// to impersonate with GCP KMS client
ImpersonateServiceAccount string
}

// NewMasterKeyFromResourceID creates a new MasterKey with the provided resource
// ID.
func NewMasterKeyFromResourceID(resourceID string) *MasterKey {
func NewMasterKeyFromResourceID(resourceID, impersonateServiceAccount string) *MasterKey {
k := &MasterKey{}
resourceID = strings.Replace(resourceID, " ", "", -1)
k.ResourceID = resourceID
k.CreationDate = time.Now().UTC()
k.ImpersonateServiceAccount = impersonateServiceAccount
return k
}

// MasterKeysFromResourceIDString takes a comma separated list of GCP KMS
// resource IDs and returns a slice of new MasterKeys for them.
func MasterKeysFromResourceIDString(resourceID string) []*MasterKey {
func MasterKeysFromResourceIDString(resourceID, impersonateServiceAccount string) []*MasterKey {
var keys []*MasterKey
if resourceID == "" {
return keys
}
for _, s := range strings.Split(resourceID, ",") {
keys = append(keys, NewMasterKeyFromResourceID(s))
keys = append(keys, NewMasterKeyFromResourceID(s, impersonateServiceAccount))
}
return keys
}
Expand Down Expand Up @@ -207,24 +214,35 @@ func (key *MasterKey) newKMSClient() (*kms.KeyManagementClient, error) {
return nil, fmt.Errorf("no valid resource ID found in %q", key.ResourceID)
}

ctx := context.Background()
var opts []option.ClientOption
switch {
case key.credentialJSON != nil:
opts = append(opts, option.WithCredentialsJSON(key.credentialJSON))
case key.ImpersonateServiceAccount != "":
ts, err := impersonate.CredentialsTokenSource(ctx, impersonate.CredentialsConfig{
TargetPrincipal: key.ImpersonateServiceAccount,
// https://developers.google.com/identity/protocols/oauth2/scopes#cloudkms
Scopes: []string{"https://www.googleapis.com/auth/cloudkms"},
})
if err != nil {
return nil, fmt.Errorf("cannot impersonate service account '%s': %w", key.ImpersonateServiceAccount, err)
}

opts = append(opts, option.WithTokenSource(ts))
default:
credentials, err := getGoogleCredentials()
if err != nil {
return nil, err
}
if credentials != nil {
if bytes.Compare(credentials, nil) != 0 {
opts = append(opts, option.WithCredentialsJSON(key.credentialJSON))
}
}
if key.grpcConn != nil {
opts = append(opts, option.WithGRPCConn(key.grpcConn))
}

ctx := context.Background()
client, err := kms.NewKeyManagementClient(ctx, opts...)
if err != nil {
return nil, err
Expand Down
4 changes: 2 additions & 2 deletions gcpkms/keysource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ var (

func TestMasterKeysFromResourceIDString(t *testing.T) {
s := "projects/sops-testing1/locations/global/keyRings/creds/cryptoKeys/key1, projects/sops-testing2/locations/global/keyRings/creds/cryptoKeys/key2"
ks := MasterKeysFromResourceIDString(s)
ks := MasterKeysFromResourceIDString(s, "")
k1 := ks[0]
k2 := ks[1]
expectedResourceID1 := "projects/sops-testing1/locations/global/keyRings/creds/cryptoKeys/key1"
Expand Down Expand Up @@ -97,7 +97,7 @@ func TestMasterKey_SetEncryptedDataKey(t *testing.T) {

func TestMasterKey_ToString(t *testing.T) {
rsrcId := testResourceID
key := NewMasterKeyFromResourceID(rsrcId)
key := NewMasterKeyFromResourceID(rsrcId, "")
assert.Equal(t, rsrcId, key.ToString())
}

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ require (
google.golang.org/api v0.87.0
google.golang.org/genproto v0.0.0-20220712132514-bdd2acd4974d
google.golang.org/grpc v1.48.0
google.golang.org/protobuf v1.28.0
google.golang.org/protobuf v1.28.1
gopkg.in/ini.v1 v1.66.4
gopkg.in/yaml.v3 v3.0.1
)
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1042,6 +1042,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
3 changes: 2 additions & 1 deletion keyservice/keyservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ func KeyFromMasterKey(mk keys.MasterKey) Key {
return Key{
KeyType: &Key_GcpKmsKey{
GcpKmsKey: &GcpKmsKey{
ResourceId: mk.ResourceID,
ResourceId: mk.ResourceID,
GcpImpersonateServiceAccount: mk.ImpersonateServiceAccount,
},
},
}
Expand Down
Loading