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

feat: new AWS IAM module #82

Open
wants to merge 11 commits into
base: rc
Choose a base branch
from
70 changes: 70 additions & 0 deletions modules/iam/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Palo Alto Networks IAM Module for AWS

One instance of the module is designed to create one policy.
It supports policies for following use cases:
* VM-Series
* S3 based bootstrap
* Lambda
* spokes with managed AWS SSM
* custom policy

## Usage

## Reference
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
### Requirements

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

### Providers

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

### Modules

No modules.

### Resources

| Name | Type |
|------|------|
| [aws_iam_instance_profile.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource |
| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource |
| [aws_iam_role_policy_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_caller_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
| [aws_iam_policy_document.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_partition.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source |

### Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_aws_s3_bucket"></a> [aws\_s3\_bucket](#input\_aws\_s3\_bucket) | Name of the s3 bucket, that is required and used for<pre>var.create_bootrap_policy</pre>. | `string` | `null` | no |
| <a name="input_create_bootrap_policy"></a> [create\_bootrap\_policy](#input\_create\_bootrap\_policy) | Create a pre-defined bootstrap policy. | `bool` | `false` | no |
| <a name="input_create_instance_profile"></a> [create\_instance\_profile](#input\_create\_instance\_profile) | Create an instance profile. | `bool` | `false` | no |
| <a name="input_create_lambda_policy"></a> [create\_lambda\_policy](#input\_create\_lambda\_policy) | Create a pre-defined lambda policies for ASG. | `bool` | `false` | no |
| <a name="input_create_role"></a> [create\_role](#input\_create\_role) | Create a dedicated role creation of pre-defined policies. | `bool` | `true` | no |
| <a name="input_create_vmseries_policy"></a> [create\_vmseries\_policy](#input\_create\_vmseries\_policy) | Create a pre-defined vmseries policy. | `bool` | `false` | no |
| <a name="input_custom_policy"></a> [custom\_policy](#input\_custom\_policy) | A custom lambda policy. Multi-statement is supported.<br>Basic example:<pre>statement1 = {<br> sid = "1"<br> effect = "Allow"<br> actions = [<br> "logs:CreateLogGroup",<br> "logs:CreateLogStream",<br> "logs:PutLogEvents"<br> ]<br> resources = [<br> "arn:*:logs:*:*:*"<br> ]<br> }<br> statement2 = {<br> sid = "2"<br> effect = "Allow"<br> actions = [<br> "ec2:AllocateAddress",<br> "ec2:AssociateAddress",<br> "ec2:AttachNetworkInterface",<br> "ec2:CreateNetworkInterface",<br> "ec2:CreateTags",<br> "ec2:DescribeAddresses",<br> "ec2:DescribeInstances",<br> "ec2:DescribeNetworkInterfaces",<br> "ec2:DescribeTags",<br> "ec2:DescribeSubnets",<br> "ec2:DeleteNetworkInterface",<br> "ec2:DeleteTags",<br> "ec2:DetachNetworkInterface",<br> "ec2:DisassociateAddress",<br> "ec2:ModifyNetworkInterfaceAttribute",<br> "ec2:ReleaseAddress",<br> "autoscaling:CompleteLifecycleAction",<br> "autoscaling:DescribeAutoScalingGroups",<br> "elasticloadbalancing:RegisterTargets",<br> "elasticloadbalancing:DeregisterTargets"<br> ]<br><br> resources = ["*"]<br><br> condition = {<br> test = "StringEquals"<br> variable = "aws:ResourceTag/Owner"<br> values = "user1"<br> }<br> }</pre> | <pre>map(object({<br> sid = string<br> effect = string<br> actions = list(string)<br> resources = list(string)<br> condition = optional(object({<br> test = string<br> variable = string<br> values = list(string)<br> }))<br> }))</pre> | `null` | no |
| <a name="input_delicense_ssm_param_name"></a> [delicense\_ssm\_param\_name](#input\_delicense\_ssm\_param\_name) | It is required for IAM de-licensing permission IAM settings.<br>Secure string in Parameter Store with value in below format:<pre>{"username":"ACCOUNT","password":"PASSWORD","panorama1":"IP_ADDRESS1","panorama2":"IP_ADDRESS2","license_manager":"LICENSE_MANAGER_NAME"}"</pre>the format can either be the plain name in case you store it without hierarchy or with a "/" in case you store in in a hierarchy | `string` | `null` | no |
| <a name="input_global_tags"></a> [global\_tags](#input\_global\_tags) | Global tags configured for all provisioned resources. | `map(any)` | n/a | yes |
| <a name="input_name_prefix"></a> [name\_prefix](#input\_name\_prefix) | Prefix used in names for the resources. (IAM Role, Instance Profile) | `string` | n/a | yes |
| <a name="input_policy_arn"></a> [policy\_arn](#input\_policy\_arn) | The AWS or Customer managed policy arn. It should be used for spoke VM scenario using the AWS managed<pre>arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore</pre>policy. | `string` | `null` | no |
| <a name="input_principal_role"></a> [principal\_role](#input\_principal\_role) | The type of entity that can take actions in AWS. | `string` | `"ec2.amazonaws.com"` | no |
| <a name="input_profile_instance_name"></a> [profile\_instance\_name](#input\_profile\_instance\_name) | A profile instance name. | `string` | `null` | no |
| <a name="input_region"></a> [region](#input\_region) | AWS region where SSM or CloudWatch is located. | `string` | n/a | yes |
| <a name="input_role_name"></a> [role\_name](#input\_role\_name) | A role name, required for the service. | `string` | n/a | yes |

### Outputs

| Name | Description |
|------|-------------|
| <a name="output_iam_role"></a> [iam\_role](#output\_iam\_role) | The role used for policies. |
| <a name="output_instance_profile"></a> [instance\_profile](#output\_instance\_profile) | The instance profile created for VM. |
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
233 changes: 233 additions & 0 deletions modules/iam/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
### IAM ROLES AND POLICIES ###

data "aws_caller_identity" "this" {}

data "aws_partition" "this" {}

locals {
account_id = data.aws_caller_identity.this.account_id
delicense_param = try(startswith(var.delicense_ssm_param_name, "/") ? var.delicense_ssm_param_name : "/${var.delicense_ssm_param_name}", null)

lambda_execute_policy = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
lambda_execute_policy = {
lambda_execute_policies = {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, from AWS perspective - it is a one policy with many statements

statement1 = {
lstadnik marked this conversation as resolved.
Show resolved Hide resolved
sid = "1"
effect = "Allow"
actions = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
resources = [
"arn:${data.aws_partition.this.partition}:logs:*:*:*"
]
}
statement2 = {
sid = "2"
effect = "Allow"
actions = [
"ec2:AllocateAddress",
"ec2:AssociateAddress",
"ec2:AttachNetworkInterface",
"ec2:CreateNetworkInterface",
"ec2:CreateTags",
"ec2:DescribeAddresses",
"ec2:DescribeInstances",
"ec2:DescribeNetworkInterfaces",
"ec2:DescribeTags",
"ec2:DescribeSubnets",
"ec2:DeleteNetworkInterface",
"ec2:DeleteTags",
"ec2:DetachNetworkInterface",
"ec2:DisassociateAddress",
"ec2:ModifyNetworkInterfaceAttribute",
"ec2:ReleaseAddress",
"autoscaling:CompleteLifecycleAction",
"autoscaling:DescribeAutoScalingGroups",
"elasticloadbalancing:RegisterTargets",
"elasticloadbalancing:DeregisterTargets"
]

resources = ["*"]

condition = {
test = "StringEquals"
variable = "aws:ResourceTag/Owner"
values = [var.global_tags["Owner"]]
}
}
statement3 = {
sid = "3"
effect = "Allow"
actions = [
"ec2:DescribeAddresses",
"ec2:DescribeInstances",
"ec2:DescribeNetworkInterfaces",
"ec2:DescribeTags",
"ec2:DescribeSubnets",
]

resources = ["*"]
}
statement4 = {
sid = "4"
effect = "Allow"
actions = [
"kms:GenerateDataKey*",
"kms:Decrypt",
"kms:CreateGrant"
]

resources = ["*"]

condition = {
test = "StringEquals"
variable = "aws:ResourceTag/Owner"
values = [var.global_tags["Owner"]]
}
}
}

lambda_delicense_policy = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
lambda_delicense_policy = {
lambda_delicense_policies = {

statement1 = {
sid = "1"
effect = "Allow"
actions = [
"ssm:DescribeParameters",
"ssm:GetParametersByPath",
"ssm:GetParameter",
"ssm:GetParameterHistory"
]

resources = [
"arn:${data.aws_partition.this.partition}:ssm:${var.region}:${local.account_id}:parameter${local.delicense_param}"
]
}
}

vmseries_policy = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
vmseries_policy = {
vmseries_policies = {

statement1 = {
sid = "1"
effect = "Allow"
actions = [
"cloudwatch:PutMetricData",
"cloudwatch:GetMetricData",
"cloudwatch:ListMetrics"
]

resources = [
"*"
]
}
statement2 = {
sid = "2"
effect = "Allow"
actions = [
"cloudwatch:PutMetricAlarm",
"cloudwatch:DescribeAlarms"
]

resources = [
"arn:${data.aws_partition.this.partition}:cloudwatch:${var.region}:${data.aws_caller_identity.this.account_id}:alarm:*"
]
}
}

bootstrap_policy = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
bootstrap_policy = {
bootstrap_policies = {

statement1 = {
sid = "1"
effect = "Allow"
actions = ["s3:GetObject"]
resources = ["arn:${data.aws_partition.this.partition}:s3:::${var.aws_s3_bucket}"]
}
statement2 = {
sid = "2"
effect = "Allow"
actions = ["s3:GetObject"]
resources = ["arn:${data.aws_partition.this.partition}:s3:::${var.aws_s3_bucket}/*"]
}
}

aws_policies = {
"custom" = {
enable = var.custom_policy == null ? false : true
definition = try(var.custom_policy, null)
},
"lambda_execute" = {
enable = var.create_lambda_policy ? true : false
definition = try(local.lambda_execute_policy, null)
lstadnik marked this conversation as resolved.
Show resolved Hide resolved
},
"lambda_delicense" = {
enable = var.create_lambda_policy && var.delicense_ssm_param_name != null ? true : false
definition = try(local.lambda_delicense_policy, null)
},
"vmseries" = {
enable = var.create_vmseries_policy
definition = try(local.vmseries_policy, null)
},
"bootstrap" = {
enable = var.create_bootrap_policy && var.aws_s3_bucket != null ? true : false
definition = try(local.bootstrap_policy, null)
}
}
}

data "aws_iam_policy_document" "this" {
for_each = { for k, v in local.aws_policies : k => v if v.enable == true }

dynamic "statement" {
for_each = each.value.definition
content {
sid = statement.value["sid"]
effect = statement.value["effect"]
resources = statement.value["resources"]
actions = statement.value["actions"]
dynamic "condition" {
for_each = lookup(statement.value, "condition", {}) != {} ? [1] : []
content {
test = try(statement.value["condition"]["test"], null)
variable = try(statement.value["condition"]["variable"], null)
values = try(statement.value["condition"]["values"], null)
}
}
}
}
}

resource "aws_iam_role" "this" {
count = var.create_role ? 1 : 0
lstadnik marked this conversation as resolved.
Show resolved Hide resolved
name = "${var.name_prefix}${var.role_name}"
assume_role_policy = <<-EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "${var.principal_role}"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}

resource "aws_iam_role_policy_attachment" "this" {
count = var.policy_arn == null ? 0 : 1
lstadnik marked this conversation as resolved.
Show resolved Hide resolved
role = aws_iam_role.this[0].name
policy_arn = var.policy_arn
}

resource "aws_iam_role_policy" "this" {
for_each = data.aws_iam_policy_document.this

name = "${var.name_prefix}${each.key}"
role = aws_iam_role.this[0].id
policy = each.value.json
}

resource "aws_iam_instance_profile" "this" {
count = var.create_instance_profile ? 1 : 0
lstadnik marked this conversation as resolved.
Show resolved Hide resolved
name = "${var.name_prefix}${var.profile_instance_name}"
role = aws_iam_role.this[0].name
}
11 changes: 11 additions & 0 deletions modules/iam/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package iam

import (
"testing"

"github.com/PaloAltoNetworks/terraform-modules-swfw-tests-skeleton/pkg/testskeleton"
)

func TestValidate(t *testing.T) {
testskeleton.ValidateCode(t, nil)
}
9 changes: 9 additions & 0 deletions modules/iam/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
output "instance_profile" {
description = "The instance profile created for VM."
value = var.create_instance_profile ? aws_iam_instance_profile.this : null
}

output "iam_role" {
description = "The role used for policies."
value = var.create_role ? aws_iam_role.this : null
}
Loading