Skip to content

Commit

Permalink
Add generation for CFN handler permissions
Browse files Browse the repository at this point in the history
This commit introduces automatic generation for handler permissions
based on the lifecycle operation used and permissions listed in the
@aws.iam#requiredActions trait on that operation.
  • Loading branch information
kstich committed Oct 15, 2021
1 parent 4a2773d commit e0bcdd0
Show file tree
Hide file tree
Showing 22 changed files with 517 additions and 11 deletions.
75 changes: 73 additions & 2 deletions docs/source/1.0/guides/generating-cloudformation-resources.rst
Original file line number Diff line number Diff line change
Expand Up @@ -289,12 +289,79 @@ jsonAdd (``Map<String, Map<String, Node>>``)
}
}
.. _generate-cloudformation-setting-disableHandlerPermissionGeneration:

disableHandlerPermissionGeneration (``boolean``)
Sets whether to disable generating ``handler`` ``permission`` lists for
Resource Schemas. By default, handler permissions lists are automatically
added to schemas based on :ref:`lifecycle-operations` and permissions
listed in the :ref:`aws.iam#requiredActions-trait` on the operation. See
`the handlers section`_ in the CloudFormation Resource Schemas
documentation for more information.

.. code-block:: json
{
"version": "1.0",
"plugins": {
"cloudformation": {
"service": "smithy.example#Queues",
"organizationName": "Smithy",
"disableHandlerPermissionGeneration": true
}
}
}
CloudFormation Resource Schema handlers determine what provisioning actions
can be performed for the resource. The handlers utilized by CloudFormation
align with some :ref:`lifecycle-operations`. These operations can also
define other permission actions required to invoke them with the :ref:`aws.iam#requiredActions-trait`.

When handler permission generation is enabled, all the actions required to
invoke the operations related to the handler, including the actions for the
operations themselves, are used to populate permission lists:

.. code-block:: json
"handlers": {
"create": {
"permissions": [
"dependency:GetDependencyComponent",
"queues:CreateQueue"
]
},
"read": {
"permissions": [
"queues:GetQueue"
]
},
"update": {
"permissions": [
"dependency:GetDependencyComponent",
"queues:UpdateQueue"
]
},
"delete": {
"permissions": [
"queues:DeleteQueue"
]
},
"list": {
"permissions": [
"queues:ListQueues"
]
}
},
.. _generate-cloudformation-setting-disableDeprecatedPropertyGeneration:

disableDeprecatedPropertyGeneration (``boolean``)
Sets whether to disable generating ``deprecatedProperties`` for Resource
Schemas. By default, deprecated members are automatically added to the
``deprecatedProperties`` schema property.
``deprecatedProperties`` schema property. See `the deprecatedProperties
section`_ in the CloudFormation Resource Schemas documentation for more
information.

.. code-block:: json
Expand All @@ -314,7 +381,8 @@ disableDeprecatedPropertyGeneration (``boolean``)
disableRequiredPropertyGeneration (``boolean``)
Sets whether to disable generating ``required`` for Resource Schemas. By
default, required members are automatically added to the ``required``
schema property.
schema property. See `the required property section`_ in the CloudFormation
Resource Schemas documentation for more information.

.. code-block:: json
Expand Down Expand Up @@ -500,3 +568,6 @@ service providers. See the `Javadocs`_ for more information.
.. _Smithy Gradle plugin: https://github.com/awslabs/smithy-gradle-plugin
.. _type name: https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-schema.html#schema-properties-typeName
.. _Javadocs: https://awslabs.github.io/smithy/javadoc/__smithy_version__/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/Smithy2CfnExtension.html
.. _the handlers section: https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-schema.html#schema-properties-handlers
.. _the deprecatedProperties section: https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-schema.html#schema-properties-deprecatedproperties
.. _the required property section: https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-schema.html#schema-properties-required
1 change: 1 addition & 0 deletions smithy-aws-cloudformation/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dependencies {
api project(":smithy-build")
api project(":smithy-jsonschema")
api project(":smithy-aws-cloudformation-traits")
api project(":smithy-aws-iam-traits")
api project(":smithy-aws-traits")

// For use in validating schemas used in tests against the supplied
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public final class CfnConfig extends JsonSchemaConfig {
/** The JSON pointer to where CloudFormation schema shared resource properties should be written. */
public static final String SCHEMA_COMPONENTS_POINTER = "#/definitions";

private boolean disableHandlerPermissionGeneration = false;
private boolean disableDeprecatedPropertyGeneration = false;
private boolean disableRequiredPropertyGeneration = false;
private boolean disableCapitalizedProperties = false;
Expand Down Expand Up @@ -89,6 +90,25 @@ public void setAlphanumericOnlyRefs(boolean alphanumericOnlyRefs) {
}
}

public boolean getDisableHandlerPermissionGeneration() {
return disableHandlerPermissionGeneration;
}

/**
* Set to true to disable generating {@code handler} property's {@code permissions}
* lists for Resource Schemas.
*
* <p>By default, handler permissions are automatically added to the {@code handler}
* property's {@code permissions} list. This includes the lifecycle operation used
* and any permissions listed in the {@code aws.iam#requiredActions} trait.
*
* @param disableHandlerPermissionGeneration True to disable handler {@code permissions}
* generation
*/
public void setDisableHandlerPermissionGeneration(boolean disableHandlerPermissionGeneration) {
this.disableHandlerPermissionGeneration = disableHandlerPermissionGeneration;
}

public boolean getDisableDeprecatedPropertyGeneration() {
return disableDeprecatedPropertyGeneration;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public List<CfnMapper> getCfnMappers() {
new AdditionalPropertiesMapper(),
new DeprecatedMapper(),
new DocumentationMapper(),
new HandlerPermissionMapper(),
new IdentifierMapper(),
new JsonAddMapper(),
new MutabilityMapper(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.smithy.aws.cloudformation.schema.fromsmithy.mappers;

import java.util.Locale;
import java.util.Set;
import java.util.TreeSet;
import software.amazon.smithy.aws.cloudformation.schema.fromsmithy.CfnMapper;
import software.amazon.smithy.aws.cloudformation.schema.fromsmithy.Context;
import software.amazon.smithy.aws.cloudformation.schema.model.Handler;
import software.amazon.smithy.aws.cloudformation.schema.model.ResourceSchema;
import software.amazon.smithy.aws.iam.traits.RequiredActionsTrait;
import software.amazon.smithy.aws.traits.ServiceTrait;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ResourceShape;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.traits.NoReplaceTrait;
import software.amazon.smithy.utils.SetUtils;
import software.amazon.smithy.utils.SmithyInternalApi;

/**
* Generates the resource's handler permissions list based on the lifecycle operation
* used and any permissions listed in the {@code aws.iam#requiredActions} trait.
*
* @see <a href="https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-schema.html#schema-properties-handlers">handlers Docs</a>
*/
@SmithyInternalApi
public final class HandlerPermissionMapper implements CfnMapper {
@Override
public void before(Context context, ResourceSchema.Builder resourceSchema) {
if (context.getConfig().getDisableHandlerPermissionGeneration()) {
return;
}

Model model = context.getModel();
ServiceShape service = context.getService();
ResourceShape resource = context.getResource();

// Start the create and update handler permission gathering.
// TODO Break this out to its own knowledge index if it becomes useful in more contexts.
Set<String> createPermissions = resource.getCreate()
.map(operation -> getPermissionsEntriesForOperation(model, service, operation))
.orElseGet(TreeSet::new);
Set<String> updatePermissions = resource.getUpdate()
.map(operation -> getPermissionsEntriesForOperation(model, service, operation))
.orElseGet(TreeSet::new);

// Add the permissions from the resource's put lifecycle operation
// to the relevant handlers.
Set<String> putPermissions = resource.getPut()
.map(operation -> getPermissionsEntriesForOperation(model, service, operation))
.orElse(SetUtils.of());
createPermissions.addAll(putPermissions);
// Put operations without the noReplace trait are used for updates.
resource.getPut()
.map(model::expectShape)
.filter(shape -> !shape.hasTrait(NoReplaceTrait.class))
.ifPresent(shape -> updatePermissions.addAll(putPermissions));

// Set the create and update handlers, if they have permissions, now that they're complete.
if (!createPermissions.isEmpty()) {
resourceSchema.addHandler("create", Handler.builder().permissions(createPermissions).build());
}
if (!updatePermissions.isEmpty()) {
resourceSchema.addHandler("update", Handler.builder().permissions(updatePermissions).build());
}

// Add the handler permission sets that don't need operation
// permissions to be combined.
resource.getRead()
.map(operation -> getPermissionsEntriesForOperation(model, service, operation))
.ifPresent(permissions -> resourceSchema.addHandler("read", Handler.builder()
.permissions(permissions).build()));

resource.getDelete()
.map(operation -> getPermissionsEntriesForOperation(model, service, operation))
.ifPresent(permissions -> resourceSchema.addHandler("delete", Handler.builder()
.permissions(permissions).build()));

resource.getList()
.map(operation -> getPermissionsEntriesForOperation(model, service, operation))
.ifPresent(permissions -> resourceSchema.addHandler("list", Handler.builder()
.permissions(permissions).build()));
}

private Set<String> getPermissionsEntriesForOperation(Model model, ServiceShape service, ShapeId operationId) {
OperationShape operation = model.expectShape(operationId, OperationShape.class);
Set<String> permissionsEntries = new TreeSet<>();

// Add the operation's permission name itself.
String operationActionName =
service.getTrait(ServiceTrait.class)
.map(ServiceTrait::getArnNamespace)
.orElse(service.getId().getName())
.toLowerCase(Locale.US);
operationActionName += ":" + operationId.getName(service);
permissionsEntries.add(operationActionName);

// Add all the other required actions for the operation.
operation.getTrait(RequiredActionsTrait.class)
.map(RequiredActionsTrait::getValues)
.map(permissionsEntries::addAll);
return permissionsEntries;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@

package software.amazon.smithy.aws.cloudformation.schema.model;

import java.util.ArrayList;
import java.util.List;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.node.NodeMapper;
import software.amazon.smithy.model.node.ToNode;
import software.amazon.smithy.utils.ListUtils;
import software.amazon.smithy.utils.MapUtils;
import software.amazon.smithy.utils.SetUtils;
import software.amazon.smithy.utils.SmithyBuilder;
import software.amazon.smithy.utils.ToSmithyBuilder;

Expand All @@ -45,10 +46,10 @@ public final class Handler implements ToNode, ToSmithyBuilder<Handler> {
DELETE, 3,
LIST, 4);

private final List<String> permissions;
private final Set<String> permissions;

private Handler(Builder builder) {
this.permissions = ListUtils.copyOf(builder.permissions);
this.permissions = SetUtils.orderedCopyOf(builder.permissions);
}

@Override
Expand All @@ -69,7 +70,7 @@ public static Builder builder() {
return new Builder();
}

public List<String> getPermissions() {
public Set<String> getPermissions() {
return permissions;
}

Expand All @@ -78,7 +79,7 @@ public static Integer getHandlerNameOrder(String name) {
}

public static final class Builder implements SmithyBuilder<Handler> {
private final List<String> permissions = new ArrayList<>();
private final Set<String> permissions = new TreeSet<>();

private Builder() {}

Expand All @@ -87,7 +88,7 @@ public Handler build() {
return new Handler(this);
}

public Builder permissions(List<String> permissions) {
public Builder permissions(Collection<String> permissions) {
this.permissions.clear();
this.permissions.addAll(permissions);
return this;
Expand Down
Loading

0 comments on commit e0bcdd0

Please sign in to comment.