// Copyright IBM Corp. 2021, 2026 // SPDX-License-Identifier: MPL-2.0 package credential import ( "fmt" "github.com/hashicorp/boundary-plugin-aws/internal/errors" "github.com/hashicorp/boundary-plugin-aws/internal/values" "github.com/hashicorp/go-secure-stdlib/awsutil/v2" "google.golang.org/protobuf/types/known/structpb" ) // CredentialAttributes contains attributes used for AWS credentials type CredentialAttributes struct { // Region is the region associated with the aws credentials Region string // DisableCredentialRotation disables the rotation of aws secrets associated with the plugin DisableCredentialRotation bool // RoleArn is the role arn associated with the aws credentials RoleArn string // RoleExternalId is the external id associated with the aws credentials RoleExternalId string // RoleSessionName is the session name associated with the aws credentials RoleSessionName string // RoleTags is the tags associated with the aws credentials RoleTags map[string]string } // GetCredentialsConfig parses values out of a protobuf struct secrets and returns a // CredentialsConfig used for configuring an AWS 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, dualStack bool) (*awsutil.CredentialsConfig, error) { // initialize secrets if it is nil // secrets can be nil because static credentials are optional if secrets == nil { secrets = &structpb.Struct{ Fields: make(map[string]*structpb.Value), } } unknownFields := values.StructFields(secrets) badFields := make(map[string]string) accessKey, err := values.GetStringValue(secrets, ConstAccessKeyId, false) if err != nil { badFields[fmt.Sprintf("secrets.%s", ConstAccessKeyId)] = err.Error() } delete(unknownFields, ConstAccessKeyId) secretKey, err := values.GetStringValue(secrets, ConstSecretAccessKey, false) if err != nil { badFields[fmt.Sprintf("secrets.%s", ConstSecretAccessKey)] = err.Error() } delete(unknownFields, ConstSecretAccessKey) // the creds_last_rotated_time field will be found in the input struct for // persisted secrets, this value is not needed for creating the CredentialsConfig delete(unknownFields, ConstCredsLastRotatedTime) for s := range unknownFields { badFields[fmt.Sprintf("secrets.%s", s)] = "unrecognized field" } opts := []awsutil.Option{} // logic for parsing the credential type // supported types: // - user static credential // - ec2 assume role dynamic credential // - enviornment variables credential switch { // static credentials and dynamic credentials cannot be used together case accessKey != "" && secretKey != "" && attrs.RoleArn != "": badFields[fmt.Sprintf("secrets.%s", ConstAccessKeyId)] = "conflicts with role_arn value" badFields[fmt.Sprintf("secrets.%s", ConstSecretAccessKey)] = "conflicts with role_arn value" badFields[fmt.Sprintf("attributes.%s", ConstRoleArn)] = "conflicts with access_key_id and secret_access_key values" // static credential is missing it's secret_access_key case accessKey != "" && secretKey == "": badFields[fmt.Sprintf("secrets.%s", ConstSecretAccessKey)] = "missing required value" // static credential is missing it's access_key_id case accessKey == "" && secretKey != "": badFields[fmt.Sprintf("secrets.%s", ConstAccessKeyId)] = "missing required value" // dynamic credentials and credential rotation is not supported case len(attrs.RoleArn) > 0 && !attrs.DisableCredentialRotation: badFields[fmt.Sprintf("attributes.%s", ConstDisableCredentialRotation)] = "disable_credential_rotation attribute is required when providing a role_arn" // add static credentials case accessKey != "" && secretKey != "": opts = append(opts, awsutil.WithAccessKey(accessKey), awsutil.WithSecretKey(secretKey), ) // add dynamic credentials case attrs.RoleArn != "": opts = append(opts, awsutil.WithRoleArn(attrs.RoleArn)) } if len(badFields) > 0 { return nil, errors.InvalidArgumentError("Error in the secrets provided", badFields) } if attrs.Region != "" { opts = append(opts, awsutil.WithRegion(attrs.Region)) } if attrs.RoleExternalId != "" { opts = append(opts, awsutil.WithRoleExternalId(attrs.RoleExternalId)) } if attrs.RoleSessionName != "" { opts = append(opts, awsutil.WithRoleSessionName(attrs.RoleSessionName)) } if len(attrs.RoleTags) != 0 { opts = append(opts, awsutil.WithRoleTags(attrs.RoleTags)) } if dualStack { opts = append(opts, []awsutil.Option{ awsutil.WithIamEndpointResolver(&iamEndpointResolver{ dualStack: dualStack, }), awsutil.WithStsEndpointResolver(&stsEndpointResolver{ dualStack: dualStack, }), }...) } cfg, err := awsutil.NewCredentialsConfig(opts...) if err != nil { return nil, errors.BadRequestStatus(err.Error()) } return cfg, nil } // GetCredentialAttributes parses values out of a protobuf struct input and returns a // CredentialAttributes used for configuring an AWS session. An status error is returned // with an InvalidArgument code if any of the following fields are missing from the protobuf // struct input or have invalid value types: region, disableCredentialRotation func GetCredentialAttributes(in *structpb.Struct) (*CredentialAttributes, error) { badFields := make(map[string]string) region, err := values.GetStringValue(in, ConstRegion, true) if err != nil { badFields[fmt.Sprintf("attributes.%s", ConstRegion)] = err.Error() } disableCredentialRotation, err := values.GetBoolValue(in, ConstDisableCredentialRotation, false) if err != nil { badFields[fmt.Sprintf("attributes.%s", ConstDisableCredentialRotation)] = err.Error() } roleArn, err := values.GetStringValue(in, ConstRoleArn, false) if err != nil { badFields[fmt.Sprintf("attributes.%s", ConstRoleArn)] = err.Error() } roleExternalId, err := values.GetStringValue(in, ConstRoleExternalId, false) if err != nil { badFields[fmt.Sprintf("attributes.%s", ConstRoleExternalId)] = err.Error() } roleSessionName, err := values.GetStringValue(in, ConstRoleSessionName, false) if err != nil { badFields[fmt.Sprintf("attributes.%s", ConstRoleSessionName)] = err.Error() } roleTags, err := values.GetMapStringString(in, ConstRoleTags, false) if err != nil { badFields[fmt.Sprintf("attributes.%s", ConstRoleTags)] = err.Error() } if len(badFields) > 0 { return nil, errors.InvalidArgumentError("Error in the attributes provided", badFields) } return &CredentialAttributes{ Region: region, DisableCredentialRotation: disableCredentialRotation, RoleArn: roleArn, RoleExternalId: roleExternalId, RoleSessionName: roleSessionName, RoleTags: roleTags, }, nil } // GetTargetCredentialAttributes parses values out of a protobuf struct input and returns a CredentialAttributes used // for configuring a second AWS session. target_role_arn is required when parsing target credentials. // A status error is returned with an InvalidArgument code when target account attributes are missing // required values or have invalid value types. func GetTargetCredentialAttributes(in *structpb.Struct) (*CredentialAttributes, error) { badFields := make(map[string]string) // Parse the role ARN of the target account to AssumeRole into. targetRoleArn, err := values.GetStringValue(in, ConstTargetRoleArn, true) if err != nil { badFields[fmt.Sprintf("attributes.%s", ConstTargetRoleArn)] = err.Error() } // try parsing target_region, fallback to region, adds target_region to bad fields only when malformed since not required targetRegion, err := values.GetStringValue(in, ConstTargetRegion, false) if err != nil { badFields[fmt.Sprintf("attributes.%s", ConstTargetRegion)] = err.Error() } if targetRegion == "" { targetRegion, err = values.GetStringValue(in, ConstRegion, true) // region is required for processing if err != nil { badFields[fmt.Sprintf("attributes.%s", ConstRegion)] = err.Error() } } // the remaining fields are optional and only raise errors when malformed disableCredentialRotation, err := values.GetBoolValue(in, ConstDisableCredentialRotation, false) if err != nil { badFields[fmt.Sprintf("attributes.%s", ConstDisableCredentialRotation)] = err.Error() } targetRoleExternalId, err := values.GetStringValue(in, ConstTargetRoleExternalId, false) if err != nil { badFields[fmt.Sprintf("attributes.%s", ConstTargetRoleExternalId)] = err.Error() } targetRoleSessionName, err := values.GetStringValue(in, ConstTargetRoleSessionName, false) if err != nil { badFields[fmt.Sprintf("attributes.%s", ConstTargetRoleSessionName)] = err.Error() } targetRoleTags, err := values.GetMapStringString(in, ConstTargetRoleTags, false) if err != nil { badFields[fmt.Sprintf("attributes.%s", ConstTargetRoleTags)] = err.Error() } if len(badFields) > 0 { return nil, errors.InvalidArgumentError("Error in the attributes provided", badFields) } return &CredentialAttributes{ Region: targetRegion, DisableCredentialRotation: disableCredentialRotation, RoleArn: targetRoleArn, RoleExternalId: targetRoleExternalId, RoleSessionName: targetRoleSessionName, RoleTags: targetRoleTags, }, nil }