From 89c8ee2d6d7cb15980332a561b8899cb7d12b153 Mon Sep 17 00:00:00 2001 From: Elim Tsiagbey Date: Wed, 30 Oct 2024 13:58:45 -0400 Subject: [PATCH] feat: Set default GCP Scope (#16) * feat: Set default GCP Scope Update the GCP credential configuration to include a default scope for GCP access. The default scope is set to `https://www.googleapis.com/auth/cloud-platform` as recommended by [GCP's Best Practice](https://cloud.google.com/compute/docs/access/service-accounts#scopes_best_practice). This allows controlling the service account's access by granting it IAM roles. --- internal/credential/attributes.go | 40 ++++++++++++++++++++------ internal/credential/attributes_test.go | 14 ++++++--- internal/credential/config.go | 14 +++++++-- internal/credential/config_test.go | 29 +++++++++++++++++-- internal/credential/options.go | 1 + internal/credential/options_test.go | 2 +- plugin/service/host/README.md | 2 +- 7 files changed, 82 insertions(+), 20 deletions(-) diff --git a/internal/credential/attributes.go b/internal/credential/attributes.go index 6a991ad..b344b52 100644 --- a/internal/credential/attributes.go +++ b/internal/credential/attributes.go @@ -130,12 +130,36 @@ func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes) return nil, errors.InvalidArgumentError("Error in the secrets provided", badFields) } - return &Config{ - ProjectId: attrs.ProjectId, - ClientEmail: attrs.ClientEmail, - Zone: attrs.Zone, - PrivateKey: privateKey, - PrivateKeyId: privateKeyId, - TargetServiceAccountId: attrs.TargetServiceAccountId, - }, nil + cfgOpts := []Option{} + if privateKeyId != "" && privateKey != "" { + cfgOpts = append(cfgOpts, + WithPrivateKey(privateKey), + WithPrivateKeyId(privateKeyId)) + } + + if attrs.ProjectId != "" { + cfgOpts = append(cfgOpts, WithProjectId(attrs.ProjectId)) + } + if attrs.ClientEmail != "" { + cfgOpts = append(cfgOpts, WithClientEmail(attrs.ClientEmail)) + } + if attrs.Zone != "" { + cfgOpts = append(cfgOpts, WithZone(attrs.Zone)) + } + if privateKey != "" { + cfgOpts = append(cfgOpts, WithPrivateKey(privateKey)) + } + if privateKeyId != "" { + cfgOpts = append(cfgOpts, WithPrivateKeyId(privateKeyId)) + } + if attrs.TargetServiceAccountId != "" { + cfgOpts = append(cfgOpts, WithTargetServiceAccountId(attrs.TargetServiceAccountId)) + } + + credentialsConfig, err := NewConfig(cfgOpts...) + if err != nil { + return nil, fmt.Errorf("error creating credentials config: %w", err) + } + + return credentialsConfig, nil } diff --git a/internal/credential/attributes_test.go b/internal/credential/attributes_test.go index 81d5bb7..e36bf73 100644 --- a/internal/credential/attributes_test.go +++ b/internal/credential/attributes_test.go @@ -119,13 +119,16 @@ func TestGetCredentialsConfig(t *testing.T) { Zone: "us-central-1a", }, expected: &Config{ - Zone: "us-central-1a", + Zone: "us-central-1a", + Scopes: []string{defaultGCPScope}, }, }, { - name: "nil attributes", - attrs: nil, - expected: &Config{}, + name: "nil attributes", + attrs: nil, + expected: &Config{ + Scopes: []string{defaultGCPScope}, + }, }, { name: "unknown fields in secrets", @@ -156,6 +159,7 @@ func TestGetCredentialsConfig(t *testing.T) { ClientEmail: "test@test.com", PrivateKey: "test-private-key", PrivateKeyId: "test-private-key-id", + Scopes: []string{defaultGCPScope}, }, }, { @@ -221,6 +225,7 @@ func TestGetCredentialsConfig(t *testing.T) { PrivateKey: "test-private-key", PrivateKeyId: "test-private-key-id", ClientEmail: "test@test.com", + Scopes: []string{defaultGCPScope}, }, }, { @@ -242,6 +247,7 @@ func TestGetCredentialsConfig(t *testing.T) { PrivateKeyId: "test-private-key-id", ClientEmail: "test@test.com", TargetServiceAccountId: "test-target-service-account", + Scopes: []string{defaultGCPScope}, }, }, } diff --git a/internal/credential/config.go b/internal/credential/config.go index ff27d35..4dbc11e 100644 --- a/internal/credential/config.go +++ b/internal/credential/config.go @@ -31,9 +31,17 @@ var findDefaultCredentialsFn = func(ctx context.Context, scopes ...string) (*goo return google.FindDefaultCredentials(ctx, scopes...) } -// serviceAccountTokenLifetime is the lifetime of the generated GCP authentication token. -// The token is valid for 1 hour. -const serviceAccountTokenLifetime = time.Hour +const ( + // serviceAccountTokenLifetime is the lifetime of the generated GCP authentication token. + // The token is valid for 1 hour. + serviceAccountTokenLifetime = time.Hour + // defaultGCPScope is the default scope for GCP. + // GCP requires the cloud-platform scope to access all GCP services. + // GCP recommends setting the scope to https://www.googleapis.com/auth/cloud-platform + // and then controlling the service account's access by granting it IAM roles. + // https://cloud.google.com/compute/docs/access/service-accounts#scopes_best_practice + defaultGCPScope = "https://www.googleapis.com/auth/cloud-platform" +) // Config is the configuration for the GCP credential. type Config struct { diff --git a/internal/credential/config_test.go b/internal/credential/config_test.go index 2febd78..f7e9e22 100644 --- a/internal/credential/config_test.go +++ b/internal/credential/config_test.go @@ -414,9 +414,11 @@ func TestNewConfig(t *testing.T) { expectError: false, }, { - name: "No Options", - options: []Option{}, - expected: &Config{}, + name: "No Options", + options: []Option{}, + expected: &Config{ + Scopes: []string{defaultGCPScope}, + }, expectError: false, }, { @@ -429,6 +431,27 @@ func TestNewConfig(t *testing.T) { expected: nil, expectError: true, }, + { + name: "no scope set", + options: []Option{ + WithProjectId("test-project-id"), + WithPrivateKey("test-private-key"), + WithPrivateKeyId("test-private-key-id"), + WithClientEmail("test-email@example.com"), + WithTargetServiceAccountId("target-service-account@example.com"), + WithZone("test-zone"), + }, + expected: &Config{ + ProjectId: "test-project-id", + PrivateKey: "test-private-key", + PrivateKeyId: "test-private-key-id", + ClientEmail: "test-email@example.com", + TargetServiceAccountId: "target-service-account@example.com", + Zone: "test-zone", + Scopes: []string{defaultGCPScope}, + }, + expectError: false, + }, } for _, tt := range tests { diff --git a/internal/credential/options.go b/internal/credential/options.go index 183be53..76bcff1 100644 --- a/internal/credential/options.go +++ b/internal/credential/options.go @@ -37,6 +37,7 @@ type Option func(*Options) error func getDefaultOptions() *Options { return &Options{ WithCredentialsConfig: &Config{}, + WithScopes: []string{defaultGCPScope}, } } diff --git a/internal/credential/options_test.go b/internal/credential/options_test.go index 1b7a19b..7b184fd 100644 --- a/internal/credential/options_test.go +++ b/internal/credential/options_test.go @@ -111,7 +111,7 @@ func Test_getOpts(t *testing.T) { t.Run("WithScopes", func(t *testing.T) { opts, err := getOpts() require.NoError(t, err) - require.Nil(t, opts.WithScopes) + require.Equal(t, []string{defaultGCPScope}, opts.WithScopes) scopes := []string{"test"} opts, err = getOpts(WithScopes(scopes)) diff --git a/plugin/service/host/README.md b/plugin/service/host/README.md index 0bd7791..d1daa3d 100644 --- a/plugin/service/host/README.md +++ b/plugin/service/host/README.md @@ -26,7 +26,7 @@ boundary host-sets create plugin \ -host-catalog-id HOST_CATALOG_ID \ -name "Example Plugin-Based Host Set" \ -description "Description for plugin-based host set" \ - -attr filters=status=RUNNING + -attr filters=status=RUNNING \ -attr filters=labels.env:prod ```