Skip to content

Commit

Permalink
Enable aws.iam#disableConditionKeyInference trait for service shape
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewFossAWS committed Oct 30, 2023
1 parent 56f6463 commit 416da8f
Show file tree
Hide file tree
Showing 6 changed files with 239 additions and 15 deletions.
39 changes: 38 additions & 1 deletion docs/source-2.0/aws/aws-iam.rst
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,47 @@ Condition keys in IAM policies can be evaluated with `condition operators`_.
Summary
Declares that the condition keys of a resource should not be inferred.
Trait selector
``resource``
``:test(service, resource)``
Value type
Annotation trait

When a service is marked with the ``aws.iam#disableConditionKeyInference``
trait, all the resources bound to the service will not have condition
keys automatically inferred from its identifiers and the identifiers
of its ancestors.

The following example shows resources ``MyResource1`` and ``MyResource2``
have had condition key inference disabled because they are bound to a
service marked with ``aws.iam#disableConditionKeyInference`` trait.

.. code-block:: smithy
$version: "2"
namespace smithy.example
use aws.api#service
use aws.iam#disableConditionKeyInference
@service(sdkId: "My Value", arnNamespace: "myservice")
@disableConditionKeyInference
service MyService {
version: "2017-02-11"
resources: [MyResource1, MyResource2]
}
resource MyResource1 {
identifiers: {
foo: String
}
}
resource MyResource2 {
identifiers: {
foo: String
}
}
A resource marked with the ``aws.iam#disableConditionKeyInference`` trait will
not have its condition keys automatically inferred from its identifiers and
the identifiers of its ancestors (if present.)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,14 @@ public ConditionKeysIndex(Model model) {
service.getResources().stream()
.flatMap(id -> OptionalUtils.stream(model.getShape(id)))
.forEach(resource -> {
compute(model, service.getId(), arnRoot, resource, null);
compute(model, service, arnRoot, resource, null);
});

// Compute the keys of operations of the service.
service.getOperations().stream()
.flatMap(id -> OptionalUtils.stream(model.getShape(id)))
.forEach(operation -> {
compute(model, service.getId(), arnRoot, operation, null);
compute(model, service, arnRoot, operation, null);
});
});
});
Expand Down Expand Up @@ -153,7 +153,7 @@ public Map<String, ConditionKeyDefinition> getDefinedConditionKeys(

private void compute(
Model model,
ShapeId service,
ServiceShape service,
String arnRoot,
Shape subject,
ResourceShape parent
Expand All @@ -163,21 +163,24 @@ private void compute(

private void compute(
Model model,
ShapeId service,
ServiceShape service,
String arnRoot,
Shape subject,
ResourceShape parent,
Set<String> parentDefinitions
) {
Set<String> definitions = new HashSet<>(parentDefinitions);
resourceConditionKeys.get(service).put(subject.getId(), definitions);
resourceConditionKeys.get(service.getId()).put(subject.getId(), definitions);
subject.getTrait(ConditionKeysTrait.class).ifPresent(trait -> definitions.addAll(trait.getValues()));

// Continue recursing into resources and computing keys.
subject.asResourceShape().ifPresent(resource -> {
boolean disableConditionKeyInference = resource.hasTrait(DisableConditionKeyInferenceTrait.class)
|| service.hasTrait(DisableConditionKeyInferenceTrait.class);

// Add any inferred resource identifiers to the resource and to the service-wide definitions.
Map<String, String> childIdentifiers = !resource.hasTrait(DisableConditionKeyInferenceTrait.class)
? inferChildResourceIdentifiers(model, service, arnRoot, resource, parent)
Map<String, String> childIdentifiers = !disableConditionKeyInference
? inferChildResourceIdentifiers(model, service.getId(), arnRoot, resource, parent)
: MapUtils.of();

// Compute the keys of each child operation, passing no keys.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@
"type": "structure",
"traits": {
"smithy.api#trait": {
"selector": "resource"
"selector": ":test(service, resource)"
},
"smithy.api#documentation": "Disables the automatic inference of condition keys of a resource."
"smithy.api#documentation": "Disables the automatic inference of condition keys of service's resources or a specific resource."
}
},
"aws.iam#requiredActions": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,11 @@
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.Optional;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.validation.Severity;
import software.amazon.smithy.model.validation.ValidatedResult;
import software.amazon.smithy.model.validation.ValidationEvent;

public class ConditionKeysIndexTest {
@Test
Expand Down Expand Up @@ -65,4 +60,67 @@ public void successfullyLoadsConditionKeys() {
assertThat(index.getDefinedConditionKeys(service, ShapeId.from("smithy.example#GetResource2")).keySet(),
is(empty()));
}

@Test
public void disableConditionKeyInferenceForResources() {
Model model = Model.assembler()
.addImport(getClass().getResource("disable-condition-key-inference-for-resources.smithy"))
.discoverModels(getClass().getClassLoader())
.assemble()
.unwrap();

ShapeId service = ShapeId.from("smithy.example#MyService");

ConditionKeysIndex index = ConditionKeysIndex.of(model);

assertThat(index.getConditionKeyNames(service),
containsInAnyOrder("my:service", "aws:operation1", "resource:1", "myservice:Resource1Id1"));

// Verify inference key myservice:Resource2Id2 does not exist
assertThat(index.getConditionKeyNames(service), not(contains("myservice:Resource2Id2")));

assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource1")),
containsInAnyOrder("resource:1", "myservice:Resource1Id1"));

assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource1")),
not(contains("myservice:Resource2Id2")));

assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource2")),
containsInAnyOrder("resource:1", "myservice:Resource1Id1"));

assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource2")),
not(contains("myservice:Resource2Id2")));
}

@Test
public void disableConditionKeyInferenceForService() {
Model model = Model.assembler()
.addImport(getClass().getResource("disable-condition-key-inference-for-service.smithy"))
.discoverModels(getClass().getClassLoader())
.assemble()
.unwrap();

ShapeId service = ShapeId.from("smithy.example#MyService");

ConditionKeysIndex index = ConditionKeysIndex.of(model);

assertThat(index.getConditionKeyNames(service),
containsInAnyOrder("my:service", "aws:operation1", "resource:1"));

// Verify inference key myservice:Resource1Id1 AND myservice:Resource2Id2 do not exist
assertThat(index.getConditionKeyNames(service),
not(contains("myservice:Resource1Id1", "myservice:Resource2Id2")));

assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource1")),
not(contains("myservice:Resource1Id1", "myservice:Resource2Id2")));

assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource1")),
contains("resource:1"));

assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource2")),
contains("resource:1"));

assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource2")),
not(contains("myservice:Resource1Id1", "myservice:Resource2Id2")));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
$version: "1.0"
namespace smithy.example

@aws.api#service(sdkId: "My")
@aws.iam#defineConditionKeys("my:service": {type: "String", documentation: "Foo baz"})
service MyService {
version: "2019-02-20",
operations: [Operation1],
resources: [Resource1]
}

@aws.iam#conditionKeys(["aws:operation1", "my:service"])
operation Operation1 {}

@aws.iam#conditionKeys(["resource:1"])
resource Resource1 {
identifiers: {
id1: ArnString,
},
resources: [Resource2]
}

@aws.iam#disableConditionKeyInference
resource Resource2 {
identifiers: {
id1: ArnString,
id2: FooString,
},
read: GetResource2,
list: ListResource2,
}

@readonly
operation GetResource2 {
input: GetResource2Input
}

structure GetResource2Input {
@required
id1: ArnString,

@required
id2: FooString
}

@documentation("This is Foo")
string FooString

@readonly
operation ListResource2 {
input: ListResource2Input,
output: ListResource2Output
}

structure ListResource2Input {
@required
id1: ArnString,
}

structure ListResource2Output {}

@aws.api#arnReference(type: "ec2:Instance")
string ArnString
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
$version: "1.0"
namespace smithy.example

@aws.api#service(sdkId: "My")
@aws.iam#defineConditionKeys("my:service": {type: "String", documentation: "Foo baz"})
@aws.iam#disableConditionKeyInference
service MyService {
version: "2019-02-20",
operations: [Operation1],
resources: [Resource1]
}

@aws.iam#conditionKeys(["aws:operation1", "my:service"])
operation Operation1 {}

@aws.iam#conditionKeys(["resource:1"])
resource Resource1 {
identifiers: {
id1: ArnString,
},
resources: [Resource2]
}

resource Resource2 {
identifiers: {
id1: ArnString,
id2: FooString,
},
read: GetResource2,
list: ListResource2,
}

@readonly
operation GetResource2 {
input: GetResource2Input
}

structure GetResource2Input {
@required
id1: ArnString,

@required
id2: FooString
}

@documentation("This is Foo")
string FooString

@readonly
operation ListResource2 {
input: ListResource2Input,
output: ListResource2Output
}

structure ListResource2Input {
@required
id1: ArnString,
}

structure ListResource2Output {}

@aws.api#arnReference(type: "ec2:Instance")
string ArnString

0 comments on commit 416da8f

Please sign in to comment.