Merge pull request #6380 from povils/aws_user_path
AWS add user_path option for role.
This commit is contained in:
commit
22a6e54957
|
@ -7,6 +7,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -556,6 +557,27 @@ func describeAzsTestUnauthorized(accessKey, secretKey, token string) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assertCreatedIAMUser(accessKey, secretKey, token string) error {
|
||||||
|
creds := credentials.NewStaticCredentials(accessKey, secretKey, token)
|
||||||
|
awsConfig := &aws.Config{
|
||||||
|
Credentials: creds,
|
||||||
|
Region: aws.String("us-east-1"),
|
||||||
|
HTTPClient: cleanhttp.DefaultClient(),
|
||||||
|
}
|
||||||
|
client := iam.New(session.New(awsConfig))
|
||||||
|
log.Printf("[WARN] Checking if IAM User is created properly...")
|
||||||
|
userOutput, err := client.GetUser(&iam.GetUserInput{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if *userOutput.User.Path != "/path/" {
|
||||||
|
return fmt.Errorf("bad: got: %#v\nexpected: %#v", userOutput.User.Path, "/path/")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func listIamUsersTest(accessKey, secretKey, token string) error {
|
func listIamUsersTest(accessKey, secretKey, token string) error {
|
||||||
creds := credentials.NewStaticCredentials(accessKey, secretKey, token)
|
creds := credentials.NewStaticCredentials(accessKey, secretKey, token)
|
||||||
awsConfig := &aws.Config{
|
awsConfig := &aws.Config{
|
||||||
|
@ -647,12 +669,13 @@ func testAccStepReadPolicy(t *testing.T, name string, value string) logicaltest.
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := map[string]interface{}{
|
expected := map[string]interface{}{
|
||||||
"policy_arns": []string(nil),
|
"policy_arns": []string(nil),
|
||||||
"role_arns": []string(nil),
|
"role_arns": []string(nil),
|
||||||
"policy_document": value,
|
"policy_document": value,
|
||||||
"credential_types": []string{iamUserCred, federationTokenCred},
|
"credential_type": strings.Join([]string{iamUserCred, federationTokenCred}, ","),
|
||||||
"default_sts_ttl": int64(0),
|
"default_sts_ttl": int64(0),
|
||||||
"max_sts_ttl": int64(0),
|
"max_sts_ttl": int64(0),
|
||||||
|
"user_path": "",
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(resp.Data, expected) {
|
if !reflect.DeepEqual(resp.Data, expected) {
|
||||||
return fmt.Errorf("bad: got: %#v\nexpected: %#v", resp.Data, expected)
|
return fmt.Errorf("bad: got: %#v\nexpected: %#v", resp.Data, expected)
|
||||||
|
@ -743,15 +766,18 @@ func TestBackend_iamUserManagedInlinePolicies(t *testing.T) {
|
||||||
"policy_document": testDynamoPolicy,
|
"policy_document": testDynamoPolicy,
|
||||||
"policy_arns": []string{ec2PolicyArn, iamPolicyArn},
|
"policy_arns": []string{ec2PolicyArn, iamPolicyArn},
|
||||||
"credential_type": iamUserCred,
|
"credential_type": iamUserCred,
|
||||||
|
"user_path": "/path/",
|
||||||
}
|
}
|
||||||
expectedRoleData := map[string]interface{}{
|
expectedRoleData := map[string]interface{}{
|
||||||
"policy_document": compacted,
|
"policy_document": compacted,
|
||||||
"policy_arns": []string{ec2PolicyArn, iamPolicyArn},
|
"policy_arns": []string{ec2PolicyArn, iamPolicyArn},
|
||||||
"credential_types": []string{iamUserCred},
|
"credential_type": iamUserCred,
|
||||||
"role_arns": []string(nil),
|
"role_arns": []string(nil),
|
||||||
"default_sts_ttl": int64(0),
|
"default_sts_ttl": int64(0),
|
||||||
"max_sts_ttl": int64(0),
|
"max_sts_ttl": int64(0),
|
||||||
|
"user_path": "/path/",
|
||||||
}
|
}
|
||||||
|
|
||||||
logicaltest.Test(t, logicaltest.TestCase{
|
logicaltest.Test(t, logicaltest.TestCase{
|
||||||
AcceptanceTest: true,
|
AcceptanceTest: true,
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
@ -760,7 +786,7 @@ func TestBackend_iamUserManagedInlinePolicies(t *testing.T) {
|
||||||
testAccStepConfig(t),
|
testAccStepConfig(t),
|
||||||
testAccStepWriteRole(t, "test", roleData),
|
testAccStepWriteRole(t, "test", roleData),
|
||||||
testAccStepReadRole(t, "test", expectedRoleData),
|
testAccStepReadRole(t, "test", expectedRoleData),
|
||||||
testAccStepRead(t, "creds", "test", []credentialTestFunc{describeInstancesTest, listIamUsersTest, listDynamoTablesTest}),
|
testAccStepRead(t, "creds", "test", []credentialTestFunc{describeInstancesTest, listIamUsersTest, listDynamoTablesTest, assertCreatedIAMUser}),
|
||||||
testAccStepRead(t, "sts", "test", []credentialTestFunc{describeInstancesTest, listIamUsersTest, listDynamoTablesTest}),
|
testAccStepRead(t, "sts", "test", []credentialTestFunc{describeInstancesTest, listIamUsersTest, listDynamoTablesTest}),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -881,12 +907,13 @@ func testAccStepReadArnPolicy(t *testing.T, name string, value string) logicalte
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := map[string]interface{}{
|
expected := map[string]interface{}{
|
||||||
"policy_arns": []string{value},
|
"policy_arns": []string{value},
|
||||||
"role_arns": []string(nil),
|
"role_arns": []string(nil),
|
||||||
"policy_document": "",
|
"policy_document": "",
|
||||||
"credential_types": []string{iamUserCred},
|
"credential_type": iamUserCred,
|
||||||
"default_sts_ttl": int64(0),
|
"default_sts_ttl": int64(0),
|
||||||
"max_sts_ttl": int64(0),
|
"max_sts_ttl": int64(0),
|
||||||
|
"user_path": "",
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(resp.Data, expected) {
|
if !reflect.DeepEqual(resp.Data, expected) {
|
||||||
return fmt.Errorf("bad: got: %#v\nexpected: %#v", resp.Data, expected)
|
return fmt.Errorf("bad: got: %#v\nexpected: %#v", resp.Data, expected)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -16,6 +17,10 @@ import (
|
||||||
"github.com/hashicorp/vault/sdk/logical"
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
userPathRegex = regexp.MustCompile(`^\/([\x21-\x7F]{0,510}\/)?$`)
|
||||||
|
)
|
||||||
|
|
||||||
func pathListRoles(b *backend) *framework.Path {
|
func pathListRoles(b *backend) *framework.Path {
|
||||||
return &framework.Path{
|
return &framework.Path{
|
||||||
Pattern: "roles/?$",
|
Pattern: "roles/?$",
|
||||||
|
@ -89,6 +94,13 @@ or IAM role to assume`,
|
||||||
Description: "Deprecated; use policy_document instead. IAM policy document",
|
Description: "Deprecated; use policy_document instead. IAM policy document",
|
||||||
Deprecated: true,
|
Deprecated: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"user_path": &framework.FieldSchema{
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "Path for IAM User. Only valid when credential_type is " + iamUserCred,
|
||||||
|
DisplayName: "User Path",
|
||||||
|
Default: "/",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||||
|
@ -245,6 +257,20 @@ func (b *backend) pathRolesWrite(ctx context.Context, req *logical.Request, d *f
|
||||||
roleEntry.MaxSTSTTL = time.Duration(maxSTSTTLRaw.(int)) * time.Second
|
roleEntry.MaxSTSTTL = time.Duration(maxSTSTTLRaw.(int)) * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if userPathRaw, ok := d.GetOk("user_path"); ok {
|
||||||
|
if legacyRole != "" {
|
||||||
|
return logical.ErrorResponse("cannot supply deprecated role or policy parameters with user_path"), nil
|
||||||
|
}
|
||||||
|
if !strutil.StrListContains(roleEntry.CredentialTypes, iamUserCred) {
|
||||||
|
return logical.ErrorResponse(fmt.Sprintf("user_path parameter only valid for %s credential type", iamUserCred)), nil
|
||||||
|
}
|
||||||
|
if !userPathRegex.MatchString(userPathRaw.(string)) {
|
||||||
|
return logical.ErrorResponse(fmt.Sprintf("The specified value for user_path is invalid. It must match '%s' regexp", userPathRegex.String())), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
roleEntry.UserPath = userPathRaw.(string)
|
||||||
|
}
|
||||||
|
|
||||||
if roleEntry.MaxSTSTTL > 0 &&
|
if roleEntry.MaxSTSTTL > 0 &&
|
||||||
roleEntry.DefaultSTSTTL > 0 &&
|
roleEntry.DefaultSTSTTL > 0 &&
|
||||||
roleEntry.DefaultSTSTTL > roleEntry.MaxSTSTTL {
|
roleEntry.DefaultSTSTTL > roleEntry.MaxSTSTTL {
|
||||||
|
@ -432,6 +458,7 @@ type awsRoleEntry struct {
|
||||||
Version int `json:"version"` // Version number of the role format
|
Version int `json:"version"` // Version number of the role format
|
||||||
DefaultSTSTTL time.Duration `json:"default_sts_ttl"` // Default TTL for STS credentials
|
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
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *awsRoleEntry) toResponseData() map[string]interface{} {
|
func (r *awsRoleEntry) toResponseData() map[string]interface{} {
|
||||||
|
@ -442,7 +469,9 @@ func (r *awsRoleEntry) toResponseData() map[string]interface{} {
|
||||||
"policy_document": r.PolicyDocument,
|
"policy_document": r.PolicyDocument,
|
||||||
"default_sts_ttl": int64(r.DefaultSTSTTL.Seconds()),
|
"default_sts_ttl": int64(r.DefaultSTSTTL.Seconds()),
|
||||||
"max_sts_ttl": int64(r.MaxSTSTTL.Seconds()),
|
"max_sts_ttl": int64(r.MaxSTSTTL.Seconds()),
|
||||||
|
"user_path": r.UserPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.InvalidData != "" {
|
if r.InvalidData != "" {
|
||||||
respData["invalid_data"] = r.InvalidData
|
respData["invalid_data"] = r.InvalidData
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/vault/sdk/logical"
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
|
@ -154,3 +155,61 @@ func TestUpgradeLegacyPolicyEntry(t *testing.T) {
|
||||||
t.Fatalf("bad: expected %#v; received %#v", expected, *output)
|
t.Fatalf("bad: expected %#v; received %#v", expected, *output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUserPathValidity(t *testing.T) {
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
description string
|
||||||
|
userPath string
|
||||||
|
isValid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "Default",
|
||||||
|
userPath: "/",
|
||||||
|
isValid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Empty",
|
||||||
|
userPath: "",
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Valid",
|
||||||
|
userPath: "/path/",
|
||||||
|
isValid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Missing leading slash",
|
||||||
|
userPath: "path/",
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Missing trailing slash",
|
||||||
|
userPath: "/path",
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Invalid character",
|
||||||
|
userPath: "/šiauliai/",
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Max length",
|
||||||
|
userPath: "/" + strings.Repeat("a", 510) + "/",
|
||||||
|
isValid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Too long",
|
||||||
|
userPath: "/" + strings.Repeat("a", 511) + "/",
|
||||||
|
isValid: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
if tc.isValid != userPathRegex.MatchString(tc.userPath) {
|
||||||
|
t.Fatalf("bad: expected %s", strconv.FormatBool(tc.isValid))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -180,9 +180,15 @@ func (b *backend) secretAccessKeysCreate(
|
||||||
return nil, errwrap.Wrapf("error writing WAL entry: {{err}}", err)
|
return nil, errwrap.Wrapf("error writing WAL entry: {{err}}", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userPath := role.UserPath
|
||||||
|
if userPath == "" {
|
||||||
|
userPath = "/"
|
||||||
|
}
|
||||||
|
|
||||||
// Create the user
|
// Create the user
|
||||||
_, err = iamClient.CreateUser(&iam.CreateUserInput{
|
_, err = iamClient.CreateUser(&iam.CreateUserInput{
|
||||||
UserName: aws.String(username),
|
UserName: aws.String(username),
|
||||||
|
Path: aws.String(userPath),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if walErr := framework.DeleteWAL(ctx, s, walID); walErr != nil {
|
if walErr := framework.DeleteWAL(ctx, s, walID); walErr != nil {
|
||||||
|
|
|
@ -230,6 +230,9 @@ updated with the new attributes.
|
||||||
TTL are capped to `max_sts_ttl`). Valid only when `credential_type` is one of
|
TTL are capped to `max_sts_ttl`). Valid only when `credential_type` is one of
|
||||||
`assumed_role` or `federation_token`.
|
`assumed_role` or `federation_token`.
|
||||||
|
|
||||||
|
- `user_path` `(string)` - The path for the user name. Valid only when
|
||||||
|
`credential_type` is `iam_user`. Default is `/`
|
||||||
|
|
||||||
Legacy parameters:
|
Legacy parameters:
|
||||||
|
|
||||||
These parameters are supported for backwards compatibility only. They cannot be
|
These parameters are supported for backwards compatibility only. They cannot be
|
||||||
|
|
Loading…
Reference in New Issue