Skip to content

Commit

Permalink
feat: adding the ability to enforce tags via the iam boundary for mac…
Browse files Browse the repository at this point in the history
…hine roles, and deploying a similar policy for sso users (#48)

* feat: adding the ability to enforce tags via the iam boundary for machine roles, and deploying a similar policy for sso users

* docs: updating the docs to reflect the changes

* fix: removing the .orig file and adding the gitignore

* fix: the parameters in the template were incorrect

* chore: updating the documentation to reflect the changes
  • Loading branch information
gambol99 committed Sep 21, 2024
1 parent bd7c825 commit b2a9e77
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 36 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ terraform.rc
# Other
.DS_Store
todo.md
*.orig

# Ignore securityhub-findings-forwarder lambda deployment package
builds
33 changes: 25 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
#
# Copyright (C) 2024 Appvia Ltd <info@appvia.io>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
Expand All @@ -14,23 +12,22 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
AUTHOR_EMAIL=info@appvia.io

.PHONY: all security lint format documentation documentation-examples validate-all validate validate-examples init examples
.PHONY: all security lint format documentation documentation-examples validate-all validate validate-examples init examples tests

default: all

all:
$(MAKE) init
$(MAKE) validate
$(MAKE) tests
$(MAKE) lint
$(MAKE) security
$(MAKE) format
$(MAKE) documentation

examples:
@echo "--> Generating documentation"
$(MAKE) validate-examples
$(MAKE) tests
$(MAKE) lint-examples
$(MAKE) lint
$(MAKE) security
Expand All @@ -55,6 +52,19 @@ documentation-examples:
find examples -type d -mindepth 1 -maxdepth 1 -exec terraform-docs markdown table --output-file README.md --output-mode inject {} \; ; \
fi

upgrade-terraform-providers:
@printf "%s Upgrading Terraform providers for %-24s" "-->" "."
@terraform init -upgrade >/dev/null && echo "[OK]" || echo "[FAILED]"
@$(MAKE) upgrade-terraform-example-providers

upgrade-terraform-example-providers:
@if [ -d examples ]; then \
find examples -type d -mindepth 1 -maxdepth 1 | while read -r dir; do \
printf "%s Upgrading Terraform providers for %-24s" "-->" "$$dir"; \
terraform -chdir=$$dir init -upgrade >/dev/null && echo "[OK]" || echo "[FAILED]"; \
done; \
fi

init:
@echo "--> Running terraform init"
@terraform init -backend=false
Expand All @@ -70,7 +80,7 @@ security-modules:
@if [ -d modules ]; then \
find modules -type d -mindepth 1 -maxdepth 1 | while read -r dir; do \
echo "--> Validating $$dir"; \
trivy config $$dir; \
trivy config --format table --exit-code 1 --severity CRITICAL,HIGH --ignorefile .trivyignore $$dir; \
done; \
fi

Expand All @@ -79,10 +89,14 @@ security-examples:
@if [ -d examples ]; then \
find examples -type d -mindepth 1 -maxdepth 1 | while read -r dir; do \
echo "--> Validating $$dir"; \
trivy config $$dir; \
trivy config --format table --exit-code 1 --severity CRITICAL,HIGH --ignorefile .trivyignore $$dir; \
done; \
fi

tests:
@echo "--> Running Terraform Tests"
@terraform test

validate:
@echo "--> Running terraform validate"
@terraform init -backend=false
Expand Down Expand Up @@ -110,6 +124,9 @@ validate-examples:
done; \
fi

validate-commits:
@echo "--> Running commitlint against the "

lint:
@echo "--> Running tflint"
@tflint --init
Expand Down
20 changes: 16 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ identity_center_start_url = "<your identity center start url - if relevant>"
identity_center_role = "<your your identity center role - consistent across accounts typically read only - if relevant>"
```

## Tagging Enforcement

The tagging enforcement feature updates the default IAM boundaries deployed by this module to include additional policy blocking the creation of resources without the required tags; defined in the `var.enforcable_tags` variable. The restrictions will be applied to all actions found in the `var.enforcable_tagging_actions`. These are the same IAM boundaries which are intended to be used by machine roles (i.e. CI/CD).

Switching on the feature will also deploy a stackset across the entire organization implementing the tagging policy deny logic. This should be referenced by roles within the account.

## Update Documentation

The `terraform-docs` utility is used to generate this README. Follow the below steps to update:
Expand Down Expand Up @@ -111,6 +117,7 @@ The `terraform-docs` utility is used to generate this README. Follow the below s
| <a name="module_network_transit_gateway_admin"></a> [network\_transit\_gateway\_admin](#module\_network\_transit\_gateway\_admin) | appvia/oidc/aws//modules/role | 1.3.2 |
| <a name="module_permissive_boundary"></a> [permissive\_boundary](#module\_permissive\_boundary) | appvia/boundary-stack/aws | 0.1.7 |
| <a name="module_securityhub_notifications"></a> [securityhub\_notifications](#module\_securityhub\_notifications) | appvia/notifications/aws | 1.0.4 |
| <a name="module_tagging_boundary"></a> [tagging\_boundary](#module\_tagging\_boundary) | appvia/boundary-stack/aws | 0.1.7 |

## Resources

Expand Down Expand Up @@ -155,7 +162,7 @@ The `terraform-docs` utility is used to generate this README. Follow the below s
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_accounts_id_to_name"></a> [accounts\_id\_to\_name](#input\_accounts\_id\_to\_name) | A mapping of account id and account name - used by notification lamdba to map an account ID to a human readable name | `map(string)` | `{}` | no |
| <a name="input_aws_accounts"></a> [aws\_accounts](#input\_aws\_accounts) | Map of AWS account names to their account IDs | <pre>object({<br> network_account_id = optional(string, "")<br> remoteaccess_account_id = optional(string, "")<br> })</pre> | n/a | yes |
| <a name="input_aws_accounts"></a> [aws\_accounts](#input\_aws\_accounts) | Map of AWS account names to their account IDs | <pre>object({<br/> network_account_id = optional(string, "")<br/> remoteaccess_account_id = optional(string, "")<br/> })</pre> | n/a | yes |
| <a name="input_aws_support_role_name"></a> [aws\_support\_role\_name](#input\_aws\_support\_role\_name) | Name of the AWS Support role | `string` | `"AWSSupportAccessRole"` | no |
| <a name="input_breakglass_users"></a> [breakglass\_users](#input\_breakglass\_users) | The number of breakglass users to create | `number` | `2` | no |
| <a name="input_cloudaccess_terraform_state_ro_policy_name"></a> [cloudaccess\_terraform\_state\_ro\_policy\_name](#input\_cloudaccess\_terraform\_state\_ro\_policy\_name) | Name of the IAM policy to attach to the CloudAccess Terraform state role | `string` | `"lza-cloudaccess-tfstate-ro"` | no |
Expand All @@ -167,18 +174,23 @@ The `terraform-docs` utility is used to generate this README. Follow the below s
| <a name="input_enable_breakglass"></a> [enable\_breakglass](#input\_enable\_breakglass) | Indicates if we should enable breakglass users and group | `bool` | `false` | no |
| <a name="input_enable_cis_alarms"></a> [enable\_cis\_alarms](#input\_enable\_cis\_alarms) | Indicates if we should enable CIS alerts | `bool` | `true` | no |
| <a name="input_enable_securityhub_alarms"></a> [enable\_securityhub\_alarms](#input\_enable\_securityhub\_alarms) | Indicates if we should enable SecurityHub alarms | `bool` | `true` | no |
| <a name="input_enable_tag_enforcement"></a> [enable\_tag\_enforcement](#input\_enable\_tag\_enforcement) | Indicates if we should enable tagging enforcement | `bool` | `false` | no |
| <a name="input_enforcable_tagging_actions"></a> [enforcable\_tagging\_actions](#input\_enforcable\_tagging\_actions) | List of enforceable tagging actions | `list(string)` | <pre>[<br/> "ec2:CreateInternetGateway",<br/> "ec2:CreateVolume",<br/> "ec2:CreateVpcPeeringConnection",<br/> "ec2:RunInstances",<br/> "ecs:CreateCluster",<br/> "ecs:CreateService",<br/> "ecs:CreateTaskSet",<br/> "eks:CreateCluster",<br/> "elasticfilesystem:CreateAccessPoint",<br/> "elasticfilesystem:CreateFileSystem",<br/> "elasticloadbalancing:CreateListener",<br/> "elasticloadbalancing:CreateLoadBalancer",<br/> "elasticloadbalancing:CreateRule",<br/> "elasticloadbalancing:CreateTargetGroup",<br/> "elasticloadbalancing:CreateTrustStore",<br/> "network-firewall:CreateFirewall",<br/> "network-firewall:CreateFirewallPolicy",<br/> "network-firewall:CreateRuleGroup",<br/> "ram:CreatePermission",<br/> "ram:CreateResourceShare",<br/> "redshift:CreateCluster",<br/> "redshift:CreateClusterParameterGroup",<br/> "redshift:CreateClusterSecurityGroup",<br/> "redshift:CreateClusterSubnetGroup",<br/> "route53:CreateHostedZone",<br/> "secretsmanager:CreateSecret"<br/>]</pre> | no |
| <a name="input_enforcable_tagging_policy_name"></a> [enforcable\_tagging\_policy\_name](#input\_enforcable\_tagging\_policy\_name) | Name of the IAM policy to use as a permissions boundary for enforceable tags | `string` | `"lza-enforceable-tags-boundary"` | no |
| <a name="input_enforcable_tagging_resources"></a> [enforcable\_tagging\_resources](#input\_enforcable\_tagging\_resources) | List of enforceable tagging resources | `list(string)` | <pre>[<br/> "*"<br/>]</pre> | no |
| <a name="input_enforcable_tags"></a> [enforcable\_tags](#input\_enforcable\_tags) | List of enforceable tags | `list(string)` | `[]` | no |
| <a name="input_identity_center_start_url"></a> [identity\_center\_start\_url](#input\_identity\_center\_start\_url) | The start URL of your Identity Center instance | `string` | `null` | no |
| <a name="input_notifications"></a> [notifications](#input\_notifications) | Configuration for the notifications | <pre>object({<br> email = optional(object({<br> addresses = list(string)<br> }), null)<br> slack = optional(object({<br> webhook_url = string<br> }), null)<br> teams = optional(object({<br> webhook_url = string<br> }), null)<br> })</pre> | <pre>{<br> "email": {<br> "addresses": []<br> },<br> "slack": null,<br> "teams": null<br>}</pre> | no |
| <a name="input_notifications"></a> [notifications](#input\_notifications) | Configuration for the notifications | <pre>object({<br/> email = optional(object({<br/> addresses = list(string)<br/> }), null)<br/> slack = optional(object({<br/> webhook_url = string<br/> }), null)<br/> teams = optional(object({<br/> webhook_url = string<br/> }), null)<br/> })</pre> | <pre>{<br/> "email": {<br/> "addresses": []<br/> },<br/> "slack": null,<br/> "teams": null<br/>}</pre> | no |
| <a name="input_permissive_permissions_boundary_name"></a> [permissive\_permissions\_boundary\_name](#input\_permissive\_permissions\_boundary\_name) | Name of the permissive IAM policy to use as a permissions boundary | `string` | `"lza-permissive-boundary"` | no |
| <a name="input_repositories"></a> [repositories](#input\_repositories) | List of repository locations for the pipelines | <pre>object({<br> accelerator = optional(object({<br> url = string<br> role_name = optional(string, "lza-accelerator")<br> }), null)<br> connectivity = optional(object({<br> url = string<br> role_name = optional(string, "lza-connectivity")<br> }), null)<br> cost_management = optional(object({<br> url = string<br> role_name = optional(string, "lza-cost-management")<br> }), null)<br> firewall = optional(object({<br> url = string<br> role_name = optional(string, "lza-firewall")<br> }), null)<br> identity = optional(object({<br> url = string<br> role_name = optional(string, "lza-identity")<br> }), null)<br> })</pre> | `{}` | no |
| <a name="input_repositories"></a> [repositories](#input\_repositories) | List of repository locations for the pipelines | <pre>object({<br/> accelerator = optional(object({<br/> url = string<br/> role_name = optional(string, "lza-accelerator")<br/> }), null)<br/> connectivity = optional(object({<br/> url = string<br/> role_name = optional(string, "lza-connectivity")<br/> }), null)<br/> cost_management = optional(object({<br/> url = string<br/> role_name = optional(string, "lza-cost-management")<br/> }), null)<br/> firewall = optional(object({<br/> url = string<br/> role_name = optional(string, "lza-firewall")<br/> }), null)<br/> identity = optional(object({<br/> url = string<br/> role_name = optional(string, "lza-identity")<br/> }), null)<br/> })</pre> | `{}` | no |
| <a name="input_scm_name"></a> [scm\_name](#input\_scm\_name) | Name of the source control management system (github or gitlab) | `string` | `"github"` | no |
| <a name="input_security_hub_identity_center_role"></a> [security\_hub\_identity\_center\_role](#input\_security\_hub\_identity\_center\_role) | The name of the role to use when redirecting through Identity Center for security hub events | `string` | `null` | no |
| <a name="input_securityhub_event_bridge_rule_name"></a> [securityhub\_event\_bridge\_rule\_name](#input\_securityhub\_event\_bridge\_rule\_name) | Display name of the EventBridge rule for Security Hub findings | `string` | `"lza-securityhub-alerts"` | no |
| <a name="input_securityhub_lambda_function_name"></a> [securityhub\_lambda\_function\_name](#input\_securityhub\_lambda\_function\_name) | Name of the Security Hub Lambda function | `string` | `"lza-securityhub-lambda-forwarder"` | no |
| <a name="input_securityhub_lambda_log_group_kms_alias"></a> [securityhub\_lambda\_log\_group\_kms\_alias](#input\_securityhub\_lambda\_log\_group\_kms\_alias) | Name of the KMS alias for the CloudWatch log group | `string` | `"alias/accelerator/kms/cloudwatch/key"` | no |
| <a name="input_securityhub_lambda_role_name"></a> [securityhub\_lambda\_role\_name](#input\_securityhub\_lambda\_role\_name) | Name of the IAM role for the Security Hub Lambda function | `string` | `"lza-securityhub-lambda-role"` | no |
| <a name="input_securityhub_lambda_runtime"></a> [securityhub\_lambda\_runtime](#input\_securityhub\_lambda\_runtime) | Runtime for the Security Hub Lambda function | `string` | `"python3.12"` | no |
| <a name="input_securityhub_severity_filter"></a> [securityhub\_severity\_filter](#input\_securityhub\_severity\_filter) | Indicates if we should enable SecurityHub | `list(string)` | <pre>[<br> "CRITICAL",<br> "HIGH"<br>]</pre> | no |
| <a name="input_securityhub_severity_filter"></a> [securityhub\_severity\_filter](#input\_securityhub\_severity\_filter) | Indicates if we should enable SecurityHub | `list(string)` | <pre>[<br/> "CRITICAL",<br/> "HIGH"<br/>]</pre> | no |
| <a name="input_securityhub_sns_topic_name"></a> [securityhub\_sns\_topic\_name](#input\_securityhub\_sns\_topic\_name) | Name of the SNS topic to send Security Hub findings to | `string` | `"lza-securityhub-alerts"` | no |
| <a name="input_tags"></a> [tags](#input\_tags) | Tags to apply to all resources | `map(string)` | n/a | yes |

Expand Down
47 changes: 32 additions & 15 deletions assets/cloudformation/default-boundary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,40 +43,40 @@ Resources:
- iam:DeletePolicyVersion
- iam:SetDefaultPolicyVersion
Resource:
- !Sub "arn:aws:iam::${AWS::AccountId}:policy/${BoundaryName}"
- !Sub "arn:aws:iam::$${AWS::AccountId}:policy/$${BoundaryName}"
- Sid: DenyRemovalOfPermBoundaryFromAnyUserOrRole
Effect: Deny
Action:
- iam:DeleteRolePermissionsBoundary
- iam:DeleteUserPermissionsBoundary
Resource:
- !Sub "arn:aws:iam::${AWS::AccountId}:user/*"
- !Sub "arn:aws:iam::${AWS::AccountId}:role/*"
- !Sub "arn:aws:iam::$${AWS::AccountId}:user/*"
- !Sub "arn:aws:iam::$${AWS::AccountId}:role/*"
Condition:
StringEquals:
"iam:PermissionsBoundary": !Sub "arn:aws:iam::${AWS::AccountId}:policy/${BoundaryName}"
"iam:PermissionsBoundary": !Sub "arn:aws:iam::$${AWS::AccountId}:policy/$${BoundaryName}"
- Sid: DenyAccessIfRequiredPermBoundaryIsNotBeingApplied
Effect: Deny
Action:
- iam:PutRolePermissionsBoundary
- iam:PutUserPermissionsBoundary
Resource:
- !Sub "arn:aws:iam::${AWS::AccountId}:user/*"
- !Sub "arn:aws:iam::${AWS::AccountId}:role/*"
- !Sub "arn:aws:iam::$${AWS::AccountId}:user/*"
- !Sub "arn:aws:iam::$${AWS::AccountId}:role/*"
Condition:
StringNotEquals:
"iam:PermissionsBoundary": !Sub "arn:aws:iam::${AWS::AccountId}:policy/${BoundaryName}"
"iam:PermissionsBoundary": !Sub "arn:aws:iam::$${AWS::AccountId}:policy/$${BoundaryName}"
- Sid: DenyUserAndRoleCreationWithOutPermBoundary
Effect: Deny
Action:
- iam:CreateRole
- iam:CreateUser
Resource:
- !Sub "arn:aws:iam::${AWS::AccountId}:user/*"
- !Sub "arn:aws:iam::${AWS::AccountId}:role/*"
- !Sub "arn:aws:iam::$${AWS::AccountId}:user/*"
- !Sub "arn:aws:iam::$${AWS::AccountId}:role/*"
Condition:
StringNotEquals:
"iam:PermissionsBoundary": !Sub "arn:aws:iam::${AWS::AccountId}:policy/${BoundaryName}"
"iam:PermissionsBoundary": !Sub "arn:aws:iam::$${AWS::AccountId}:policy/$${BoundaryName}"
- Sid: NoCIPolicyEdit
Effect: Deny
Action:
Expand All @@ -86,8 +86,8 @@ Resources:
- iam:DeletePolicyVersion
- iam:SetDefaultPolicyVersion
Resource:
- !Sub "arn:aws:iam::${AWS::AccountId}:policy/${TerraformStateRWPolicyName}"
- !Sub "arn:aws:iam::${AWS::AccountId}:policy/${TerraformStateROPolicyName}"
- !Sub "arn:aws:iam::$${AWS::AccountId}:policy/$${TerraformStateRWPolicyName}"
- !Sub "arn:aws:iam::$${AWS::AccountId}:policy/$${TerraformStateROPolicyName}"
- Sid: DenyCreateOrDestroyIAMUsers
Effect: Deny
Action:
Expand All @@ -103,16 +103,33 @@ Resources:
Action:
- dynamoDB:DeleteTable
Resource:
- !Sub "arn:aws:dynamodb::${AWS::AccountId}:table/${AWS::AccountId}-${AWS::Region}-tflock"
- !Sub "arn:aws:dynamodb::$${AWS::AccountId}:table/$${AWS::AccountId}-$${AWS::Region}-tflock"
- Sid: ProtectS3RemoteState
Effect: Deny
Action:
- s3:DeleteBucket
Resource:
- !Sub "arn:aws:s3:::${AWS::AccountId}-${AWS::Region}-tfstate"
- !Sub "arn:aws:s3:::$${AWS::AccountId}-$${AWS::Region}-tfstate"
- Sid: DenyCloudWatchAlarms
Effect: Deny
Action:
- cloudwatch:*
Resource:
- !Sub "arn:aws:cloudwatch::${AWS::AccountId}:alarm:*"
- !Sub "arn:aws:cloudwatch::$${AWS::AccountId}:alarm:*"
%{ if enable_tagging_enforcement ~}
- Sid: EnforceTaggingPolicy
Effect: Deny
Action:
%{ for action in actions ~}
- "${action}"
%{ endfor ~}
Resource: [
%{ for resource in resources ~}
- "${resource}"
%{ endfor ~}
Condition:
Null:
%{ for tag in tags ~}
"aws:RequestTag/${tag}": "true"
%{ endfor ~}
%{ endif ~}
Loading

0 comments on commit b2a9e77

Please sign in to comment.