Skip to content

Commit

Permalink
Pass Session Tags to credential validation check (#4862)
Browse files Browse the repository at this point in the history
The provider wasn't sending the session tags when validating
credentials. This caused perpetual credential check failures if users
used the tags for enforcing access. This change fixes that.

Fixes #4849
  • Loading branch information
flostadler authored Dec 3, 2024
1 parent d0ed212 commit bd51234
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 0 deletions.
11 changes: 11 additions & 0 deletions provider/provider_yaml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,17 @@ func TestAccDefaultTagsWithImport(t *testing.T) {
}
}

func TestAssumeRoleSessionTags(t *testing.T) {
t.Parallel()
ptest := pulumiTest(t, filepath.Join("test-programs", "assume-role-session-tags"), opttest.SkipInstall())
result := ptest.Up(t)
t.Logf("STDOUT: %v", result.StdOut)
t.Logf("STDERR: %v", result.StdErr)

require.Contains(t, result.Outputs, "bucketArn")
assert.NotEmpty(t, result.Outputs["bucketArn"].Value.(string))
}

// testTagsPulumiLifecycle tests the complete lifecycle of a pulumi program
// Scenarios that this tests:
// 1. `Up` with both provider `defaultTags`/`ignoreTags` and resource level `tags`
Expand Down
21 changes: 21 additions & 0 deletions provider/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,26 @@ func arrayValue(vars resource.PropertyMap, prop resource.PropertyKey, envs []str
return vals
}

func extractTags(vars resource.PropertyMap, prop resource.PropertyKey) map[string]string {
val, ok := vars[prop]

if !ok || !val.IsObject() {
return nil
}

tagProp := val.ObjectValue()
tags := make(map[string]string, len(tagProp))

for k, v := range tagProp {
if !v.IsString() {
continue
}
tags[string(k)] = v.StringValue()
}

return tags
}

// returns a pointer so we can distinguish between a zero value and a missing value
func durationFromConfig(vars resource.PropertyMap, prop resource.PropertyKey) (*time.Duration, error) {
val, ok := vars[prop]
Expand Down Expand Up @@ -630,6 +650,7 @@ func validateCredentials(vars resource.PropertyMap, c shim.ResourceConfig) error
SessionName: stringValue(details.ObjectValue(), "sessionName", []string{}),
SourceIdentity: stringValue(details.ObjectValue(), "sourceIdentity", []string{}),
TransitiveTagKeys: arrayValue(details.ObjectValue(), "transitiveTagKeys", []string{}),
Tags: extractTags(details.ObjectValue(), "tags"),
}
duration, err := durationFromConfig(details.ObjectValue(), "durationSeconds")
if err != nil {
Expand Down
65 changes: 65 additions & 0 deletions provider/resources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,68 @@ func TestHasOptionalOrRequiredNamePropertyOptimized(t *testing.T) {
}
}
}
func TestExtractTags(t *testing.T) {
tests := []struct {
name string
vars resource.PropertyMap
prop resource.PropertyKey
expected map[string]string
}{
{
name: "valid tags",
vars: resource.PropertyMap{
"tags": resource.NewObjectProperty(resource.PropertyMap{
"Name": resource.NewStringProperty("example"),
"Env": resource.NewStringProperty("production"),
}),
},
prop: "tags",
expected: map[string]string{
"Name": "example",
"Env": "production",
},
},
{
name: "no tags",
vars: resource.PropertyMap{
"tags": resource.NewObjectProperty(resource.PropertyMap{}),
},
prop: "tags",
expected: map[string]string{},
},
{
name: "non-string tags",
vars: resource.PropertyMap{
"tags": resource.NewObjectProperty(resource.PropertyMap{
"Name": resource.NewStringProperty("example"),
"Count": resource.NewNumberProperty(1),
}),
},
prop: "tags",
expected: map[string]string{
"Name": "example",
},
},
{
name: "missing tags property",
vars: resource.PropertyMap{},
prop: "tags",
expected: nil,
},
{
name: "tags property is not an object",
vars: resource.PropertyMap{
"tags": resource.NewStringProperty("not-an-object"),
},
prop: "tags",
expected: nil,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual := extractTags(tt.vars, tt.prop)
assert.Equal(t, tt.expected, actual)
})
}
}
75 changes: 75 additions & 0 deletions provider/test-programs/assume-role-session-tags/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: assume-role-session-tags
runtime: yaml

variables:
awsAccount:
fn::invoke:
function: aws:getCallerIdentity
currentRole:
fn::invoke:
function: aws:iam:getSessionContext
arguments:
arn: ${awsAccount.arn}

resources:
bootstrapProvider:
type: pulumi:providers:aws

iamRole:
type: aws:iam:Role
properties:
assumeRolePolicy:
fn::toJSON:
Version: "2012-10-17"
Statement:
- Action:
- "sts:AssumeRole"
- "sts:TagSession"
Effect: "Allow"
Principal:
AWS: ${currentRole.issuerArn}
Condition:
StringEquals:
"aws:RequestTag/Repository":
- "my-org/my-repo"
inlinePolicies:
- name: "inline-policy"
policy:
fn::toJSON:
Version: "2012-10-17"
Statement:
- Action:
- "s3:*"
Effect: "Allow"
Resource: "*"
options:
provider: ${bootstrapProvider}

# IAM has a delay in propagating the new role, so we need to wait for it to be available
# AWS is aiming for P99 below 2s so 6s should be enough
wait6s:
type: time:Sleep
properties:
createDuration: 6s

provider:
type: pulumi:providers:aws
properties:
assumeRole:
roleArn: ${iamRole.arn}
sessionName: "session-tagging-test"
tags:
Repository: "my-org/my-repo"
options:
dependsOn:
- ${wait6s}

myTestBucket:
type: aws:s3:Bucket
options:
provider: ${provider}

outputs:
bucketArn: ${myTestBucket.arn}
roleId: ${iamRole.id}
roleARN: ${iamRole.arn}

0 comments on commit bd51234

Please sign in to comment.