Skip to content

Commit

Permalink
Add S3_AccessControl hook (#238)
Browse files Browse the repository at this point in the history
* Add S3_AccessControl hook.

* Update docs.

* Update pipeline.
  • Loading branch information
mrinaudo-aws authored Sep 22, 2023
1 parent bb31709 commit eb7c6b1
Show file tree
Hide file tree
Showing 28 changed files with 1,262 additions and 1 deletion.
29 changes: 29 additions & 0 deletions hooks/S3_AccessControl/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# macOS
.DS_Store
._*

# Maven outputs
.classpath

# IntelliJ
*.iml
.idea
out.java
out/
.settings
.project

# auto-generated files
target/

# our logs
rpdk.log*

# contains credentials
sam-tests/

# tests-related
.hypothesis/

# generated archive
*.zip
29 changes: 29 additions & 0 deletions hooks/S3_AccessControl/.rpdk-config
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"artifact_type": "HOOK",
"typeName": "AwsCommunity::S3::AccessControl",
"language": "java",
"runtime": "java8",
"entrypoint": "com.awscommunity.s3.accesscontrol.HookHandlerWrapper::handleRequest",
"testEntrypoint": "com.awscommunity.s3.accesscontrol.HookHandlerWrapper::testEntrypoint",
"settings": {
"version": false,
"subparser_name": null,
"verbose": 0,
"force": false,
"type_name": null,
"artifact_type": null,
"endpoint_url": null,
"region": null,
"target_schemas": [],
"profile": null,
"namespace": [
"com",
"awscommunity",
"s3",
"accesscontrol"
],
"codegen_template_path": "guided_aws",
"protocolVersion": "2.0.0"
},
"executableEntrypoint": "com.awscommunity.s3.accesscontrol.HookHandlerWrapperExecutable"
}
147 changes: 147 additions & 0 deletions hooks/S3_AccessControl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# AwsCommunity::S3::AccessControl


- [Overview](#Overview)
- [Usage](#Usage)
- [Example templates](#example-templates)
- [Tests](#Tests)
- [Unit tests](#Unit-tests)
- [Contract tests](#Contract-tests)
- [Hook development notes](#Hook-development-notes)


## Overview
This hook for [AWS CloudFormation](https://aws.amazon.com/cloudformation/) validates that the legacy `AccessControl` [property](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html#cfn-s3-bucket-accesscontrol) for an `AWS::S3::Bucket` [resource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html) is either set to `Private`, or is not present.


## Usage
This hook is written in [Kotlin](https://kotlinlang.org/). To build it on your machine, install [Apache Maven](https://maven.apache.org/install.html), and a JDK (8, or 11). For more information, see [Prerequisites for developing hooks](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/hooks-walkthrough-java.html#prerequisites-developing-hooks-java).

Next, you'll need to install the [CloudFormation CLI](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/what-is-cloudformation-cli.html), that you'll use to run contract tests for this hook, and to [submit](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-cli-submit.html) this hook to the CloudFormation registry, as a private extension, in the AWS account and Region(s) of your choice.

The following example shows how to build and submit the hook to the registry, in the `us-east-1` region for the account you use:

```shell
cfn generate && mvn clean verify && cfn submit --set-default --region us-east-1
```

After you submit the hook to the registry, you'll need to configure it. One of the ways of doing this is to first create a `type_config.json` file as shown next (for more information on `Properties` defined for this hook, see [Configuration options](#Configuration-options)):

```shell
cat <<EOF > type_config.json
{
"CloudFormationConfiguration": {
"HookConfiguration": {
"TargetStacks": "ALL",
"FailureMode": "FAIL",
"Properties": {
}
}
}
}
EOF
```

and then submit the hook configuration using the [AWS Command Line Interface (AWS CLI)](https://aws.amazon.com/cli/). First, get the [Amazon Resource Name](https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html) (ARN) for this hook, as follows (examples are for the `us-east-1` region):

```shell
aws cloudformation list-types \
--type HOOK \
--filters TypeNamePrefix=AwsCommunity::S3::AccessControl \
--query 'TypeSummaries[?TypeName==`AwsCommunity::S3::AccessControl`].TypeArn' \
--output text \
--region us-east-1
```

Next, find the ARN value string in the output of the command above, and use it with this command by replacing the placeholder text below in upper case characters for `YOUR_HOOK_ARN`:

```shell
aws cloudformation set-type-configuration \
--configuration file://type_config.json \
--type-arn 'YOUR_HOOK_ARN' \
--region us-east-1
```


# Example templates

Templates in this section are marked as:
- _Non-compliant_: this hook will find the given template to be non-compliant, and
- _Compliant_: the template will be found to be compliant, and should deploy successfully.

Note that you'll also find templates called `integ-succeed.yml` and `integ-fail.yml` in the `test` directory, that can be used to create stacks for integration testing.

Non-compliant: the `AccessControl` [property](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html#cfn-s3-bucket-accesscontrol) for the `AWS::S3::Bucket` [resource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html) is present in the template, and with a value other than `Private`:

```yaml
AWSTemplateFormatVersion: "2010-09-09"

Description: Test-only template that describes an Amazon S3 bucket for integration tests.

Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
AccessControl: PublicRead
```
Compliant, example 1: `AccessControl` is present in the template, and with a value of `Private`:

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: Test-only template that describes an Amazon S3 bucket for integration tests.
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
AccessControl: Private
```

Compliant, example 2: `AccessControl` is not present in the template:

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: Test-only template that describes an Amazon S3 bucket for integration tests.
Resources:
S3Bucket:
Type: AWS::S3::Bucket
```

## Tests


### Unit tests
Run unit tests, and verify code coverage with:

```shell
mvn clean verify
```


### Contract tests
Contract tests help you validate hooks you develop work as expected, and are required to pass when you submit a CloudFormation extension such as a hook to the public registry. It is recommended to strive to pass contract tests also when you write a private extension, to help discover potential issues.

To run contract tests, open a new terminal window and run, from the root directory of this project:

```shell
sam local start-lambda
```

For more information, see [Testing resource types locally using AWS SAM](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test.html#resource-type-develop-test).

Open a another terminal window, build this hook, and run contract tests:

```shell
cfn generate && mvn clean verify && cfn test -v --enforce-timeout 90
```

## Hook development notes
When you build this hook's project via Maven, the correct hook input model from the schema will be automatically generated. You can also do this manually with the following command: `cfn generate`.

> Please don't modify files under `target/generated-sources/rpdk`, as they will be automatically overwritten.

The generated code uses [Lombok](https://projectlombok.org/) to annotate Java classes, including classes in the `target/generated-sources/rpdk` path mentioned above. This hook -that is written in Kotlin- needs to consume such Lombok-annotated Java classes: one way to do this is to use [Delombok](https://projectlombok.org/features/delombok) to delombok relevant source files. The `pom.xml` file, for this `AwsCommunity::S3::AccessControl` hook, uses the `delombok` goal for the [lombok.maven plugin](https://github.com/awhitford/lombok.maven) to delombok, during the `process-sources` phase, the `target/generated-sources/rpdk` generated classes into the `target/generated-sources/delombok` target directory that, in turn, is then added as a source with the `add-source` goal for the `build-helper-maven-plugin`.
27 changes: 27 additions & 0 deletions hooks/S3_AccessControl/awscommunity-s3-accesscontrol.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"typeName": "AwsCommunity::S3::AccessControl",
"description": "This hook for AWS CloudFormation validates that the legacy AccessControl property for an AWS::S3::Bucket resource is either set to Private, or is not present.",
"sourceUrl": "https://github.com/aws-cloudformation/community-registry-extensions/tree/main/hooks/S3_AccessControl",
"documentationUrl": "https://github.com/aws-cloudformation/community-registry-extensions/blob/main/hooks/S3_AccessControl/README.md",
"typeConfiguration": {
"properties": {
},
"additionalProperties": false
},
"required": [],
"handlers": {
"preCreate": {
"targetNames": [
"AWS::S3::Bucket"
],
"permissions": []
},
"preUpdate": {
"targetNames": [
"AWS::S3::Bucket"
],
"permissions": []
}
},
"additionalProperties": false
}
33 changes: 33 additions & 0 deletions hooks/S3_AccessControl/docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# AwsCommunity::S3::AccessControl

## Activation

To activate a hook in your account, use the following JSON as the `Configuration` request parameter for [`SetTypeConfiguration`](https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_SetTypeConfiguration.html) API request.

### Configuration

<pre>
{
"CloudFormationConfiguration": {
"<a href="https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/hooks-structure.html#hooks-hook-configuration" title="HookConfiguration">HookConfiguration</a>": {
"<a href="https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/hooks-structure.html#hooks-targetstacks" title="TargetStacks">TargetStacks</a>": <a href="#footnote-1">"ALL" | "NONE"</a>,
"<a href="https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/hooks-structure.html#hooks-failuremode" title="FailureMode">FailureMode</a>": <a href="#footnote-1">"FAIL" | "WARN"</a> ,
"<a href="https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/hooks-structure.html#hooks-properties" title="Properties">Properties</a>" : {
}
}
}
}
</pre>

---

## Targets

* `AWS::S3::Bucket`

---

<p id="footnote-1"><i> Please note that the enum values for <a href="https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/hooks-structure.html#hooks-targetstacks" title="TargetStacks">
TargetStacks</a> and <a href="https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/hooks-structure.html#hooks-failuremode" title="FailureMode">FailureMode</a>
might go out of date, please refer to their official documentation page for up-to-date values. </i></p>

40 changes: 40 additions & 0 deletions hooks/S3_AccessControl/hook-role-prod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
AWSTemplateFormatVersion: "2010-09-09"
Description: >
This CloudFormation template creates a role assumed by CloudFormation
during Hook operations on behalf of the customer.
Resources:
ExecutionRole:
Type: AWS::IAM::Role
Properties:
MaxSessionDuration: 8400
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- hooks.cloudformation.amazonaws.com
- resources.cloudformation.amazonaws.com
Action: sts:AssumeRole
Condition:
StringEquals:
aws:SourceAccount:
Ref: AWS::AccountId
StringLike:
aws:SourceArn:
Fn::Sub: arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:type/hook/AwsCommunity-S3-AccessControl/*
Path: "/"
Policies:
- PolicyName: HookTypePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Deny
Action:
- "*"
Resource: "*"
Outputs:
ExecutionRoleArn:
Value:
Fn::GetAtt: ExecutionRole.Arn
40 changes: 40 additions & 0 deletions hooks/S3_AccessControl/hook-role.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
AWSTemplateFormatVersion: "2010-09-09"
Description: >
This CloudFormation template creates a role assumed by CloudFormation
during Hook operations on behalf of the customer.
Resources:
ExecutionRole:
Type: AWS::IAM::Role
Properties:
MaxSessionDuration: 8400
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- hooks.cloudformation.amazonaws.com
- resources.cloudformation.amazonaws.com
Action: sts:AssumeRole
Condition:
StringEquals:
aws:SourceAccount:
Ref: AWS::AccountId
StringLike:
aws:SourceArn:
Fn::Sub: arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:type/hook/AwsCommunity-S3-AccessControl/*
Path: "/"
Policies:
- PolicyName: HookTypePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Deny
Action:
- "*"
Resource: "*"
Outputs:
ExecutionRoleArn:
Value:
Fn::GetAtt: ExecutionRole.Arn
7 changes: 7 additions & 0 deletions hooks/S3_AccessControl/inputs/inputs_1_invalid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"AWS::S3::Bucket": {
"resourceProperties": {
"AccessControl": "PublicRead"
}
}
}
5 changes: 5 additions & 0 deletions hooks/S3_AccessControl/inputs/inputs_1_pre_create.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"AWS::S3::Bucket": {
"resourceProperties": {}
}
}
7 changes: 7 additions & 0 deletions hooks/S3_AccessControl/inputs/inputs_1_pre_update.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"AWS::S3::Bucket": {
"resourceProperties": {
"AccessControl": "Private"
}
}
}
1 change: 1 addition & 0 deletions hooks/S3_AccessControl/lombok.config
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
lombok.addLombokGeneratedAnnotation = true
Loading

0 comments on commit eb7c6b1

Please sign in to comment.