open-vault/builtin/logical/aws/path_roles_test.go
Joel Thompson 551b7a5e5c secret/aws: Support permissions boundaries on iam_user creds (#6786)
* secrets/aws: Support permissions boundaries on iam_user creds

This allows configuring Vault to attach a permissions boundary policy to
IAM users that it creates, configured on a per-Vault-role basis.

* Fix indentation of policy in docs

Use spaces instead of tabs
2019-09-19 16:35:12 -07:00

451 lines
14 KiB
Go

package aws
import (
"context"
"reflect"
"strconv"
"strings"
"testing"
"github.com/hashicorp/vault/sdk/logical"
)
const adminAccessPolicyARN = "arn:aws:iam::aws:policy/AdministratorAccess"
func TestBackend_PathListRoles(t *testing.T) {
var resp *logical.Response
var err error
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
b := Backend()
if err := b.Setup(context.Background(), config); err != nil {
t.Fatal(err)
}
roleData := map[string]interface{}{
"role_arns": []string{"arn:aws:iam::123456789012:role/path/RoleName"},
"credential_type": assumedRoleCred,
"default_sts_ttl": 3600,
"max_sts_ttl": 3600,
}
roleReq := &logical.Request{
Operation: logical.UpdateOperation,
Storage: config.StorageView,
Data: roleData,
}
for i := 1; i <= 10; i++ {
roleReq.Path = "roles/testrole" + strconv.Itoa(i)
resp, err = b.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: role creation failed. resp:%#v\n err:%v", resp, err)
}
}
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.ListOperation,
Path: "roles",
Storage: config.StorageView,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: listing roles failed. resp:%#v\n err:%v", resp, err)
}
if len(resp.Data["keys"].([]string)) != 10 {
t.Fatalf("failed to list all 10 roles")
}
resp, err = b.HandleRequest(context.Background(), &logical.Request{
Operation: logical.ListOperation,
Path: "roles/",
Storage: config.StorageView,
})
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: listing roles failed. resp:%#v\n err:%v", resp, err)
}
if len(resp.Data["keys"].([]string)) != 10 {
t.Fatalf("failed to list all 10 roles")
}
}
func TestUpgradeLegacyPolicyEntry(t *testing.T) {
var input string
var expected awsRoleEntry
var output *awsRoleEntry
input = "arn:aws:iam::123456789012:role/path/RoleName"
expected = awsRoleEntry{
CredentialTypes: []string{assumedRoleCred},
RoleArns: []string{input},
ProhibitFlexibleCredPath: true,
Version: 1,
}
output = upgradeLegacyPolicyEntry(input)
if output.InvalidData != "" {
t.Fatalf("bad: error processing upgrade of %q: got invalid data of %v", input, output.InvalidData)
}
if !reflect.DeepEqual(*output, expected) {
t.Fatalf("bad: expected %#v; received %#v", expected, *output)
}
input = "arn:aws:iam::123456789012:policy/MyPolicy"
expected = awsRoleEntry{
CredentialTypes: []string{iamUserCred},
PolicyArns: []string{input},
ProhibitFlexibleCredPath: true,
Version: 1,
}
output = upgradeLegacyPolicyEntry(input)
if output.InvalidData != "" {
t.Fatalf("bad: error processing upgrade of %q: got invalid data of %v", input, output.InvalidData)
}
if !reflect.DeepEqual(*output, expected) {
t.Fatalf("bad: expected %#v; received %#v", expected, *output)
}
input = "arn:aws:iam::aws:policy/AWSManagedPolicy"
expected.PolicyArns = []string{input}
output = upgradeLegacyPolicyEntry(input)
if output.InvalidData != "" {
t.Fatalf("bad: error processing upgrade of %q: got invalid data of %v", input, output.InvalidData)
}
if !reflect.DeepEqual(*output, expected) {
t.Fatalf("bad: expected %#v; received %#v", expected, *output)
}
input = `
{
"Version": "2012-10-07",
"Statement": [
{
"Effect": "Allow",
"Action": "ec2:Describe*",
"Resource": "*"
}
]
}`
compacted, err := compactJSON(input)
if err != nil {
t.Fatalf("error parsing JSON: %v", err)
}
expected = awsRoleEntry{
CredentialTypes: []string{iamUserCred, federationTokenCred},
PolicyDocument: compacted,
ProhibitFlexibleCredPath: true,
Version: 1,
}
output = upgradeLegacyPolicyEntry(input)
if output.InvalidData != "" {
t.Fatalf("bad: error processing upgrade of %q: got invalid data of %v", input, output.InvalidData)
}
if !reflect.DeepEqual(*output, expected) {
t.Fatalf("bad: expected %#v; received %#v", expected, *output)
}
// Due to lack of prior input validation, this could exist in the storage, and we need
// to be able to read it out in some fashion, so have to handle this in a poor fashion
input = "arn:gobbledygook"
expected = awsRoleEntry{
InvalidData: input,
Version: 1,
}
output = upgradeLegacyPolicyEntry(input)
if !reflect.DeepEqual(*output, expected) {
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))
}
})
}
}
func TestRoleCRUDWithPermissionsBoundary(t *testing.T) {
roleName := "test_perm_boundary"
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
b := Backend()
if err := b.Setup(context.Background(), config); err != nil {
t.Fatal(err)
}
permissionsBoundaryARN := "arn:aws:iam::aws:policy/EC2FullAccess"
roleData := map[string]interface{}{
"credential_type": iamUserCred,
"policy_arns": []string{adminAccessPolicyARN},
"permissions_boundary_arn": permissionsBoundaryARN,
}
request := &logical.Request{
Operation: logical.UpdateOperation,
Path: "roles/" + roleName,
Storage: config.StorageView,
Data: roleData,
}
resp, err := b.HandleRequest(context.Background(), request)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: role creation failed. resp:%#v\nerr:%v", resp, err)
}
request = &logical.Request{
Operation: logical.ReadOperation,
Path: "roles/" + roleName,
Storage: config.StorageView,
}
resp, err = b.HandleRequest(context.Background(), request)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: reading role failed. resp:%#v\nerr:%v", resp, err)
}
if resp.Data["credential_type"] != iamUserCred {
t.Errorf("bad: expected credential_type of %s, got %s instead", iamUserCred, resp.Data["credential_type"])
}
if resp.Data["permissions_boundary_arn"] != permissionsBoundaryARN {
t.Errorf("bad: expected permissions_boundary_arn of %s, got %s instead", permissionsBoundaryARN, resp.Data["permissions_boundary_arn"])
}
}
func TestRoleWithPermissionsBoundaryValidation(t *testing.T) {
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
b := Backend()
if err := b.Setup(context.Background(), config); err != nil {
t.Fatal(err)
}
roleData := map[string]interface{}{
"credential_type": assumedRoleCred, // only iamUserCred supported with permissions_boundary_arn
"role_arns": []string{"arn:aws:iam::123456789012:role/VaultRole"},
"permissions_boundary_arn": "arn:aws:iam::aws:policy/FooBar",
}
request := &logical.Request{
Operation: logical.UpdateOperation,
Path: "roles/test_perm_boundary",
Storage: config.StorageView,
Data: roleData,
}
resp, err := b.HandleRequest(context.Background(), request)
if err == nil && (resp == nil || !resp.IsError()) {
t.Fatalf("bad: expected role creation to fail due to bad credential_type, but it didn't. resp:%#v\nerr:%v", resp, err)
}
roleData = map[string]interface{}{
"credential_type": iamUserCred,
"policy_arns": []string{adminAccessPolicyARN},
"permissions_boundary_arn": "arn:aws:notiam::aws:policy/FooBar",
}
request.Data = roleData
resp, err = b.HandleRequest(context.Background(), request)
if err == nil && (resp == nil || !resp.IsError()) {
t.Fatalf("bad: expected role creation to fail due to malformed permissions_boundary_arn, but it didn't. resp:%#v\nerr:%v", resp, err)
}
}
func TestValidateAWSManagedPolicy(t *testing.T) {
expectErr := func(arn string) {
err := validateAWSManagedPolicy(arn)
if err == nil {
t.Errorf("bad: expected arn of %s to return an error but it didn't", arn)
}
}
expectErr("not_an_arn")
expectErr("notarn:aws:iam::aws:policy/FooBar")
expectErr("arn:aws:notiam::aws:policy/FooBar")
expectErr("arn:aws:iam::aws:notpolicy/FooBar")
expectErr("arn:aws:iam::aws:policynot/FooBar")
arn := "arn:aws:iam::aws:policy/FooBar"
err := validateAWSManagedPolicy(arn)
if err != nil {
t.Errorf("bad: expected arn of %s to not return an error but it did: %#v", arn, err)
}
}
func TestRoleEntryValidationCredTypes(t *testing.T) {
roleEntry := awsRoleEntry{
CredentialTypes: []string{},
PolicyArns: []string{adminAccessPolicyARN},
}
if roleEntry.validate() == nil {
t.Errorf("bad: invalid roleEntry with no CredentialTypes %#v passed validation", roleEntry)
}
roleEntry.CredentialTypes = []string{"invalid_type"}
if roleEntry.validate() == nil {
t.Errorf("bad: invalid roleEntry with invalid CredentialTypes %#v passed validation", roleEntry)
}
roleEntry.CredentialTypes = []string{iamUserCred, "invalid_type"}
if roleEntry.validate() == nil {
t.Errorf("bad: invalid roleEntry with invalid CredentialTypes %#v passed validation", roleEntry)
}
}
func TestRoleEntryValidationIamUserCred(t *testing.T) {
var allowAllPolicyDocument = `{"Version": "2012-10-17", "Statement": [{"Sid": "AllowAll", "Effect": "Allow", "Action": "*", "Resource": "*"}]}`
roleEntry := awsRoleEntry{
CredentialTypes: []string{iamUserCred},
PolicyArns: []string{adminAccessPolicyARN},
PermissionsBoundaryARN: adminAccessPolicyARN,
}
err := roleEntry.validate()
if err != nil {
t.Errorf("bad: valid roleEntry %#v failed validation: %v", roleEntry, err)
}
roleEntry.PolicyDocument = allowAllPolicyDocument
err = roleEntry.validate()
if err != nil {
t.Errorf("bad: valid roleEntry %#v failed validation: %v", roleEntry, err)
}
roleEntry.PolicyArns = []string{}
err = roleEntry.validate()
if err != nil {
t.Errorf("bad: valid roleEntry %#v failed validation: %v", roleEntry, err)
}
roleEntry = awsRoleEntry{
CredentialTypes: []string{iamUserCred},
RoleArns: []string{"arn:aws:iam::123456789012:role/SomeRole"},
}
if roleEntry.validate() == nil {
t.Errorf("bad: invalid roleEntry with invalid RoleArns parameter %#v passed validation", roleEntry)
}
roleEntry = awsRoleEntry{
CredentialTypes: []string{iamUserCred},
PolicyArns: []string{adminAccessPolicyARN},
DefaultSTSTTL: 1,
}
if roleEntry.validate() == nil {
t.Errorf("bad: invalid roleEntry with unrecognized DefaultSTSTTL %#v passed validation", roleEntry)
}
roleEntry.DefaultSTSTTL = 0
roleEntry.MaxSTSTTL = 1
if roleEntry.validate() == nil {
t.Errorf("bad: invalid roleEntry with unrecognized MaxSTSTTL %#v passed validation", roleEntry)
}
}
func TestRoleEntryValidationAssumedRoleCred(t *testing.T) {
var allowAllPolicyDocument = `{"Version": "2012-10-17", "Statement": [{"Sid": "AllowAll", "Effect": "Allow", "Action": "*", "Resource": "*"}]}`
roleEntry := awsRoleEntry{
CredentialTypes: []string{assumedRoleCred},
RoleArns: []string{"arn:aws:iam::123456789012:role/SomeRole"},
PolicyArns: []string{adminAccessPolicyARN},
PolicyDocument: allowAllPolicyDocument,
DefaultSTSTTL: 2,
MaxSTSTTL: 3,
}
if err := roleEntry.validate(); err != nil {
t.Errorf("bad: valid roleEntry %#v failed validation: %v", roleEntry, err)
}
roleEntry.MaxSTSTTL = 1
if roleEntry.validate() == nil {
t.Errorf("bad: invalid roleEntry with MaxSTSTTL < DefaultSTSTTL %#v passed validation", roleEntry)
}
roleEntry.MaxSTSTTL = 0
roleEntry.UserPath = "/foobar/"
if roleEntry.validate() == nil {
t.Errorf("bad: invalid roleEntry with unrecognized UserPath %#v passed validation", roleEntry)
}
roleEntry.UserPath = ""
roleEntry.PermissionsBoundaryARN = adminAccessPolicyARN
if roleEntry.validate() == nil {
t.Errorf("bad: invalid roleEntry with unrecognized PermissionsBoundary %#v passed validation", roleEntry)
}
}
func TestRoleEntryValidationFederationTokenCred(t *testing.T) {
var allowAllPolicyDocument = `{"Version": "2012-10-17", "Statement": [{"Sid": "AllowAll", "Effect": "Allow", "Action": "*", "Resource": "*"}]}`
roleEntry := awsRoleEntry{
CredentialTypes: []string{federationTokenCred},
PolicyDocument: allowAllPolicyDocument,
PolicyArns: []string{adminAccessPolicyARN},
DefaultSTSTTL: 2,
MaxSTSTTL: 3,
}
if err := roleEntry.validate(); err != nil {
t.Errorf("bad: valid roleEntry %#v failed validation: %v", roleEntry, err)
}
roleEntry.RoleArns = []string{"arn:aws:iam::123456789012:role/SomeRole"}
if roleEntry.validate() == nil {
t.Errorf("bad: invalid roleEntry with unrecognized RoleArns %#v passed validation", roleEntry)
}
roleEntry.RoleArns = []string{}
roleEntry.UserPath = "/foobar/"
if roleEntry.validate() == nil {
t.Errorf("bad: invalid roleEntry with unrecognized UserPath %#v passed validation", roleEntry)
}
roleEntry.UserPath = ""
roleEntry.MaxSTSTTL = 1
if roleEntry.validate() == nil {
t.Errorf("bad: invalid roleEntry with MaxSTSTTL < DefaultSTSTTL %#v passed validation", roleEntry)
}
roleEntry.MaxSTSTTL = 0
roleEntry.PermissionsBoundaryARN = adminAccessPolicyARN
if roleEntry.validate() == nil {
t.Errorf("bad: invalid roleEntry with unrecognized PermissionsBoundary %#v passed validation", roleEntry)
}
}