mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-14 18:47:01 +02:00
* Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License. Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUS-1.1 * Fix test that expected exact offset on hcl file --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Co-authored-by: Sarah Thompson <sthompson@hashicorp.com> Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
452 lines
14 KiB
Go
452 lines
14 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
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(config)
|
|
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(config)
|
|
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(config)
|
|
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) {
|
|
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) {
|
|
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) {
|
|
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)
|
|
}
|
|
}
|