Skip to content

Commit

Permalink
Merge pull request #1 from sudoblark/feature/initial-module-setup
Browse files Browse the repository at this point in the history
Initial module setup
  • Loading branch information
benjaminlukeclark authored Sep 14, 2024
2 parents 735133a + 7e01954 commit d186c04
Show file tree
Hide file tree
Showing 12 changed files with 459 additions and 4 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/commit-to-pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
validation:
strategy:
matrix:
folder: ["add", "folders", "here"]
folder: ["./", "examples/iam_role"]
name: Terraform validate for ${{ matrix.folder }}
runs-on: ubuntu-20.04
steps:
Expand All @@ -41,7 +41,7 @@ jobs:
linting:
strategy:
matrix:
folder: ["add", "folders", "here"]
folder: ["./", "examples/iam_role"]
name: Terraform lint for ${{ matrix.folder }}
runs-on: ubuntu-20.04
steps:
Expand All @@ -59,7 +59,7 @@ jobs:
plan:
strategy:
matrix:
folder: ["add", "folders", "here"]
folder: ["examples/iam_role"]
name: Terraform plan for ${{ matrix.folder }}
runs-on: ubuntu-20.04
needs: [validation, linting]
Expand Down
1 change: 1 addition & 0 deletions .terraform-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.5.1
75 changes: 74 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,84 @@ The below documentation is intended to assist users in utilising the module, the
the module itself, and the [examples](#examples) section which has examples of how to utilise the module.

<!-- BEGIN_TF_DOCS -->
## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | ~> 1.5.0 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 5.61.0 |

## Providers

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | 5.67.0 |

## Modules

No modules.

## Resources

| Name | Type |
|------|------|
| [aws_iam_policy.policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_role.roles](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_policy_document.assume_policies](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.attached_policies](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_application_name"></a> [application\_name](#input\_application\_name) | Name of the application utilising resource. | `string` | n/a | yes |
| <a name="input_environment"></a> [environment](#input\_environment) | Which environment this is being instantiated in. | `string` | n/a | yes |
| <a name="input_raw_iam_roles"></a> [raw\_iam\_roles](#input\_raw\_iam\_roles) | Data structure<br>---------------<br>A list of dictionaries, where each dictionary has the following attributes:<br><br>REQUIRED<br>---------<br>- suffix : Suffix to use for the role name<br>- iam\_policy\_statements : A list of dictionaries where each dictionary is an IAM statement defining permissions<br>-- Each dictionary in this list must define the following attributes:<br>--- sid: Friendly name for the policy, no spaces or special characters allowed<br>--- actions: A list of IAM actions the role is allowed to perform<br>--- resources: Which resource(s) the role may perform the above actions against<br>--- conditions : An OPTIONAL list of dictionaries, which each defines:<br>---- test : Test condition for limiting the action<br>---- variable : Value to test<br>---- values : A list of strings, denoting what to test for<br><br>OPTIONAL<br>---------<br>- path : Path to create the role and policies under, defaults to "/"<br><br>- assume\_policy\_principles : A list of dictionaries where each dictionary defines a principle allowed to assume the role.<br>-- Each dictionary in this list must define the following attributes:<br>--- type : A string defining what type the principle(s) is/are<br>--- identifiers : A list of strings, where each string is an allowed principle<br>--- conditions : An OPTIONAL list of dictionaries, which each defines:<br>---- test : Test condition for limiting the action<br>---- variable : Value to test<br>---- values : A list of strings, denoting what to test for<br><br><br>Constraints<br>---------------<br>- <var.environment>-<var.application\_name>-<suffix> has<br>to be lower than 38 characters due to IAM role naming requirements. Cannot encode in variable validation as<br>string interpolations are not allowed in variables. | <pre>list(<br> object({<br> suffix = string,<br> path = optional(string, "/"),<br> iam_policy_statements = list(<br> object({<br> sid = string,<br> actions = list(string),<br> resources = list(string),<br> conditions = optional(list(<br> object({<br> test : string,<br> variable : string,<br> values = list(string)<br> })<br> ), [])<br> })<br> ),<br> assume_policy_principles = optional(list(<br> object({<br> type = string,<br> identifiers = list(string),<br> conditions = optional(list(<br> object({<br> test : string,<br> variable : string,<br> values = list(string)<br> })<br> ), [])<br> })<br> ), [])<br> })<br> )</pre> | n/a | yes |

## Outputs

No outputs.
<!-- END_TF_DOCS -->

## Data structure
<POPULATE WITH YOUR DATA STRUCTURE>
```
Data structure
---------------
A list of dictionaries, where each dictionary has the following attributes:
REQUIRED
---------
- suffix : Suffix to use for the role name
- iam_policy_statements : A list of dictionaries where each dictionary is an IAM statement defining permissions
-- Each dictionary in this list must define the following attributes:
--- sid: Friendly name for the policy, no spaces or special characters allowed
--- actions: A list of IAM actions the role is allowed to perform
--- resources: Which resource(s) the role may perform the above actions against
--- conditions : An OPTIONAL list of dictionaries, which each defines:
---- test : Test condition for limiting the action
---- variable : Value to test
---- values : A list of strings, denoting what to test for
OPTIONAL
---------
- path : Path to create the role and policies under, defaults to "/"
- assume_policy_principles : A list of dictionaries where each dictionary defines a principle allowed to assume the role.
-- Each dictionary in this list must define the following attributes:
--- type : A string defining what type the principle(s) is/are
--- identifiers : A list of strings, where each string is an allowed principle
--- conditions : An OPTIONAL list of dictionaries, which each defines:
---- test : Test condition for limiting the action
---- variable : Value to test
---- values : A list of strings, denoting what to test for
Constraints
---------------
- <var.environment>-<var.application_name>-<suffix> has
to be lower than 38 characters due to IAM role naming requirements. Cannot encode in variable validation as
string interpolations are not allowed in variables.
```

## Examples
See `examples` folder for an example setup.
5 changes: 5 additions & 0 deletions examples/iam_role/data.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Retrieve the current AWS Account info
data "aws_caller_identity" "current_account" {}

# Get current region
data "aws_region" "current_region" {}
168 changes: 168 additions & 0 deletions examples/iam_role/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
locals {
raw_iam_roles = [
{
suffix = "redshift-role",
iam_policy_statements = [
{
sid = "AllowListGlueBuckets"
effect = "Allow"
actions = [
"s3:ListBucket",
"s3:GetBucketAcl"
]
resources = [
"arn:aws:s3:::raw-bucket",
"arn:aws:s3:::processed-bucket"
]
},
{
sid = "AllowGlueBucketObjectManipulation"
effect = "Allow"
actions = [
"s3:GetObject",
"s3:PutObject"
]
resources = [
"arn:aws:s3:::raw-bucket/*",
"arn:aws:s3:::processed-bucket/*"
]
},
{
sid = "AllowGlueTableAccess"
effect = "Allow"
actions = [
"glue:SearchTables",
"glue:GetDatabase",
"glue:GetTableVersion",
"glue:GetTables",
"glue:GetTableVersions",
"glue:GetDatabases",
"glue:GetTable",
"glue:BatchGetPartition",
"glue:GetPartitions"
]
resources = [
"arn:aws:glue:${data.aws_region.current_region.name}:${data.aws_caller_identity.current_account.account_id}:table/*/*",
"arn:aws:glue:${data.aws_region.current_region.name}:${data.aws_caller_identity.current_account.account_id}:catalog",
"arn:aws:glue:${data.aws_region.current_region.name}:${data.aws_caller_identity.current_account.account_id}:database/*"
]
},
{
sid = "AllowDescribeOwnStatements"
effect = "Allow"
actions = [
"redshift-data:DescribeStatement"
]
resources = [
"*"
]
}
]
},
{
suffix = "sagemaker-executor",
iam_policy_statements = [
{
sid = "AllowIAMPassRoleToSageMakerExecutionRole"
effect = "Allow"
actions = ["iam:PassRole"]
resources = [
"arn:aws:iam::${data.aws_caller_identity.current_account.account_id}:role/aws-${var.environment}-${var.application_name}-sagemaker-executor*"
]
conditions : [
{
test = "StringEquals"
variable = "iam:PassedToService"
values = ["sagemaker.amazonaws.com"]
}
]
},
{
sid = "AllowReadOnlyS3BucketGetObjectToSageMakerExecutionRole"
effect = "Allow"
actions = ["s3:GetObject"]
resources = [
"arn:aws:s3:::${var.environment}-${var.application_name}-example-export-bucket/*",
"arn:aws:s3::${var.environment}-${var.application_name}-example-artefact-bucket/*",
"arn:aws:s3:::${var.environment}-${var.application_name}-example-output-bucket/*"
]
},
{
sid = "AllowReadOnlyS3ListBucketToSageMakerExecutionRole"
effect = "Allow"
actions = ["s3:ListBucket"]
resources = [
"arn:aws:s3:::-${var.environment}-${var.application_name}-example-export-bucket",
"arn:aws:s3:::-${var.environment}-${var.application_name}-example-artefact-bucket",
"arn:aws:s3:::-${var.environment}-${var.application_name}-example-output-bucket"
]
},
{
sid = "AllowReadWriteAccessFor${title(var.environment)}S3Bucket"
effect = "Allow"
actions = [
"s3:PutObject",
"s3:DeleteObject",
"s3:AbortMultipartUpload"
]
resources = [
"arn:aws:s3:::${var.environment}-${var.application_name}-example-artefact-bucket/*",
"arn:aws:s3:::${var.environment}-${var.application_name}-example-output-bucket/*"
]
},
{
sid = "AllowSageMakerAccessTo${title(var.environment)}SageMakerExecutionRole"
effect = "Allow"
actions = [
"sagemaker:DescribeModelPackage*",
"sagemaker:ListModelPackageGroups",
"sagemaker:ListModelPackages",
"sagemaker:CreateModel",
"sagemaker:CreatePipeline",
"sagemaker:DescribePipeline",
"sagemaker:CreateProcessingJob",
"sagemaker:CreateTransformJob",
"sagemaker:AddTags",
"sagemaker:DescribeProcessingJob",
"sagemaker:DescribeTransformJob",
"sagemaker:DescribeModel",
"sagemaker:ListPipelineExecutions",
"sagemaker:ListPipelineExecutionSteps",
"sagemaker:UpdatePipeline",
"sagemaker:StopProcessingJob",
"sagemaker:StopTransformJob"
]
resources = [
"arn:aws:sagemaker:${data.aws_region.current_region.name}:${data.aws_caller_identity.current_account.account_id}:*"
]
},
{
sid = "AllowCloudWatchAccessToSageMakerExecutionRole"
effect = "Allow"
actions = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
resources = [
"arn:aws:logs:${data.aws_region.current_region.name}:${data.aws_caller_identity.current_account.account_id}:*"
]
}
],
assume_policy_principles : [{
type = "AWS"
identifiers = ["arn:aws:iam::${data.aws_caller_identity.current_account.account_id}:role/github-actions-role"]
},
{
type = "Service"
identifiers = ["sagemaker.amazonaws.com"]
conditions : [{
test = "StringEquals"
variable = "AWS:SourceAccount"
values = [data.aws_caller_identity.current_account.account_id]
}]
}
]
}
]
}
22 changes: 22 additions & 0 deletions examples/iam_role/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.61.0"
}
}
required_version = "~> 1.5.0"
}

provider "aws" {
region = "eu-west-2"
}

module "iam_role" {
source = "github.com/sudoblark/sudoblark.terraform.module.aws.iam_role?ref=1.0.0"

application_name = var.application_name
environment = var.environment
raw_iam_roles = local.raw_iam_roles

}
15 changes: 15 additions & 0 deletions examples/iam_role/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
variable "environment" {
description = "Which environment this is being instantiated in."
type = string
validation {
condition = contains(["dev", "test", "prod"], var.environment)
error_message = "Must be either dev, test or prod"
}
default = "prod"
}

variable "application_name" {
description = "Name of the application utilising the resource resource."
type = string
default = "demo-app"
}
11 changes: 11 additions & 0 deletions iam_policy.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
resource "aws_iam_policy" "policy" {
for_each = { for role in var.raw_iam_roles : role.suffix => role }

name = format(lower("aws-${var.environment}-${var.application_name}-%s-policy"), each.value["suffix"])
path = each.value["path"]
policy = data.aws_iam_policy_document.attached_policies[each.value["suffix"]].json

depends_on = [
data.aws_iam_policy_document.attached_policies
]
}
50 changes: 50 additions & 0 deletions iam_policy_document.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
data "aws_iam_policy_document" "attached_policies" {
for_each = { for role in var.raw_iam_roles : role.suffix => role }

dynamic "statement" {
for_each = each.value["iam_policy_statements"]

content {
sid = statement.value["sid"]
actions = statement.value["actions"]
resources = statement.value["resources"]

dynamic "condition" {
for_each = statement.value["conditions"]

content {
test = condition.value["test"]
variable = condition.value["variable"]
values = condition.value["values"]
}
}
}
}
}

data "aws_iam_policy_document" "assume_policies" {
for_each = { for role in var.raw_iam_roles : role.suffix => role }

dynamic "statement" {
for_each = length(each.value["assume_policy_principles"]) > 0 ? each.value["assume_policy_principles"] : []

content {
actions = ["sts:AssumeRole"]
principals {
type = statement.value["type"]
identifiers = statement.value["identifiers"]
}

dynamic "condition" {
for_each = statement.value["conditions"]

content {
test = condition.value["test"]
variable = condition.value["variable"]
values = condition.value["values"]
}
}
}
}
}

Loading

0 comments on commit d186c04

Please sign in to comment.