From 97f2cdef254ebf357946971ec420e054fb058f3c Mon Sep 17 00:00:00 2001 From: kstich Date: Wed, 8 Nov 2023 15:13:25 -0800 Subject: [PATCH] Add disableConditionKeyInheritance to iamResource This commit adds a disableConditionKeyInheritance property to the iamResource trait, which can be set to true to decouple an IAM resource's condition keys from those of its parent resource(s). This can be combined with the conditionKeys trait to explicitly configure an IAM resource's condition keys. --- docs/source-2.0/aws/aws-iam.rst | 26 +++++++++++- .../aws/iam/traits/ConditionKeysIndex.java | 7 +++- .../aws/iam/traits/IamResourceTrait.java | 24 +++++++++-- .../resources/META-INF/smithy/aws.iam.smithy | 14 ++++--- .../iam/traits/ConditionKeysIndexTest.java | 5 +++ .../aws/iam/traits/IamResourceTraitTest.java | 2 + .../smithy/aws/iam/traits/iam-resource.smithy | 25 ++++++----- .../traits/successful-condition-keys.smithy | 42 +++++++++++++++---- 8 files changed, 117 insertions(+), 28 deletions(-) diff --git a/docs/source-2.0/aws/aws-iam.rst b/docs/source-2.0/aws/aws-iam.rst index a7e2ae28ed6..ee39923dbb8 100644 --- a/docs/source-2.0/aws/aws-iam.rst +++ b/docs/source-2.0/aws/aws-iam.rst @@ -100,6 +100,12 @@ members: - ``string`` - A relative URL path that defines more information about the resource within a set of IAM-related documentation. + * - disableConditionKeyInheritance + - ``boolean`` + - When set to ``true``, decouples this IAM resource's condition keys from + those of its parent resource(s). This can be used in combination with + the :ref:`aws.iam#conditionKeys-trait` trait to isolate a resource's + condition keys from those of its parent(s). The following example defines a simple resource with a name in AWS IAM that deviates from the :ref:`shape name of the shape ID ` of the resource. @@ -751,7 +757,7 @@ Given the following model, resource MyResource { identifiers: {foo: String} operations: [MyOperation] - resources: [MyInnerResource] + resources: [MyInnerResource, MyDetachedResource, MyCustomResource] } @iamResource(name: "InnerResource") @@ -759,6 +765,19 @@ Given the following model, identifiers: {yum: String} } + @disableConditionKeyInference + @iamResource(disableConditionKeyInheritance: true) + resource MyDetachedResource { + identifiers: {yum: String} + } + + @disableConditionKeyInference + @iamResource(disableConditionKeyInheritance: true) + @conditionKeys(["aws:region"]) + resource MyCustomResource { + identifiers: {yum: String} + } + @conditionKeys(["aws:region"]) operation MyOperation {} @@ -779,6 +798,11 @@ The computed condition keys for the service are: * ``myservice:MyResourceFoo`` * ``otherservice:Bar`` * ``myservice:InnerResourceYum`` + * - ``MyDetachedResource`` + - None + * - ``MyCustomResource`` + - + * ``aws:region`` * - ``MyOperation`` - * ``aws:region`` diff --git a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ConditionKeysIndex.java b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ConditionKeysIndex.java index af5c1ed4b5f..346616d513d 100644 --- a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ConditionKeysIndex.java +++ b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ConditionKeysIndex.java @@ -169,7 +169,12 @@ private void compute( ResourceShape parent, Set parentDefinitions ) { - Set definitions = new HashSet<>(parentDefinitions); + Set definitions = new HashSet<>(); + if (!subject.hasTrait(IamResourceTrait.ID) + || !subject.expectTrait(IamResourceTrait.class).isDisableConditionKeyInheritance() + ) { + definitions.addAll(parentDefinitions); + } resourceConditionKeys.get(service.getId()).put(subject.getId(), definitions); subject.getTrait(ConditionKeysTrait.class).ifPresent(trait -> definitions.addAll(trait.getValues())); diff --git a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamResourceTrait.java b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamResourceTrait.java index 2e3bed2fbbf..1787b320d95 100644 --- a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamResourceTrait.java +++ b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamResourceTrait.java @@ -32,13 +32,15 @@ public final class IamResourceTrait extends AbstractTrait implements ToSmithyBuilder { public static final ShapeId ID = ShapeId.from("aws.iam#iamResource"); - public final String name; - public final String relativeDocumentation; + private final String name; + private final String relativeDocumentation; + private final boolean disableConditionKeyInheritance; private IamResourceTrait(Builder builder) { super(ID, builder.getSourceLocation()); name = builder.name; relativeDocumentation = builder.relativeDocumentation; + disableConditionKeyInheritance = builder.disableConditionKeyInheritance; } /** @@ -47,7 +49,7 @@ private IamResourceTrait(Builder builder) { * @return Returns the name. */ public Optional getName() { - return Optional.of(name); + return Optional.ofNullable(name); } /** @@ -60,6 +62,16 @@ public Optional getRelativeDocumentation() { return Optional.ofNullable(relativeDocumentation); } + /** + * Gets if this IAM resource's condition keys are decoupled from + * those of its parent resource(s). + * + * @return Returns true if condition key inheritance is disabled. + */ + public boolean isDisableConditionKeyInheritance() { + return disableConditionKeyInheritance; + } + public static Builder builder() { return new Builder(); } @@ -93,6 +105,7 @@ public Trait createTrait(ShapeId target, Node value) { public static final class Builder extends AbstractTraitBuilder { private String name; private String relativeDocumentation; + private boolean disableConditionKeyInheritance; private Builder() {} @@ -110,5 +123,10 @@ public Builder relativeDocumentation(String relativeDocumentation) { this.relativeDocumentation = relativeDocumentation; return this; } + + public Builder disableConditionKeyInheritance(boolean disableConditionKeyInheritance) { + this.disableConditionKeyInheritance = disableConditionKeyInheritance; + return this; + } } } diff --git a/smithy-aws-iam-traits/src/main/resources/META-INF/smithy/aws.iam.smithy b/smithy-aws-iam-traits/src/main/resources/META-INF/smithy/aws.iam.smithy index 47fd142938e..15702860215 100644 --- a/smithy-aws-iam-traits/src/main/resources/META-INF/smithy/aws.iam.smithy +++ b/smithy-aws-iam-traits/src/main/resources/META-INF/smithy/aws.iam.smithy @@ -25,7 +25,7 @@ list conditionKeys { string conditionKeyValue /// Defines the set of condition keys that appear within a service in addition to -/// inferred and global condition keys. +/// inferred and global condition keys. @trait(selector: "service") map defineConditionKeys { key: IamIdentifier @@ -65,9 +65,13 @@ structure iamResource { /// The name of the resource in AWS IAM. name: String - /// A relative URL path that defines more information about the resource - /// within a set of IAM-related documentation. + /// A relative URL path that defines more information about the resource + /// within a set of IAM-related documentation. relativeDocumentation: String + + /// When set to `true`, decouples this IAM resource's condition keys from + /// those of its parent resource(s). + disableConditionKeyInheritance: Boolean } /// Other actions that the invoker must be authorized to perform when executing the targeted operation. @@ -112,8 +116,8 @@ structure ConditionKeyDefinition { /// A valid URL that defines more information about the condition key. externalDocumentation: String - /// A relative URL path that defines more information about the condition key - /// within a set of IAM-related documentation. + /// A relative URL path that defines more information about the condition key + /// within a set of IAM-related documentation. relativeDocumentation: String } diff --git a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ConditionKeysIndexTest.java b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ConditionKeysIndexTest.java index e470547809e..d5e7f9c8bc9 100644 --- a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ConditionKeysIndexTest.java +++ b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ConditionKeysIndexTest.java @@ -49,6 +49,11 @@ public void successfullyLoadsConditionKeys() { assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource2")), containsInAnyOrder("aws:accountId", "foo:baz", "myservice:Resource1Id1", "myservice:ResourceTwoId2")); + // This resource has inheritance disabled. + assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource3")), empty()); + // This resource has inheritance disabled and an explicit list provided. + assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource4")), + contains("foo:baz")); // Note that while this operation binds identifiers, it contains no unique ConditionKeys to bind. assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#GetResource2")), is(empty())); diff --git a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/IamResourceTraitTest.java b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/IamResourceTraitTest.java index eb7bc183769..dc03eb967f1 100644 --- a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/IamResourceTraitTest.java +++ b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/IamResourceTraitTest.java @@ -16,6 +16,7 @@ package software.amazon.smithy.aws.iam.traits; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; @@ -37,5 +38,6 @@ public void loadsFromModel() { assertTrue(superResource.hasTrait(IamResourceTrait.class)); assertEquals(superResource.expectTrait(IamResourceTrait.class).getName().get(), "super"); assertEquals(superResource.expectTrait(IamResourceTrait.class).getRelativeDocumentation().get(), "API-Super.html"); + assertFalse(superResource.expectTrait(IamResourceTrait.class).isDisableConditionKeyInheritance()); } } diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/iam-resource.smithy b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/iam-resource.smithy index 55fc02b9ae6..627e7260278 100644 --- a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/iam-resource.smithy +++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/iam-resource.smithy @@ -1,31 +1,34 @@ -$version: "1.0" +$version: "2.0" namespace smithy.example use aws.api#arn +use aws.api#service +use aws.iam#iamResource -@aws.api#service(sdkId: "My") +@service(sdkId: "My") service MyService { - version: "2020-07-02", + version: "2020-07-02" resources: [SuperResource] } -@aws.iam#iamResource( - name: "super", +@iamResource( + name: "super" relativeDocumentation: "API-Super.html" + disableConditionKeyInheritance: false ) @arn(template: "super/{id1}") resource SuperResource { identifiers: { - id1: String, - }, + id1: String + } read: GetResource } @readonly operation GetResource { - input: GetResourceInput, - output: GetResourceOutput, + input: GetResourceInput + output: GetResourceOutput } structure GetResourceInput { @@ -34,9 +37,9 @@ structure GetResourceInput { } structure GetResourceOutput { - super: Super, + super: Super } structure Super { - id1: String, + id1: String } diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/successful-condition-keys.smithy b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/successful-condition-keys.smithy index a7c0999c086..f6a209a0565 100644 --- a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/successful-condition-keys.smithy +++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/successful-condition-keys.smithy @@ -1,8 +1,15 @@ $version: "1.0" namespace smithy.example -@aws.api#service(sdkId: "My") -@aws.iam#defineConditionKeys( +use aws.api#arnReference +use aws.api#service +use aws.iam#conditionKeys +use aws.iam#defineConditionKeys +use aws.iam#disableConditionKeyInference +use aws.iam#iamResource + +@service(sdkId: "My") +@defineConditionKeys( "foo:baz": { type: "String", documentation: "Foo baz", @@ -15,18 +22,18 @@ service MyService { resources: [Resource1] } -@aws.iam#conditionKeys(["aws:accountId", "foo:baz"]) +@conditionKeys(["aws:accountId", "foo:baz"]) operation Operation1 {} -@aws.iam#conditionKeys(["aws:accountId", "foo:baz"]) +@conditionKeys(["aws:accountId", "foo:baz"]) resource Resource1 { identifiers: { id1: ArnString, }, - resources: [Resource2] + resources: [Resource2, Resource3, Resource4] } -@aws.iam#iamResource(name: "ResourceTwo") +@iamResource(name: "ResourceTwo") resource Resource2 { identifiers: { id1: ArnString, @@ -36,6 +43,27 @@ resource Resource2 { list: ListResource2, } +@disableConditionKeyInference +@iamResource(disableConditionKeyInheritance: true) +resource Resource3 { + identifiers: { + id1: ArnString + id2: FooString + id3: String + } +} + +@disableConditionKeyInference +@iamResource(disableConditionKeyInheritance: true) +@conditionKeys(["foo:baz"]) +resource Resource4 { + identifiers: { + id1: ArnString + id2: FooString + id4: String + } +} + @readonly operation GetResource2 { input: GetResource2Input @@ -65,5 +93,5 @@ structure ListResource2Input { structure ListResource2Output {} -@aws.api#arnReference(type: "ec2:Instance") +@arnReference(type: "ec2:Instance") string ArnString