Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add generation for CFN handler permissions #939

Merged
merged 1 commit into from
Oct 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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``)
kstich marked this conversation as resolved.
Show resolved Hide resolved
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
kstich marked this conversation as resolved.
Show resolved Hide resolved

{
"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.
kstich marked this conversation as resolved.
Show resolved Hide resolved
// 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