From eec01f7d69dfac56b2ac9257ea561879f7a7ea65 Mon Sep 17 00:00:00 2001 From: Elim Tsiagbey Date: Tue, 17 Sep 2024 10:50:10 -0400 Subject: [PATCH] feat: add attributes (#6) add support for google host attributes: - project_id (Required): The project ID associated with the service account - zone (Required): The deployment area within a region disable_credential_rotation (Optional): Disables credential rotation by the plugin - client_email (Optional): The email address associated with the service account. The email address used to uniquely identify the service account. It is required for authentication and authorization. If it is not set, authentication will fail. - target_service_account_id (Optional): The unique identifier for the service account that will be impersonated. - disable_credential_rotation (Optional): Disable the rotation of google service account key associated with the plugin --- internal/credential/attributes.go | 46 +++++++++++++++++++---- internal/credential/attributes_test.go | 52 +++++++++++++++++++++++--- internal/credential/const.go | 18 +++++++-- plugin/service/host/attributes.go | 12 ++++-- plugin/service/host/attributes_test.go | 20 +++++----- plugin/service/host/plugin_test.go | 20 +++++----- 6 files changed, 128 insertions(+), 40 deletions(-) diff --git a/internal/credential/attributes.go b/internal/credential/attributes.go index 154de08..71afc88 100644 --- a/internal/credential/attributes.go +++ b/internal/credential/attributes.go @@ -11,21 +11,33 @@ import ( "google.golang.org/protobuf/types/known/structpb" ) -// CredentialAttributes contain attributes used for authenticating to Google Cloud +// CredentialAttributes contain attributes used for authenticating to GCP // and accessing a list of instances type CredentialAttributes struct { - Project string - Zone string + // ProjectId is the project id associated with the GCP credentials + ProjectId string + + // Zone is the zone associated with the GCP credentials + Zone string + + // DisableCredentialRotation disables the rotation of GCP service account key associated with the plugin + DisableCredentialRotation bool + + // ClientEmail is the email associated with the GCP cloud credentials + ClientEmail string + + // TargetServiceAccount is the unique identifier for the service account that will be impersonated + TargetServiceAccountId string } -// GetCredentialAttributes checks attributes required by Google Cloud to access +// GetCredentialAttributes checks attributes required by GCP to access // a list of instances and populate them into Boundary's host catalog func GetCredentialAttributes(in *structpb.Struct) (*CredentialAttributes, error) { badFields := make(map[string]string) - project, err := values.GetStringValue(in, ConstProject, true) + projectId, err := values.GetStringValue(in, ConstProjectId, true) if err != nil { - badFields[fmt.Sprintf("attributes.%s", ConstProject)] = err.Error() + badFields[fmt.Sprintf("attributes.%s", ConstProjectId)] = err.Error() } zone, err := values.GetStringValue(in, ConstZone, true) @@ -33,12 +45,30 @@ func GetCredentialAttributes(in *structpb.Struct) (*CredentialAttributes, error) badFields[fmt.Sprintf("attributes.%s", ConstZone)] = err.Error() } + disableCredentialRotation, err := values.GetBoolValue(in, ConstDisableCredentialRotation, false) + if err != nil { + badFields[fmt.Sprintf("attributes.%s", ConstDisableCredentialRotation)] = err.Error() + } + + clientEmail, err := values.GetStringValue(in, ConstClientEmail, false) + if err != nil { + badFields[fmt.Sprintf("attributes.%s", ConstClientEmail)] = err.Error() + } + + targetServiceAccountId, err := values.GetStringValue(in, ConstTargetServiceAccountID, false) + if err != nil { + badFields[fmt.Sprintf("attributes.%s", ConstTargetServiceAccountID)] = err.Error() + } + if len(badFields) > 0 { return nil, errors.InvalidArgumentError("Error in the attributes provider", badFields) } return &CredentialAttributes{ - Project: project, - Zone: zone, + ProjectId: projectId, + Zone: zone, + DisableCredentialRotation: disableCredentialRotation, + ClientEmail: clientEmail, + TargetServiceAccountId: targetServiceAccountId, }, nil } diff --git a/internal/credential/attributes_test.go b/internal/credential/attributes_test.go index ba125ea..00cd0a0 100644 --- a/internal/credential/attributes_test.go +++ b/internal/credential/attributes_test.go @@ -22,7 +22,7 @@ func TestGetCredentialAttributes(t *testing.T) { { name: "missing project", in: map[string]any{}, - expectedErrContains: "missing required value \"project\"", + expectedErrContains: "missing required value \"project_id\"", }, { name: "missing zone", @@ -32,12 +32,52 @@ func TestGetCredentialAttributes(t *testing.T) { { name: "valid project and zone", in: map[string]any{ - ConstProject: "test-project", - ConstZone: "us-central-1", + ConstProjectId: "test-project", + ConstZone: "us-central-1", }, expected: &CredentialAttributes{ - Project: "test-project", - Zone: "us-central-1", + ProjectId: "test-project", + Zone: "us-central-1", + DisableCredentialRotation: false, + }, + }, + { + name: "with disable_credential_rotation", + in: map[string]any{ + ConstDisableCredentialRotation: true, + ConstProjectId: "test-project", + ConstZone: "us-central-1", + }, + expected: &CredentialAttributes{ + DisableCredentialRotation: true, + ProjectId: "test-project", + Zone: "us-central-1", + }, + }, + { + name: "with client_email", + in: map[string]any{ + ConstClientEmail: "test@test.com", + ConstProjectId: "test-project", + ConstZone: "us-central-1", + }, + expected: &CredentialAttributes{ + ClientEmail: "test@test.com", + ProjectId: "test-project", + Zone: "us-central-1", + }, + }, + { + name: "with target_service_account_id", + in: map[string]any{ + ConstTargetServiceAccountID: "test-target-service-account", + ConstProjectId: "test-project", + ConstZone: "us-central-1", + }, + expected: &CredentialAttributes{ + TargetServiceAccountId: "test-target-service-account", + ProjectId: "test-project", + Zone: "us-central-1", }, }, } @@ -59,7 +99,7 @@ func TestGetCredentialAttributes(t *testing.T) { } require.NoError(err) - require.EqualValues(tc.expected.Project, actual.Project) + require.EqualValues(tc.expected.ProjectId, actual.ProjectId) require.EqualValues(tc.expected.Zone, actual.Zone) }) } diff --git a/internal/credential/const.go b/internal/credential/const.go index a56f694..279f2aa 100644 --- a/internal/credential/const.go +++ b/internal/credential/const.go @@ -4,9 +4,21 @@ package credential const ( - // ConstProject defines the attribute name for a Google Cloud project - ConstProject = "project" + // ConstProjectId defines the attribute name for a GCP project + ConstProjectId = "project_id" - // ConstZone defines the attribute name for a Google Cloud zone + // ConstZone defines the attribute name for a GCP zone ConstZone = "zone" + + // ConstDisableCredentialRotation is the key for the disable credential rotation in the GCP credentials. + ConstDisableCredentialRotation = "disable_credential_rotation" + + // ConstCredsLastRotatedTime is the key for the last rotated time in the GCP credentials. + ConstCredsLastRotatedTime = "creds_last_rotated_time" + + // ConstClientEmail is the email address associated with the service account + ConstClientEmail = "client_email" + + // ConstTargetServiceAccountID is the unique identifier for the service account that will be impersonated. + ConstTargetServiceAccountID = "target_service_account_id" ) diff --git a/plugin/service/host/attributes.go b/plugin/service/host/attributes.go index d330c61..4a7a485 100644 --- a/plugin/service/host/attributes.go +++ b/plugin/service/host/attributes.go @@ -34,10 +34,16 @@ func getCatalogAttributes(in *structpb.Struct) (*CatalogAttributes, error) { for s := range unknownFields { switch s { // Ignore knownFields from CredentialAttributes - case cred.ConstProject: + case cred.ConstProjectId: continue case cred.ConstZone: continue + case cred.ConstDisableCredentialRotation: + continue + case cred.ConstClientEmail: + continue + case cred.ConstTargetServiceAccountID: + continue default: badFields[fmt.Sprintf("attributes.%s", s)] = "unrecognized field" } @@ -107,7 +113,7 @@ func getSetAttributes(in *structpb.Struct) (*SetAttributes, error) { func buildListInstancesRequest(attributes *SetAttributes, catalog *CatalogAttributes) *computepb.ListInstancesRequest { request := &computepb.ListInstancesRequest{ - Project: catalog.Project, + Project: catalog.ProjectId, Zone: catalog.Zone, } @@ -121,7 +127,7 @@ func buildListInstancesRequest(attributes *SetAttributes, catalog *CatalogAttrib func buildListInstanceGroupsRequest(attributes *SetAttributes, catalog *CatalogAttributes) *computepb.ListInstancesInstanceGroupsRequest { request := &computepb.ListInstancesInstanceGroupsRequest{ InstanceGroup: attributes.InstanceGroup, - Project: catalog.Project, + Project: catalog.ProjectId, Zone: catalog.Zone, } diff --git a/plugin/service/host/attributes_test.go b/plugin/service/host/attributes_test.go index a8ae2b6..c6f7fda 100644 --- a/plugin/service/host/attributes_test.go +++ b/plugin/service/host/attributes_test.go @@ -21,20 +21,20 @@ func TestGetCatalogAttributes(t *testing.T) { expectedErrContains string }{ { - name: "missing project and zone", + name: "missing project_id and zone", in: &structpb.Struct{ Fields: make(map[string]*structpb.Value), }, - expectedErrContains: "attributes.project: missing required value \"project\", attributes.zone: missing required value \"zone\"", + expectedErrContains: "attributes.project_id: missing required value \"project_id\", attributes.zone: missing required value \"zone\"", }, { name: "unknown fields", in: &structpb.Struct{ Fields: map[string]*structpb.Value{ - "project": structpb.NewStringValue("test-12345"), - "zone": structpb.NewStringValue("us-central-1a"), - "foo": structpb.NewBoolValue(true), - "bar": structpb.NewBoolValue(true), + "project_id": structpb.NewStringValue("test-12345"), + "zone": structpb.NewStringValue("us-central-1a"), + "foo": structpb.NewBoolValue(true), + "bar": structpb.NewBoolValue(true), }, }, expectedErrContains: "attributes.bar: unrecognized field, attributes.foo: unrecognized field", @@ -43,13 +43,13 @@ func TestGetCatalogAttributes(t *testing.T) { name: "valid project and zone", in: &structpb.Struct{ Fields: map[string]*structpb.Value{ - "project": structpb.NewStringValue("test-12345"), - "zone": structpb.NewStringValue("us-central-1a"), + "project_id": structpb.NewStringValue("test-12345"), + "zone": structpb.NewStringValue("us-central-1a"), }, }, expected: &CatalogAttributes{&cred.CredentialAttributes{ - Project: "test-12345", - Zone: "us-central-1a", + ProjectId: "test-12345", + Zone: "us-central-1a", }}, }, } diff --git a/plugin/service/host/plugin_test.go b/plugin/service/host/plugin_test.go index 79a8ac0..b4875f8 100644 --- a/plugin/service/host/plugin_test.go +++ b/plugin/service/host/plugin_test.go @@ -40,8 +40,8 @@ func TestListHosts(t *testing.T) { hostCatalogAttributes := &hostcatalogs.HostCatalog_Attributes{ Attributes: wrapMap(t, map[string]interface{}{ - cred.ConstProject: project, - cred.ConstZone: zone, + cred.ConstProjectId: project, + cred.ConstZone: zone, }), } @@ -309,8 +309,8 @@ func TestCreateCatalog(t *testing.T) { hostCatalogAttributes := &hostcatalogs.HostCatalog_Attributes{ Attributes: wrapMap(t, map[string]interface{}{ - cred.ConstProject: project, - cred.ConstZone: zone, + cred.ConstProjectId: project, + cred.ConstZone: zone, }), } @@ -390,8 +390,8 @@ func TestUpdateCatalog(t *testing.T) { hostCatalogAttributes := &hostcatalogs.HostCatalog_Attributes{ Attributes: wrapMap(t, map[string]interface{}{ - cred.ConstProject: project, - cred.ConstZone: zone, + cred.ConstProjectId: project, + cred.ConstZone: zone, }), } @@ -477,8 +477,8 @@ func TestCreateSet(t *testing.T) { Attrs: &hostcatalogs.HostCatalog_Attributes{ Attributes: &structpb.Struct{ Fields: map[string]*structpb.Value{ - cred.ConstProject: structpb.NewStringValue("test-project"), - cred.ConstZone: structpb.NewStringValue("us-central1-a"), + cred.ConstProjectId: structpb.NewStringValue("test-project"), + cred.ConstZone: structpb.NewStringValue("us-central1-a"), }, }, }, @@ -599,8 +599,8 @@ func TestUpdateSet(t *testing.T) { Attrs: &hostcatalogs.HostCatalog_Attributes{ Attributes: &structpb.Struct{ Fields: map[string]*structpb.Value{ - cred.ConstProject: structpb.NewStringValue("test-project"), - cred.ConstZone: structpb.NewStringValue("us-central1-a"), + cred.ConstProjectId: structpb.NewStringValue("test-project"), + cred.ConstZone: structpb.NewStringValue("us-central1-a"), }, }, },