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"), }, }, },