Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IAM tagging support for iam_user roles in AWS secret engine #10953

Merged
merged 4 commits into from
Feb 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions builtin/logical/aws/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1437,6 +1437,65 @@ func testAccStepReadIamGroups(t *testing.T, name string, groups []string) logica
}
}

func TestBackend_iamTagsCrud(t *testing.T) {
logicaltest.Test(t, logicaltest.TestCase{
AcceptanceTest: true,
LogicalBackend: getBackend(t),
Steps: []logicaltest.TestStep{
testAccStepConfig(t),
testAccStepWriteIamTags(t, "test", map[string]string{"key1": "value1", "key2": "value2"}),
testAccStepReadIamTags(t, "test", map[string]string{"key1": "value1", "key2": "value2"}),
testAccStepDeletePolicy(t, "test"),
testAccStepReadIamTags(t, "test", map[string]string{}),
},
})
}

func testAccStepWriteIamTags(t *testing.T, name string, tags map[string]string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "roles/" + name,
Data: map[string]interface{}{
"credential_type": iamUserCred,
"iam_tags": tags,
},
}
}

func testAccStepReadIamTags(t *testing.T, name string, tags map[string]string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ReadOperation,
Path: "roles/" + name,
Check: func(resp *logical.Response) error {
if resp == nil {
if len(tags) == 0 {
return nil
}

return fmt.Errorf("vault response not received")
}

expected := map[string]interface{}{
"policy_arns": []string(nil),
"role_arns": []string(nil),
"policy_document": "",
"credential_type": iamUserCred,
"default_sts_ttl": int64(0),
"max_sts_ttl": int64(0),
"user_path": "",
"permissions_boundary_arn": "",
"iam_groups": []string(nil),
"iam_tags": tags,
}
if !reflect.DeepEqual(resp.Data, expected) {
return fmt.Errorf("bad: got: %#v\nexpected: %#v", resp.Data, expected)
}

return nil
},
}
}

func generateUniqueName(prefix string) string {
return testhelpers.RandomWithPrefix(prefix)
}
Expand Down
41 changes: 29 additions & 12 deletions builtin/logical/aws/path_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@ and policy_arns parameters.`,
},
},

"iam_tags": &framework.FieldSchema{
Type: framework.TypeKVPairs,
Description: `IAM tags to be set for any users created by this role. These must be presented
as Key-Value pairs. This can be represented as a map or a list of equal sign
delimited key pairs.`,
DisplayAttrs: &framework.DisplayAttributes{
Name: "IAM Tags",
Value: "[key1=value1, key2=value2]",
},
},

"default_sts_ttl": &framework.FieldSchema{
Type: framework.TypeDurationSecond,
Description: fmt.Sprintf("Default TTL for %s and %s credential types when no TTL is explicitly requested with the credentials", assumedRoleCred, federationTokenCred),
Expand Down Expand Up @@ -301,6 +312,10 @@ func (b *backend) pathRolesWrite(ctx context.Context, req *logical.Request, d *f
roleEntry.IAMGroups = iamGroups.([]string)
}

if iamTags, ok := d.GetOk("iam_tags"); ok {
roleEntry.IAMTags = iamTags.(map[string]string)
Valarissa marked this conversation as resolved.
Show resolved Hide resolved
}

if legacyRole != "" {
roleEntry = upgradeLegacyPolicyEntry(legacyRole)
if roleEntry.InvalidData != "" {
Expand Down Expand Up @@ -481,18 +496,19 @@ func setAwsRole(ctx context.Context, s logical.Storage, roleName string, roleEnt
}

type awsRoleEntry struct {
CredentialTypes []string `json:"credential_types"` // Entries must all be in the set of ("iam_user", "assumed_role", "federation_token")
PolicyArns []string `json:"policy_arns"` // ARNs of managed policies to attach to an IAM user
RoleArns []string `json:"role_arns"` // ARNs of roles to assume for AssumedRole credentials
PolicyDocument string `json:"policy_document"` // JSON-serialized inline policy to attach to IAM users and/or to specify as the Policy parameter in AssumeRole calls
IAMGroups []string `json:"iam_groups"` // Names of IAM groups that generated IAM users will be added to
InvalidData string `json:"invalid_data,omitempty"` // Invalid role data. Exists to support converting the legacy role data into the new format
ProhibitFlexibleCredPath bool `json:"prohibit_flexible_cred_path,omitempty"` // Disallow accessing STS credentials via the creds path and vice verse
Version int `json:"version"` // Version number of the role format
DefaultSTSTTL time.Duration `json:"default_sts_ttl"` // Default TTL for STS credentials
MaxSTSTTL time.Duration `json:"max_sts_ttl"` // Max allowed TTL for STS credentials
UserPath string `json:"user_path"` // The path for the IAM user when using "iam_user" credential type
PermissionsBoundaryARN string `json:"permissions_boundary_arn"` // ARN of an IAM policy to attach as a permissions boundary
CredentialTypes []string `json:"credential_types"` // Entries must all be in the set of ("iam_user", "assumed_role", "federation_token")
PolicyArns []string `json:"policy_arns"` // ARNs of managed policies to attach to an IAM user
RoleArns []string `json:"role_arns"` // ARNs of roles to assume for AssumedRole credentials
PolicyDocument string `json:"policy_document"` // JSON-serialized inline policy to attach to IAM users and/or to specify as the Policy parameter in AssumeRole calls
IAMGroups []string `json:"iam_groups"` // Names of IAM groups that generated IAM users will be added to
IAMTags map[string]string `json:"iam_tags"` // IAM tags that will be added to the generated IAM users
InvalidData string `json:"invalid_data,omitempty"` // Invalid role data. Exists to support converting the legacy role data into the new format
ProhibitFlexibleCredPath bool `json:"prohibit_flexible_cred_path,omitempty"` // Disallow accessing STS credentials via the creds path and vice verse
Version int `json:"version"` // Version number of the role format
DefaultSTSTTL time.Duration `json:"default_sts_ttl"` // Default TTL for STS credentials
MaxSTSTTL time.Duration `json:"max_sts_ttl"` // Max allowed TTL for STS credentials
UserPath string `json:"user_path"` // The path for the IAM user when using "iam_user" credential type
PermissionsBoundaryARN string `json:"permissions_boundary_arn"` // ARN of an IAM policy to attach as a permissions boundary
}

func (r *awsRoleEntry) toResponseData() map[string]interface{} {
Expand All @@ -502,6 +518,7 @@ func (r *awsRoleEntry) toResponseData() map[string]interface{} {
"role_arns": r.RoleArns,
"policy_document": r.PolicyDocument,
"iam_groups": r.IAMGroups,
"iam_tags": r.IAMTags,
"default_sts_ttl": int64(r.DefaultSTSTTL.Seconds()),
"max_sts_ttl": int64(r.MaxSTSTTL.Seconds()),
"user_path": r.UserPath,
Expand Down
30 changes: 26 additions & 4 deletions builtin/logical/aws/secret_access_keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import (
"regexp"
"time"

"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/awsutil"
"github.com/hashicorp/vault/sdk/logical"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/awsutil"
"github.com/hashicorp/vault/sdk/logical"
)

const secretAccessKeyType = "access_keys"
Expand Down Expand Up @@ -210,7 +211,8 @@ func (b *backend) assumeRole(ctx context.Context, s logical.Storage,
func (b *backend) secretAccessKeysCreate(
ctx context.Context,
s logical.Storage,
displayName, policyName string, role *awsRoleEntry) (*logical.Response, error) {
displayName, policyName string,
role *awsRoleEntry) (*logical.Response, error) {
iamClient, err := b.clientIAM(ctx, s)
if err != nil {
return logical.ErrorResponse(err.Error()), nil
Expand Down Expand Up @@ -286,6 +288,26 @@ func (b *backend) secretAccessKeysCreate(
}
}

var tags []*iam.Tag
for key, value := range role.IAMTags {
// This assignment needs to be done in order to create unique addresses for
// these variables. Without doing so, all the tags will be copies of the last
// tag listed in the role.
k, v := key, value
tags = append(tags, &iam.Tag{Key: &k, Value: &v})
}

if len(tags) > 0 {
_, err = iamClient.TagUser(&iam.TagUserInput{
Tags: tags,
UserName: &username,
})

if err != nil {
return logical.ErrorResponse("Error adding tags to user: %s", err), awsutil.CheckAWSError(err)
}
}

// Create the keys
keyResp, err := iamClient.CreateAccessKey(&iam.CreateAccessKeyInput{
UserName: aws.String(username),
Expand Down
49 changes: 49 additions & 0 deletions website/content/api-docs/secret/aws.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,12 @@ updated with the new attributes.
policies from each group in `iam_groups` combined with the `policy_document`
and `policy_arns` parameters.

- `iam_tags` `(list: [])` - A list of strings representing a key/value pair to be used as a
tag for any `iam_user` user that is created by this role. Format is a key and value
separated by an `=` (e.g. `test_key=value`). Note: when using the CLI multiple tags
can be specified in the role configuration by adding another `iam_tags` assignment
in the same command.

- `default_sts_ttl` `(string)` - The default TTL for STS credentials. When a TTL is not
specified when STS credentials are requested, and a default TTL is specified
on the role, then this default TTL will be used. Valid only when
Expand Down Expand Up @@ -329,6 +335,49 @@ Using groups:
}
```

Using tags:
Valarissa marked this conversation as resolved.
Show resolved Hide resolved
<Tabs>
<Tab heading="cURL">
```json
{
"credential_type": "iam_user",
"iam_tags": [
"first_key=first_value",
"second_key=second_value"
]
}
```
or
```json
{
"credential_type": "iam_user",
"iam_tags": {
"first_key": "first_value",
"second_key": "second_value"
}
}
```
</Tab>
<Tab heading="CLI">
```bash
vault write aws/roles/example-role \
credential_type=iam_user \
iam_tags="first_key=first_value" \
iam_tags="second_key=second_value" \
```
or
```bash
vault write aws/roles/example-role \
credential_type=iam_user \
[email protected]
```
where test.json is
```json
["tag1=42", "tag2=something"]
```
</Tab>
</Tabs>

## Read Role

This endpoint queries an existing role by the given name. If the role does not
Expand Down