Files
threshold-plugin-gcp/internal/credential/attributes.go
T
hashicorp-copywrite[bot] 722757e5c2 [COMPLIANCE] Add/Update Copyright Headers (#38)
Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
2026-05-11 22:40:22 -07:00

169 lines
6.0 KiB
Go

// Copyright IBM Corp. 2024, 2026
// SPDX-License-Identifier: MPL-2.0
package credential
import (
"fmt"
"github.com/hashicorp/boundary-plugin-gcp/internal/errors"
"github.com/hashicorp/boundary-plugin-gcp/internal/values"
"google.golang.org/protobuf/types/known/structpb"
)
// CredentialAttributes contain attributes used for authenticating to GCP
// and accessing a list of instances
type CredentialAttributes struct {
// 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 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)
projectId, err := values.GetStringValue(in, ConstProjectId, true)
if err != nil {
badFields[fmt.Sprintf("attributes.%s", ConstProjectId)] = err.Error()
}
zone, err := values.GetStringValue(in, ConstZone, true)
if err != nil {
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{
ProjectId: projectId,
Zone: zone,
DisableCredentialRotation: disableCredentialRotation,
ClientEmail: clientEmail,
TargetServiceAccountId: targetServiceAccountId,
}, nil
}
// GetCredentialsConfig parses values out of a protobuf struct secrets and returns a
// Config used for configuring an GCP session. An status error is returned
// with an InvalidArgument code if any unrecognized fields are found in the protobuf
// struct input.
func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes) (*Config, error) {
// initialize secrets if it is nil
// secrets can be nil if the authentication type is Application Default Credential(ADC)
if secrets == nil {
secrets = &structpb.Struct{
Fields: make(map[string]*structpb.Value),
}
}
// initialize attrs if it is nil
// attributes can be nil if the authentication type is Application Default Credential(ADC)
// where the credentials are inferred from the environment and no fields are provided
// https://cloud.google.com/docs/authentication/application-default-credentials
if attrs == nil {
attrs = &CredentialAttributes{}
}
unknownFields := values.StructFields(secrets)
badFields := make(map[string]string)
privateKeyId, err := values.GetStringValue(secrets, ConstPrivateKeyId, false)
if err != nil {
badFields[fmt.Sprintf("secrets.%s", ConstPrivateKeyId)] = err.Error()
}
delete(unknownFields, ConstPrivateKeyId)
privateKey, err := values.GetStringValue(secrets, ConstPrivateKey, false)
if err != nil {
badFields[fmt.Sprintf("secrets.%s", ConstPrivateKey)] = err.Error()
}
delete(unknownFields, ConstPrivateKey)
delete(unknownFields, ConstCredsLastRotatedTime)
for s := range unknownFields {
badFields[fmt.Sprintf("secrets.%s", s)] = "unrecognized field"
}
switch {
// service account impersonation authentication requires the target service account id and client_email
case attrs.TargetServiceAccountId != "" && attrs.ClientEmail == "":
badFields[fmt.Sprintf("secrets.%s", ConstPrivateKey)] = "must not be empty when target service account id is set"
// service account impersonation authentication requires the target service account id and private key
case attrs.TargetServiceAccountId != "" && privateKey == "":
badFields[fmt.Sprintf("attributes.%s", ConstClientEmail)] = "must not be empty when target service account id is set"
// service account key credentials requires the private key and client email
case privateKey != "" && attrs.ClientEmail == "":
badFields[fmt.Sprintf("attributes.%s", ConstClientEmail)] = "must not be empty when private key is set"
// service account key credentials requires the client_email
case attrs.ClientEmail != "" && privateKey == "":
badFields[fmt.Sprintf("secrets.%s", ConstPrivateKey)] = "must not be empty when client email is set"
}
if len(badFields) > 0 {
return nil, errors.InvalidArgumentError("Error in the secrets provided", badFields)
}
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
}