Skip to content

Commit

Permalink
feat: assume role provider credentials
Browse files Browse the repository at this point in the history
This commit updates the awsutil depedency to its latest version, which
supports the capabilty of using dynamic credentials. The storage service
was refactored to make static credentials optional. The credentials pkg
was refactored to support several attribute fields used for the assume
role provider. The host service still enforces the use of static
credentials.
  • Loading branch information
ddebko committed Jun 20, 2023
1 parent 7bf9fe2 commit ff420ae
Show file tree
Hide file tree
Showing 23 changed files with 1,382 additions and 780 deletions.
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,32 @@ Boundary](https://www.boundaryproject.io/).

## Credential Rotation

Although credentials are stored encrypted within Boundary, by default this
This is the following priority for the credential chain:
static, assume role, environment variables.

### Static Credentials

Although static credentials are stored encrypted within Boundary, by default this
plugin will attempt to rotate credentials when they are supplied through the
`secrets` object. The given credentials will be used to create a new credential,
and then the given credential will be revoked. In this way, after rotation,
only Boundary knows the client secret in use by this plugin.
only Boundary knows the client secret in use by this plugin. More information
about AWS static credentials can be found [here](https://docs.aws.amazon.com/sdkref/latest/guide/feature-static-credentials.html).

Credential rotation can be turned off by setting the
`disable_credential_rotation` attribute to `true`.

### Assume Role Credentials

This plugin will attempt to assume a role when a `role_arn` is supplied through the
`attributes` object. More information about assume an AWS role can be found [here](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html). This feature only works when the plugin is running on a self managed Boundary [worker](https://developer.hashicorp.com/boundary/tutorials/hcp-administration/hcp-manage-workers).

### Environment Credentials

This plugin will attempt to retrieve credentials from environment variables. More
information about environment variables for AWS credentials can be found [here](https://docs.aws.amazon.com/sdkref/latest/guide/feature-static-credentials.html). This feature only works when the plugin is running on
a self managed Boundary [worker](https://developer.hashicorp.com/boundary/tutorials/hcp-administration/hcp-manage-workers).

## Dynamic Hosts

This plugin supports dynamically sourcing hosts from Amazon EC2.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
github.com/google/go-cmp v0.5.9
github.com/google/uuid v1.3.0
github.com/hashicorp/boundary/sdk v0.0.33
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6
github.com/hashicorp/go-secure-stdlib/awsutil v0.2.2
github.com/hashicorp/terraform-json v0.14.0
github.com/mitchellh/mapstructure v1.5.0
github.com/stretchr/testify v1.8.2
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go v1.44.80 h1:jEXGecSgPdvM5KnyDsSgFhZSm7WwaTp4h544Im4SfhI=
github.com/aws/aws-sdk-go v1.44.80/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go-v2 v1.17.7 h1:CLSjnhJSTSogvqUGhIC6LqFKATMRexcxLZ0i/Nzk9Eg=
Expand Down Expand Up @@ -91,6 +92,8 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9
github.com/hashicorp/go-plugin v1.4.9 h1:ESiK220/qE0aGxWdzKIvRH69iLiuN/PjoLTm69RoWtU=
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6 h1:W9WN8p6moV1fjKLkeqEgkAMu5rauy9QeYDAmIaPuuiA=
github.com/hashicorp/go-secure-stdlib/awsutil v0.1.6/go.mod h1:MpCPSPGLDILGb4JMm94/mMi3YysIqsXzGCzkEZjcjXg=
github.com/hashicorp/go-secure-stdlib/awsutil v0.2.2 h1:kWg2vyKl7BRXrNxYziqDJ55n+vtOQ1QsGORjzoeB+uM=
github.com/hashicorp/go-secure-stdlib/awsutil v0.2.2/go.mod h1:oKHSQs4ivIfZ3fbXGQOop1XuDfdSb8RIsWTGaAanSfg=
github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng=
github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.9 h1:YZ01/aiARPneDUryzjiPGvr3pmjCSC+1WBZKI1P7I7k=
Expand Down
91 changes: 75 additions & 16 deletions internal/credential/attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,47 @@ type CredentialAttributes struct {

// 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 input and returns a
// GetCredentialsConfig parses values out of a protobuf struct secrets and returns a
// CredentialsConfig used for configuring an AWS session. An error is returned if
// any of the following fields are missing from the protobuf struct input or have
// invalid value types: access_key_id, secret_access_key. An error is returned if
// any unrecognized fields are found in the protobuf struct input.
func GetCredentialsConfig(in *structpb.Struct, region string) (*awsutil.CredentialsConfig, error) {
unknownFields := values.StructFields(in)
func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes, required 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(in, ConstAccessKeyId, true)
accessKey, err := values.GetStringValue(secrets, ConstAccessKeyId, required)
if err != nil {
badFields[fmt.Sprintf("secrets.%s", ConstAccessKeyId)] = err.Error()
}
delete(unknownFields, ConstAccessKeyId)

secretKey, err := values.GetStringValue(in, ConstSecretAccessKey, true)
secretKey, err := values.GetStringValue(secrets, ConstSecretAccessKey, required)
if err != nil {
badFields[fmt.Sprintf("secrets.%s", ConstSecretAccessKey)] = err.Error()
}
delete(unknownFields, ConstSecretAccessKey)

if region == "" {
badFields[fmt.Sprintf("attributes.%s", ConstRegion)] = err.Error()
}

// 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)
Expand All @@ -58,11 +72,32 @@ func GetCredentialsConfig(in *structpb.Struct, region string) (*awsutil.Credenti
return nil, errors.InvalidArgumentError("Error in the secrets provided", badFields)
}

return awsutil.NewCredentialsConfig(
awsutil.WithAccessKey(accessKey),
awsutil.WithSecretKey(secretKey),
awsutil.WithRegion(region),
)
opts := []awsutil.Option{
awsutil.WithEnvironmentCredentials(true),
}
if accessKey != "" && secretKey != "" {
opts = append(opts,
awsutil.WithAccessKey(accessKey),
awsutil.WithSecretKey(secretKey),
)
}
if attrs.Region != "" {
opts = append(opts, awsutil.WithRegion(attrs.Region))
}
if attrs.RoleArn != "" {
opts = append(opts, awsutil.WithRoleArn(attrs.RoleArn))
}
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))
}

return awsutil.NewCredentialsConfig(opts...)
}

// GetCredentialAttributes parses values out of a protobuf struct input and returns a
Expand All @@ -88,12 +123,36 @@ func GetCredentialAttributes(in *structpb.Struct) (*CredentialAttributes, error)
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.GetMapString(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
}
Loading

0 comments on commit ff420ae

Please sign in to comment.