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

Support CheckNoPublicAccess check and CheckAccessNotGranted check wit… #11

Merged
merged 1 commit into from
Jun 11, 2024
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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ FROM python:3.10
# Install tf-policy-validator
# Cloning the `terraform-iam-policy-validator` repo for default config for terraform templates
# ToDo: Once we start using the tags for releases, we should use the tag associated with the version we download by using flag --branch <tag>
RUN pip install tf-policy-validator==0.0.6 && git clone https://github.com/awslabs/terraform-iam-policy-validator.git
RUN pip install tf-policy-validator==0.0.8 && git clone https://github.com/awslabs/terraform-iam-policy-validator.git

ENV TERRAFORM_CONFIG_DEFAULT=/terraform-iam-policy-validator/iam_check/config/default.yaml

Expand Down
59 changes: 49 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,27 @@ See [action.yml](action.yaml) for the full documentation for this action's input
<th style="text-align: center;">VALIDATE_POLICY</th>
<th style="text-align: center;">CHECK_NO_NEW_ACCESS</th>
<th style="text-align: center;">CHECK_ACCESS_NOT_GRANTED</th>
<th style="text-align: center;">CHECK_NO_PUBLIC_ACCESS</th>
</tr>
<tr>
<td>policy-check-type</td>
<td>Name of the policy check.<br />Note: Each value corresponds to an IAM Access Analyzer API. <br />- <a href="https://docs.aws.amazon.com/access-analyzer/latest/APIReference/API_ValidatePolicy.html">ValidatePolicy</a><br />- <a href="https://docs.aws.amazon.com/access-analyzer/latest/APIReference/API_CheckNoNewAccess.html">CheckNoNewAccess</a><br />- <a href="https://docs.aws.amazon.com/access-analyzer/latest/APIReference/API_CheckAccessNotGranted.html">CheckAccessNotGranted</a></td>
<td>VALIDATE_POLICY, CHECK_NO_NEW_ACCESS, CHECK_ACCESS_NOT_GRANTED.</td>
<td>Name of the policy check.<br />Note: Each value corresponds to an IAM Access Analyzer API. <br />- <a href="https://docs.aws.amazon.com/access-analyzer/latest/APIReference/API_ValidatePolicy.html">ValidatePolicy</a><br />- <a href="https://docs.aws.amazon.com/access-analyzer/latest/APIReference/API_CheckNoNewAccess.html">CheckNoNewAccess</a><br />- <a href="https://docs.aws.amazon.com/access-analyzer/latest/APIReference/API_CheckAccessNotGranted.html">CheckAccessNotGranted</a><br />- <a href="https://docs.aws.amazon.com/access-analyzer/latest/APIReference/API_CheckNoPublicAccess.html">CheckNoPublicAccess</a></td>
<td>VALIDATE_POLICY, CHECK_NO_NEW_ACCESS, CHECK_ACCESS_NOT_GRANTED, CHECK_NO_PUBLIC_ACCESS.</td>
<td>Yes</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td>template-path</td>
<td>The path to the Terraform template.</td>
<td>The path to the CloudFormation template.</td>
<td>FILE_PATH.json</td>
<td>Yes</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td>region</td>
Expand All @@ -47,6 +50,7 @@ See [action.yml](action.yaml) for the full documentation for this action's input
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td>ignore-finding</td>
Expand All @@ -56,31 +60,47 @@ See [action.yml](action.yaml) for the full documentation for this action's input
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td>actions</td>
<td>List of comma-separated actions. Example format - ACTION,ACTION,ACTION. <br /><br /><strong>This attribute is only considered and required when policy-check-type is "CHECK_ACCESS_NOT_GRANTED".</strong></td>
<td>List of comma-separated actions. Example format - ACTION,ACTION,ACTION. <br /><br /><strong>This attribute is only considered when policy-check-type is "CHECK_ACCESS_NOT_GRANTED". At least one of "actions" or "resources" must be provided</strong></td>
<td>ACTION,ACTION,ACTION</td>
<td>No</td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
<td>❌</td>
</tr>
<tr>
<td>resources</td>
<td>List of comma-separated resource ARNs. Example format - RESOURCE,RESOURCE,RESOURCE. <br /><br /><strong>This attribute is only considered when policy-check-type is "CHECK_ACCESS_NOT_GRANTED". At least one of "actions" or "resources" must be provided</strong></td>
<td>RESOURCE,RESOURCE,RESOURCE</td>
<td>No</td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
<td>❌</td>
</tr>
<tr>
<td>reference-policy</td>
<td>A JSON formatted file that specifies the path to the reference policy that is used for a permissions comparison. <br /><br /><strong>This attribute is only considered and required when policy-check-type is "CHECK_NO_NEW_ACCESS".</strong></td>
<td>FILE_PATH.json</td>
<td>No</td>
<td>❌</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
</tr>
<tr>
<td>reference-policy-type</td>
<td>The policy type associated with the IAM policy under analysis and the reference policy. Valid values: IDENTITY, RESOURCE. <br /><br /><strong> This attribute is only considered and required when policy-check-type is "CHECK_NO_NEW_ACCESS"</strong></td>
<td>REFERENCE_POLICY_TYPE</td>
<td>No</td>
<td>❌</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
</tr>
<tr>
<td>treat-finding-type-as-blocking</td>
Expand All @@ -90,14 +110,17 @@ See [action.yml](action.yaml) for the full documentation for this action's input
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
</tr>
<tr>
<td>treat-findings-as-non-blocking</td>
<td>By default, the tool will exit with a non-zero exit code when it detects any findings. Set this flag to exit with an exit code of 0 when it detects findings. You can use this to run new checks in a shadow or log only mode before enforcing them. <br /><br /><strong>This attribute is considered only when policy-check-type is "CHECK_NO_NEW_ACCESS" or "CHECK_ACCESS_NOT_GRANTED".</strong></td>
<td>By default, the tool will exit with a non-zero exit code when it detects any findings. Set this flag to exit with an exit code of 0 when it detects findings. You can use this to run new checks in a shadow or log only mode before enforcing them. <br /><br /><strong>This attribute is considered only when policy-check-type is "CHECK_NO_NEW_ACCESS", "CHECK_ACCESS_NOT_GRANTED", or "CHECK_NO_PUBLIC_ACCESS.</strong></td>
<td> </td>
<td>No</td>
<td>❌</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td>allow-external-principals</td>
Expand All @@ -107,14 +130,17 @@ See [action.yml](action.yaml) for the full documentation for this action's input
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
</tr>
<tr>
<td>allow-dynamic-ref-without-version</td>
<td>Override the default behavior and allow dynamic SSM references without version numbers. The version number ensures that the SSM parameter value that was validated is the one that is deployed.</td>
<td> </td>
<td>No</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
</tr>
<tr>
<td>exclude-resource-types</td>
Expand All @@ -124,6 +150,7 @@ See [action.yml](action.yaml) for the full documentation for this action's input
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
</tr>
</tbody>
</table>
Expand All @@ -135,18 +162,17 @@ See [action.yml](action.yaml) for the full documentation for this action's input

- Setting up the role: Role used in the GitHub workflow should have necessary permissions required
- to be called from the GitHub workflows - setup OpenID Connect(OIDC) provider and IAM role & Trust policy as described in step 1 & 2 in [this](https://aws.amazon.com/blogs/security/use-iam-roles-to-connect-github-actions-to-actions-in-aws/) blog
- to call the AWS APIs for the policy checks - ValidatePolicy, CheckNoNewAccess, CheckAccessNotGranted. Refer [this](https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-checks-validating-policies.html) page for more details
- to call the AWS APIs for the policy checks - ValidatePolicy, CheckNoNewAccess, CheckAccessNotGranted, CheckNoPublicAccess. Refer [this](https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-checks-validating-policies.html) page for more details

```
- name: Checkout Repo
uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.POLICY_VALIDATOR_ROLE }} # Role with permissions to invoke access-analyzer:ValidatePolicy,access-analyzer:CheckNoNewAccess, access-analyzer:CheckAccessNotGranted
role-to-assume: ${{ secrets.POLICY_VALIDATOR_ROLE }} # Role with permissions to invoke access-analyzer:ValidatePolicy,access-analyzer:CheckNoNewAccess, access-analyzer:CheckAccessNotGranted, access-analyzer:CheckNoPublicAccess
aws-region: aws-example-region
```

#### Getting started using starter workflows

To get started quickly, add a starter workflow to the `.github/workflows` directory of your repository. In order to do that, do the following -
Expand Down Expand Up @@ -191,13 +217,26 @@ Please find the starter workflow [here](https://github.com/actions/starter-workf
#### Using for the `CHECK_ACCESS_NOT_GRANTED` CHECK

```
- name: Run CHECK_ACCESS_NOT_GRANTED check
id: run-check-no-new-access
- name: Run CHECK_ACCESS_NOT_GRANTED check
id: run-check-access-not-granted
uses: aws-actions/terraform-aws-iam-policy-validator@v1.0.1
with:
policy-check-type: 'CHECK_ACCESS_NOT_GRANTED'
template-path: file-path-to-the-cfn-templates
actions: "action1, action2.."
resources: "resource1, resource2.."
region: aws-example-region
```

#### Using for the `CHECK_NO_PUBLIC_ACCESS` CHECK

```
- name: Run CHECK_NO_PUBLIC_ACCESS check
id: run-check-no-public-access
uses: aws-actions/cloudformation-aws-iam-policy-validator@v1.0.1
with:
policy-check-type: 'CHECK_NO_PUBLIC_ACCESS'
template-path: file-path-to-the-cfn-templates
region: aws-example-region
```

Expand Down
4 changes: 3 additions & 1 deletion action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ inputs:
ignore-finding:
description: 'Allow validation failures to be ignored. Specify as a comma separated list of findings to be ignored. Can be individual finding codes (e.g. "PASS_ROLE_WITH_STAR_IN_RESOURCE"), a specific resource name (e.g. "MyResource"), or a combination of both separated by a period.(e.g. "MyResource.PASS_ROLE_WITH_STAR_IN_RESOURCE"). Names of finding codes may change in IAM Access Analyzer over time. Valid options: FINDING_CODE,RESOURCE_NAME,RESOURCE_NAME.FINDING_CODE'
actions:
description: 'List of comma-separated actions. Example format - ACTION,ACTION,ACTION. This attribute is considered and required when policy-check-type is "CHECK_ACCESS_NOT_GRANTED"'
description: 'List of comma-separated actions. Example format - ACTION,ACTION,ACTION. This attribute is considered when policy-check-type is "CHECK_ACCESS_NOT_GRANTED". At least one of "actions" or "resources" must be specified.'
resources:
description: 'List of comma-separated resource ARNs. Example format - RESOURCE,RESOURCE,RESOURCE. This attribute is considered when policy-check-type is "CHECK_ACCESS_NOT_GRANTED" At least one of "actions" or "resources" must be specified.'
reference-policy:
description: 'A JSON formatted file that specifies the path to the reference policy that is used for a permissions comparison. This attribute is considered and required when policy-check-type is "CHECK_NO_NEW_ACCESS"'
reference-policy-type:
Expand Down
43 changes: 35 additions & 8 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
VALIDATE_POLICY = "VALIDATE_POLICY"
CHECK_NO_NEW_ACCESS = "CHECK_NO_NEW_ACCESS"
CHECK_ACCESS_NOT_GRANTED = "CHECK_ACCESS_NOT_GRANTED"
CHECK_NO_PUBLIC_ACCESS = "CHECK_NO_PUBLIC_ACCESS"

CLI_POLICY_VALIDATOR = "tf-policy-validator"

Expand All @@ -26,7 +27,10 @@
"INPUT_REFERENCE-POLICY-TYPE",
}

CHECK_ACCESS_NOT_GRANTED_SPECIFIC_REQUIRED_INPUTS = {"INPUT_ACTIONS"}
# Use tuple to specify that at least one of the enclosed inputs is required.
CHECK_ACCESS_NOT_GRANTED_SPECIFIC_REQUIRED_INPUTS = {("INPUT_ACTIONS", "INPUT_RESOURCES")}

CHECK_NO_PUBLIC_ACCESS_SPECIFIC_REQUIRED_INPUTS = set()

# excluding the "INPUT_POLICY-CHECK-TYPE". Contains only other required inputs in cfn-policy-validator
COMMON_OPTIONAL_INPUTS = {
Expand All @@ -46,11 +50,15 @@
# Excluding the TREAT-FINDINGS-AS-NON-BLOCKING which is a flag and needs special handling
CHECK_ACCESS_NOT_GRANTED_SPECIFIC_OPTIONAL_INPUTS = set()

# Excluding the TREAT-FINDINGS-AS-NON-BLOCKING which is a flag and needs special handling
CHECK_NO_PUBLIC_ACCESS_SPECIFIC_OPTIONAL_INPUTS = set()


VALID_POLICY_CHECK_TYPES = [
VALIDATE_POLICY,
CHECK_NO_NEW_ACCESS,
CHECK_ACCESS_NOT_GRANTED,
CHECK_NO_PUBLIC_ACCESS
]

# Name of the output defined in the GitHub action schema
Expand Down Expand Up @@ -99,6 +107,10 @@ def get_required_inputs(policy_check):
check_specific_required_inputs = (
CHECK_ACCESS_NOT_GRANTED_SPECIFIC_REQUIRED_INPUTS
)
elif policy_check == CHECK_NO_PUBLIC_ACCESS:
check_specific_required_inputs = (
CHECK_NO_PUBLIC_ACCESS_SPECIFIC_REQUIRED_INPUTS
)
required_inputs = COMMON_REQUIRED_INPUTS.union(check_specific_required_inputs)
return required_inputs

Expand All @@ -114,6 +126,10 @@ def get_optional_inputs(policy_check):
check_specific_optional_inputs = (
CHECK_ACCESS_NOT_GRANTED_SPECIFIC_OPTIONAL_INPUTS
)
elif policy_check == CHECK_NO_PUBLIC_ACCESS:
check_specific_optional_inputs = (
CHECK_NO_PUBLIC_ACCESS_SPECIFIC_OPTIONAL_INPUTS
)
optional_inputs = check_specific_optional_inputs.union(COMMON_OPTIONAL_INPUTS)
return optional_inputs

Expand Down Expand Up @@ -147,19 +163,30 @@ def get_sub_command(inputFields, areRequiredFields):
flags = []

for input in inputFields:
# The default values to these environment variable when passed to docker is empty string through GitHub Actions
if os.environ[input] != "":
flag_name = get_flag_name(input)
flags.extend(["--{}".format(flag_name), os.environ[input]])
elif areRequiredFields:
raise ValueError("Missing value for required field: {}", input)
# Checking that at least one of a set of required fields is provided
if isinstance(input, tuple):
provided = False
for field in input:
if os.environ[field] != "":
flag_name = get_flag_name(field)
flags.extend(["--{}".format(flag_name), os.environ[field]])
provided = True
if provided == False:
raise ValueError(f"Missing value for at least one of the required fields: {str(input)}")
else:
# The default values to these environment variable when passed to docker is empty string through GitHub Actions
if os.environ[input] != "":
flag_name = get_flag_name(input)
flags.extend(["--{}".format(flag_name), os.environ[input]])
elif areRequiredFields:
raise ValueError("Missing value for required field: {}", input)

return flags


def get_treat_findings_as_non_blocking_flag(policy_check):
# This is specific to custom checks - CheckAccessNotGranted & CheckNoNewAccess
if policy_check in (CHECK_ACCESS_NOT_GRANTED, CHECK_NO_NEW_ACCESS):
if policy_check in (CHECK_ACCESS_NOT_GRANTED, CHECK_NO_NEW_ACCESS, CHECK_NO_PUBLIC_ACCESS):
val = os.environ[TREAT_FINDINGS_AS_NON_BLOCKING]
if val == "True":
return ["--{}".format(get_flag_name(TREAT_FINDINGS_AS_NON_BLOCKING))]
Expand Down
Loading