initial commit
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
<!-- This readme file is generated with terraform-docs -->
|
||||
## Requirements
|
||||
|
||||
No requirements.
|
||||
|
||||
## Providers
|
||||
|
||||
| Name | Version |
|
||||
|------|---------|
|
||||
| aws | n/a |
|
||||
|
||||
## Modules
|
||||
|
||||
No modules.
|
||||
|
||||
## Resources
|
||||
|
||||
| Name | Type |
|
||||
|------|------|
|
||||
| [aws_kms_alias.allpurpose](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource |
|
||||
| [aws_kms_alias.backup](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource |
|
||||
| [aws_kms_alias.database](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource |
|
||||
| [aws_kms_alias.log](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource |
|
||||
| [aws_kms_alias.notify](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource |
|
||||
| [aws_kms_alias.secret](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource |
|
||||
| [aws_kms_alias.storage](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource |
|
||||
| [aws_kms_key.allpurpose](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource |
|
||||
| [aws_kms_key.backup](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource |
|
||||
| [aws_kms_key.database](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource |
|
||||
| [aws_kms_key.eks_ebs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource |
|
||||
| [aws_kms_key.log](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource |
|
||||
| [aws_kms_key.notify](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource |
|
||||
| [aws_kms_key.secret](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource |
|
||||
| [aws_kms_key.storage](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource |
|
||||
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
|
||||
| [aws_iam_policy_document.UseOfKeyByAll](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
|
||||
| [aws_iam_policy_document.base](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
|
||||
| [aws_iam_policy_document.eksebs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
|
||||
| [aws_iam_policy_document.log](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
|
||||
| [aws_iam_policy_document.notify](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
|
||||
| [aws_iam_policy_document.rds](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
|
||||
| [aws_iam_policy_document.storage](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
|
||||
| [aws_iam_role.asg-service-linked-role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_role) | data source |
|
||||
| [aws_region.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |
|
||||
|
||||
## Inputs
|
||||
|
||||
| Name | Description | Type | Default | Required |
|
||||
|------|-------------|------|---------|:--------:|
|
||||
| bypass\_policy\_lockout\_safety\_check | A flag to indicate whether to bypass the key policy lockout safety check. Setting this value to true increases the risk that the KMS key becomes unmanageable | `bool` | `false` | no |
|
||||
| create-allpurpose-key | Create a CMK for general use | `bool` | n/a | yes |
|
||||
| create-backup-key | Create a CMK for use with AWS backup | `bool` | n/a | yes |
|
||||
| create-database-key | Create a CMK for use with databases such as RDS, DynamoDB, Redis | `bool` | n/a | yes |
|
||||
| create-eksebs-key | Create a CMK for use with ENS volumes on EKS nodes | `bool` | n/a | yes |
|
||||
| create-log-key | Create a CMK for use with logging such as CloudwatchLogs and Cloudtrail | `bool` | n/a | yes |
|
||||
| create-notify-key | Create a CMK for use with notification and events | `bool` | n/a | yes |
|
||||
| create-secret-key | Create a CMK for use with secretsmanager | `bool` | n/a | yes |
|
||||
| create-storage-key | Create a CMK for use with storage such as EBS, S3, EFS | `bool` | n/a | yes |
|
||||
| customer\_master\_key\_spec | Specifies whether the key contains a symmetric key or an asymmetric key pair and the encryption algorithms or signing algorithms that the key supports. Valid values: `SYMMETRIC_DEFAULT`, `RSA_2048`, `RSA_3072`, `RSA_4096`, `HMAC_256`, `ECC_NIST_P256`, `ECC_NIST_P384`, `ECC_NIST_P521`, or `ECC_SECG_P256K1`. Defaults to `SYMMETRIC_DEFAULT` | `string` | `"SYMMETRIC_DEFAULT"` | no |
|
||||
| deletion\_window\_in\_days | The waiting period, specified in number of days. After the waiting period ends, AWS KMS deletes the KMS key. If you specify a value, it must be between `7` and `30`, inclusive. If you do not specify a value, it defaults to `30` | `number` | `30` | no |
|
||||
| description | The description of the key as viewed in AWS console | `string` | `null` | no |
|
||||
| enable\_default\_policy | Specifies whether to enable the default key policy. Defaults to `true` | `bool` | `true` | no |
|
||||
| enable\_key\_rotation | Specifies whether key rotation is enabled. Defaults to `true` | `bool` | `true` | no |
|
||||
| grants | A map of grant definitions to create | `any` | `{}` | no |
|
||||
| is\_enabled | Specifies whether the key is enabled. Defaults to `true` | `bool` | `true` | no |
|
||||
| key\_administrator\_arn | IAM user/group/role with highest permissions. If none is specified, access will be granted to this account | `string` | `null` | no |
|
||||
| key\_usage | Specifies the intended use of the key. Valid values: `ENCRYPT_DECRYPT` or `SIGN_VERIFY`. Defaults to `ENCRYPT_DECRYPT` | `string` | `"ENCRYPT_DECRYPT"` | no |
|
||||
| multi\_region | Indicates whether the KMS key is a multi-Region (`true`) or regional (`false`) key. Defaults to `false` | `bool` | `false` | no |
|
||||
| name-prefix | Assign a name prefix for key alias | `string` | `null` | no |
|
||||
| policy | A valid policy JSON document. Although this is a key policy, not an IAM policy, an `aws_iam_policy_document`, in the form that designates a principal, can be used | `string` | `null` | no |
|
||||
| rotation\_period\_in\_days | rotation period in days | `number` | `365` | no |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Name | Description |
|
||||
|------|-------------|
|
||||
| cmks | Customer managed KMS key arns |
|
||||
|
||||
---
|
||||
## Authorship
|
||||
This module was developed by xpk.
|
||||
@@ -0,0 +1,21 @@
|
||||
data "aws_caller_identity" "this" {}
|
||||
|
||||
module "example-keys" {
|
||||
source = "../"
|
||||
|
||||
name-prefix = "xpk"
|
||||
deletion_window_in_days = 7
|
||||
create-allpurpose-key = true
|
||||
create-backup-key = true
|
||||
create-database-key = true
|
||||
create-log-key = true
|
||||
create-notify-key = true
|
||||
create-secret-key = true
|
||||
create-storage-key = true
|
||||
create-eksebs-key = true
|
||||
key_administrator_arn = data.aws_caller_identity.this.arn
|
||||
}
|
||||
|
||||
output "cmks" {
|
||||
value = module.example-keys.cmks.*
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
provider "aws" {
|
||||
region = "ap-east-1"
|
||||
|
||||
default_tags {
|
||||
tags = {
|
||||
Environment = "lab"
|
||||
Project = "iac"
|
||||
Application = "terraform"
|
||||
Owner = "ken2026"
|
||||
TerraformDir = "${reverse(split("/", path.cwd))[1]}/${reverse(split("/", path.cwd))[0]}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output "last-updated" {
|
||||
value = timestamp()
|
||||
}
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.13.0"
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = "~> 5.100.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,629 @@
|
||||
/*
|
||||
Module to create the following CMKs:
|
||||
- allpurpose
|
||||
- storage
|
||||
- database
|
||||
- secrets
|
||||
- backup
|
||||
- log
|
||||
- notify
|
||||
*/
|
||||
|
||||
data "aws_region" "this" {}
|
||||
data "aws_caller_identity" "current" {}
|
||||
|
||||
# Keys
|
||||
resource "aws_kms_key" "allpurpose" {
|
||||
count = var.create-allpurpose-key ? 1 : 0
|
||||
description = "All purpose customer-managed KMS key"
|
||||
enable_key_rotation = var.enable_key_rotation
|
||||
rotation_period_in_days = var.rotation_period_in_days
|
||||
is_enabled = var.is_enabled
|
||||
policy = data.aws_iam_policy_document.UseOfKeyByAll.json
|
||||
deletion_window_in_days = var.deletion_window_in_days
|
||||
customer_master_key_spec = "SYMMETRIC_DEFAULT"
|
||||
key_usage = "ENCRYPT_DECRYPT"
|
||||
multi_region = var.multi_region
|
||||
bypass_policy_lockout_safety_check = var.bypass_policy_lockout_safety_check
|
||||
}
|
||||
|
||||
resource "aws_kms_key" "storage" {
|
||||
count = var.create-storage-key ? 1 : 0
|
||||
description = "Customer-managed KMS key for encrypting cloud storage such as EBS and S3"
|
||||
enable_key_rotation = var.enable_key_rotation
|
||||
rotation_period_in_days = var.rotation_period_in_days
|
||||
is_enabled = var.is_enabled
|
||||
policy = data.aws_iam_policy_document.storage.json
|
||||
deletion_window_in_days = var.deletion_window_in_days
|
||||
customer_master_key_spec = "SYMMETRIC_DEFAULT"
|
||||
key_usage = "ENCRYPT_DECRYPT"
|
||||
multi_region = var.multi_region
|
||||
bypass_policy_lockout_safety_check = var.bypass_policy_lockout_safety_check
|
||||
}
|
||||
|
||||
# Key use for EBS volumes on EKS nodes
|
||||
resource "aws_kms_key" "eks_ebs" {
|
||||
count = var.create-eksebs-key ? 1 : 0
|
||||
description = "CMK for use with ENS volumes on EKS nodes"
|
||||
enable_key_rotation = var.enable_key_rotation
|
||||
rotation_period_in_days = var.rotation_period_in_days
|
||||
is_enabled = var.is_enabled
|
||||
policy = data.aws_iam_policy_document.eksebs[0].json
|
||||
deletion_window_in_days = var.deletion_window_in_days
|
||||
customer_master_key_spec = "SYMMETRIC_DEFAULT"
|
||||
key_usage = "ENCRYPT_DECRYPT"
|
||||
multi_region = var.multi_region
|
||||
bypass_policy_lockout_safety_check = var.bypass_policy_lockout_safety_check
|
||||
}
|
||||
|
||||
resource "aws_kms_key" "database" {
|
||||
count = var.create-database-key ? 1 : 0
|
||||
description = "Customer-managed KMS key for encrypting cloud databases such as RDS and Elasticache"
|
||||
enable_key_rotation = var.enable_key_rotation
|
||||
rotation_period_in_days = var.rotation_period_in_days
|
||||
is_enabled = var.is_enabled
|
||||
policy = data.aws_iam_policy_document.rds.json
|
||||
deletion_window_in_days = var.deletion_window_in_days
|
||||
customer_master_key_spec = "SYMMETRIC_DEFAULT"
|
||||
key_usage = "ENCRYPT_DECRYPT"
|
||||
multi_region = var.multi_region
|
||||
bypass_policy_lockout_safety_check = var.bypass_policy_lockout_safety_check
|
||||
}
|
||||
|
||||
resource "aws_kms_key" "secret" {
|
||||
count = var.create-secret-key ? 1 : 0
|
||||
description = "Customer-managed KMS key for encrypting secrets"
|
||||
enable_key_rotation = var.enable_key_rotation
|
||||
rotation_period_in_days = var.rotation_period_in_days
|
||||
is_enabled = var.is_enabled
|
||||
policy = data.aws_iam_policy_document.UseOfKeyByAll.json
|
||||
deletion_window_in_days = var.deletion_window_in_days
|
||||
customer_master_key_spec = "SYMMETRIC_DEFAULT"
|
||||
key_usage = "ENCRYPT_DECRYPT"
|
||||
multi_region = var.multi_region
|
||||
bypass_policy_lockout_safety_check = var.bypass_policy_lockout_safety_check
|
||||
}
|
||||
|
||||
resource "aws_kms_key" "backup" {
|
||||
count = var.create-backup-key ? 1 : 0
|
||||
description = "Customer-managed KMS key for encrypting backup data"
|
||||
enable_key_rotation = var.enable_key_rotation
|
||||
rotation_period_in_days = var.rotation_period_in_days
|
||||
is_enabled = var.is_enabled
|
||||
policy = data.aws_iam_policy_document.UseOfKeyByAll.json
|
||||
deletion_window_in_days = var.deletion_window_in_days
|
||||
customer_master_key_spec = "SYMMETRIC_DEFAULT"
|
||||
key_usage = "ENCRYPT_DECRYPT"
|
||||
multi_region = var.multi_region
|
||||
bypass_policy_lockout_safety_check = var.bypass_policy_lockout_safety_check
|
||||
}
|
||||
|
||||
resource "aws_kms_key" "log" {
|
||||
count = var.create-log-key ? 1 : 0
|
||||
description = "Customer-managed KMS key for cloudwatch logs and cloudtrail"
|
||||
enable_key_rotation = var.enable_key_rotation
|
||||
rotation_period_in_days = var.rotation_period_in_days
|
||||
is_enabled = var.is_enabled
|
||||
policy = data.aws_iam_policy_document.log.json
|
||||
deletion_window_in_days = var.deletion_window_in_days
|
||||
customer_master_key_spec = "SYMMETRIC_DEFAULT"
|
||||
key_usage = "ENCRYPT_DECRYPT"
|
||||
multi_region = var.multi_region
|
||||
bypass_policy_lockout_safety_check = var.bypass_policy_lockout_safety_check
|
||||
}
|
||||
|
||||
resource "aws_kms_key" "notify" {
|
||||
count = var.create-notify-key ? 1 : 0
|
||||
description = "Customer-managed KMS key for encrypting notifications"
|
||||
enable_key_rotation = var.enable_key_rotation
|
||||
rotation_period_in_days = var.rotation_period_in_days
|
||||
is_enabled = var.is_enabled
|
||||
policy = data.aws_iam_policy_document.notify.json
|
||||
deletion_window_in_days = var.deletion_window_in_days
|
||||
customer_master_key_spec = "SYMMETRIC_DEFAULT"
|
||||
key_usage = "ENCRYPT_DECRYPT"
|
||||
multi_region = var.multi_region
|
||||
bypass_policy_lockout_safety_check = var.bypass_policy_lockout_safety_check
|
||||
}
|
||||
|
||||
|
||||
locals {
|
||||
prefix = var.name-prefix == null ? "" : "${var.name-prefix}-"
|
||||
}
|
||||
|
||||
# Key aliases
|
||||
resource "aws_kms_alias" "allpurpose" {
|
||||
count = var.create-allpurpose-key ? 1 : 0
|
||||
name = "alias/${local.prefix}allpurpose"
|
||||
target_key_id = aws_kms_key.allpurpose[0].id
|
||||
}
|
||||
|
||||
resource "aws_kms_alias" "storage" {
|
||||
count = var.create-storage-key ? 1 : 0
|
||||
name = "alias/${local.prefix}storage"
|
||||
target_key_id = aws_kms_key.storage[0].id
|
||||
}
|
||||
|
||||
resource "aws_kms_alias" "database" {
|
||||
count = var.create-database-key ? 1 : 0
|
||||
name = "alias/${local.prefix}database"
|
||||
target_key_id = aws_kms_key.database[0].id
|
||||
}
|
||||
|
||||
resource "aws_kms_alias" "backup" {
|
||||
count = var.create-backup-key ? 1 : 0
|
||||
name = "alias/${local.prefix}backup"
|
||||
target_key_id = aws_kms_key.backup[0].id
|
||||
}
|
||||
|
||||
resource "aws_kms_alias" "secret" {
|
||||
count = var.create-secret-key ? 1 : 0
|
||||
name = "alias/${local.prefix}secret"
|
||||
target_key_id = aws_kms_key.secret[0].id
|
||||
}
|
||||
|
||||
resource "aws_kms_alias" "log" {
|
||||
count = var.create-log-key ? 1 : 0
|
||||
name = "alias/${local.prefix}log"
|
||||
target_key_id = aws_kms_key.log[0].id
|
||||
}
|
||||
|
||||
resource "aws_kms_alias" "notify" {
|
||||
count = var.create-notify-key ? 1 : 0
|
||||
name = "alias/${local.prefix}notify"
|
||||
target_key_id = aws_kms_key.notify[0].id
|
||||
}
|
||||
|
||||
# Policies
|
||||
data "aws_iam_policy_document" "storage" {
|
||||
source_policy_documents = [data.aws_iam_policy_document.base.json]
|
||||
statement {
|
||||
sid = "Allow use by AWS services"
|
||||
effect = "Allow"
|
||||
principals {
|
||||
identifiers = [
|
||||
"delivery.logs.amazonaws.com" # vpc flow log
|
||||
]
|
||||
type = "Service"
|
||||
}
|
||||
actions = [
|
||||
"kms:Encrypt",
|
||||
"kms:Decrypt",
|
||||
"kms:ReEncrypt*",
|
||||
"kms:GenerateDataKey*",
|
||||
"kms:Describe*"
|
||||
]
|
||||
resources = ["*"]
|
||||
}
|
||||
statement {
|
||||
sid = "Allow use of key by s3 and ec2"
|
||||
effect = "Allow"
|
||||
principals {
|
||||
identifiers = [data.aws_caller_identity.current.account_id]
|
||||
type = "AWS"
|
||||
}
|
||||
actions = [
|
||||
"kms:Encrypt",
|
||||
"kms:Decrypt",
|
||||
"kms:ReEncrypt*",
|
||||
"kms:GenerateDataKey*",
|
||||
"kms:Describe*"
|
||||
]
|
||||
resources = ["*"]
|
||||
condition {
|
||||
test = "StringEquals"
|
||||
values = [
|
||||
"ec2.${data.aws_region.this.name}.amazonaws.com",
|
||||
"s3.${data.aws_region.this.name}.amazonaws.com"
|
||||
]
|
||||
variable = "kms:ViaService"
|
||||
}
|
||||
}
|
||||
# this needs to be explicitly allowed for users and roles to be able to encrypt and decrypt data
|
||||
statement {
|
||||
sid = "Allow use of key by users and roles in same account"
|
||||
effect = "Allow"
|
||||
principals {
|
||||
identifiers = [data.aws_caller_identity.current.account_id]
|
||||
type = "AWS"
|
||||
}
|
||||
actions = [
|
||||
"kms:Encrypt",
|
||||
"kms:Decrypt",
|
||||
"kms:ReEncrypt*",
|
||||
"kms:GenerateDataKey*",
|
||||
"kms:Describe*"
|
||||
]
|
||||
resources = ["*"]
|
||||
}
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "rds" {
|
||||
source_policy_documents = [data.aws_iam_policy_document.base.json]
|
||||
statement {
|
||||
sid = "Allow use by AWS services"
|
||||
effect = "Allow"
|
||||
principals {
|
||||
identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"]
|
||||
type = "AWS"
|
||||
}
|
||||
actions = [
|
||||
"kms:Encrypt",
|
||||
"kms:Decrypt",
|
||||
"kms:ReEncrypt*",
|
||||
"kms:GenerateDataKey*",
|
||||
"kms:Describe*"
|
||||
]
|
||||
resources = ["*"]
|
||||
condition {
|
||||
test = "StringEquals"
|
||||
values = [
|
||||
"rds.${data.aws_region.this.id}.amazonaws.com",
|
||||
"elasticache.${data.aws_region.this.id}.amazonaws.com",
|
||||
"dax.${data.aws_region.this.id}.amazonaws.com"
|
||||
]
|
||||
variable = "kms:ViaService"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data "aws_iam_role" "asg-service-linked-role" {
|
||||
count = var.create-eksebs-key ? 1 : 0
|
||||
name = "AWSServiceRoleForAutoScaling"
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "eksebs" {
|
||||
count = var.create-eksebs-key ? 1 : 0
|
||||
source_policy_documents = [data.aws_iam_policy_document.base.json]
|
||||
statement {
|
||||
sid = "Allow use by EKS"
|
||||
effect = "Allow"
|
||||
principals {
|
||||
identifiers = [
|
||||
data.aws_iam_role.asg-service-linked-role[0].arn
|
||||
]
|
||||
type = "AWS"
|
||||
}
|
||||
actions = [
|
||||
"kms:Encrypt",
|
||||
"kms:Decrypt",
|
||||
"kms:ReEncrypt*",
|
||||
"kms:GenerateDataKey*",
|
||||
"kms:Describe*"
|
||||
]
|
||||
resources = ["*"]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "Allow grants by EKS"
|
||||
effect = "Allow"
|
||||
principals {
|
||||
identifiers = [
|
||||
data.aws_iam_role.asg-service-linked-role[0].arn
|
||||
]
|
||||
type = "AWS"
|
||||
}
|
||||
actions = [
|
||||
"kms:RevokeGrant",
|
||||
"kms:ListGrants",
|
||||
"kms:CreateGrant"
|
||||
]
|
||||
resources = ["*"]
|
||||
condition {
|
||||
test = "Bool"
|
||||
values = ["true"]
|
||||
variable = "kms:GrantIsForAWSResource"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "notify" {
|
||||
source_policy_documents = [data.aws_iam_policy_document.base.json]
|
||||
statement {
|
||||
sid = "Allow use by AWS services"
|
||||
effect = "Allow"
|
||||
principals {
|
||||
identifiers = [
|
||||
"logs.${data.aws_region.this.id}.amazonaws.com", # cloudwatch log groups
|
||||
"backup.amazonaws.com", # Notifications
|
||||
"events.amazonaws.com" # Notifications
|
||||
]
|
||||
type = "Service"
|
||||
}
|
||||
actions = [
|
||||
"kms:Encrypt",
|
||||
"kms:Decrypt",
|
||||
"kms:ReEncrypt*",
|
||||
"kms:GenerateDataKey*",
|
||||
"kms:Describe*"
|
||||
]
|
||||
resources = ["*"]
|
||||
}
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "log" {
|
||||
source_policy_documents = [data.aws_iam_policy_document.base.json]
|
||||
statement {
|
||||
sid = "Allow use by AWS services"
|
||||
effect = "Allow"
|
||||
principals {
|
||||
identifiers = [
|
||||
"logs.${data.aws_region.this.id}.amazonaws.com", # cloudwatch log groups
|
||||
"apigateway.${data.aws_region.this.id}.amazonaws.com", # api gateway
|
||||
"delivery.logs.amazonaws.com", # vpc flow log
|
||||
"cloudtrail.amazonaws.com" # cloudtrail
|
||||
]
|
||||
type = "Service"
|
||||
}
|
||||
actions = [
|
||||
"kms:Encrypt",
|
||||
"kms:Decrypt",
|
||||
"kms:ReEncrypt*",
|
||||
"kms:GenerateDataKey*",
|
||||
"kms:Describe*"
|
||||
]
|
||||
resources = ["*"]
|
||||
}
|
||||
}
|
||||
|
||||
# allow all entities in this account to perform encryption and decryption
|
||||
data "aws_iam_policy_document" "UseOfKeyByAll" {
|
||||
source_policy_documents = [data.aws_iam_policy_document.base.json]
|
||||
statement {
|
||||
sid = "AllowUseOfKey"
|
||||
effect = "Allow"
|
||||
principals {
|
||||
identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"]
|
||||
type = "AWS"
|
||||
}
|
||||
actions = [
|
||||
"kms:Encrypt",
|
||||
"kms:Decrypt",
|
||||
"kms:ReEncrypt*",
|
||||
"kms:GenerateDataKey*",
|
||||
"kms:DescribeKey"
|
||||
]
|
||||
resources = ["*"]
|
||||
condition {
|
||||
test = "StringEquals"
|
||||
values = [data.aws_caller_identity.current.account_id]
|
||||
variable = "aws:PrincipalAccount"
|
||||
}
|
||||
}
|
||||
statement {
|
||||
sid = "AllowAttachmentOfPersistentResources"
|
||||
effect = "Allow"
|
||||
principals {
|
||||
identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"]
|
||||
type = "AWS"
|
||||
}
|
||||
actions = [
|
||||
"kms:CreateGrant",
|
||||
"kms:ListGrants",
|
||||
"kms:RevokeGrant"
|
||||
]
|
||||
resources = ["*"]
|
||||
condition {
|
||||
test = "Bool"
|
||||
values = ["true"]
|
||||
variable = "kms:GrantIsForAWSResource"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# base policies allowing full access to key admin and read access to all
|
||||
data "aws_iam_policy_document" "base" {
|
||||
source_policy_documents = [jsonencode(
|
||||
{
|
||||
"Id" : "CmkBasePolicy",
|
||||
"Version" : "2012-10-17",
|
||||
"Statement" : [
|
||||
{
|
||||
"Sid" : "ReadPermissonForAll",
|
||||
"Effect" : "Allow",
|
||||
"Principal" : {
|
||||
"AWS" : "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
|
||||
},
|
||||
"Action" : [
|
||||
"kms:DescribeKey",
|
||||
"kms:GetKeyPolicy",
|
||||
"kms:ListAliases",
|
||||
"kms:ListKeyPolicies",
|
||||
"kms:ListKeys",
|
||||
"kms:ListResourceTags"
|
||||
],
|
||||
"Resource" : "*"
|
||||
},
|
||||
{
|
||||
"Sid" : "KeyAdministratorsAccess",
|
||||
"Effect" : "Allow",
|
||||
"Principal" : {
|
||||
"AWS" : var.key_administrator_arn != null ? var.key_administrator_arn : "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
|
||||
},
|
||||
"Action" : [
|
||||
"kms:Create*",
|
||||
"kms:Describe*",
|
||||
"kms:Enable*",
|
||||
"kms:List*",
|
||||
"kms:Put*",
|
||||
"kms:Update*",
|
||||
"kms:Revoke*",
|
||||
"kms:Disable*",
|
||||
"kms:Get*",
|
||||
"kms:Delete*",
|
||||
"kms:TagResource",
|
||||
"kms:UntagResource",
|
||||
"kms:ScheduleKeyDeletion",
|
||||
"kms:CancelKeyDeletion",
|
||||
"kms:RotateKeyOnDemand"
|
||||
],
|
||||
"Resource" : "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
)]
|
||||
}
|
||||
|
||||
# data "aws_iam_policy_document" "this" {
|
||||
# source_policy_documents = var.source_policy_documents
|
||||
# override_policy_documents = var.override_policy_documents
|
||||
#
|
||||
# # Default policy - account wide access to all key operations
|
||||
# dynamic "statement" {
|
||||
# for_each = var.enable_default_policy ? [1] : []
|
||||
#
|
||||
# content {
|
||||
# sid = "Default"
|
||||
# actions = ["kms:*"]
|
||||
# resources = ["*"]
|
||||
#
|
||||
# principals {
|
||||
# type = "AWS"
|
||||
# identifiers = ["arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:root"]
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# # Key owner - all key operations
|
||||
# dynamic "statement" {
|
||||
# for_each = var.enable_default_policy ? [1] : []
|
||||
#
|
||||
# content {
|
||||
# sid = "KeyOwner"
|
||||
# actions = ["kms:*"]
|
||||
# resources = ["*"]
|
||||
#
|
||||
# principals {
|
||||
# type = "AWS"
|
||||
# identifiers = var.key_owners
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# # Key administrators - https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-default-allow-administrators
|
||||
# dynamic "statement" {
|
||||
# for_each = length(var.key_administrators) > 0 ? [1] : []
|
||||
#
|
||||
# content {
|
||||
# sid = "KeyAdministration"
|
||||
# actions = [
|
||||
# "kms:Create*",
|
||||
# "kms:Describe*",
|
||||
# "kms:Enable*",
|
||||
# "kms:List*",
|
||||
# "kms:Put*",
|
||||
# "kms:Update*",
|
||||
# "kms:Revoke*",
|
||||
# "kms:Disable*",
|
||||
# "kms:Get*",
|
||||
# "kms:Delete*",
|
||||
# "kms:TagResource",
|
||||
# "kms:UntagResource",
|
||||
# "kms:ScheduleKeyDeletion",
|
||||
# "kms:CancelKeyDeletion",
|
||||
# ]
|
||||
# resources = ["*"]
|
||||
#
|
||||
# principals {
|
||||
# type = "AWS"
|
||||
# identifiers = var.key_administrators
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# # Key users - https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-default-allow-users
|
||||
# dynamic "statement" {
|
||||
# for_each = length(var.key_users) > 0 ? [1] : []
|
||||
#
|
||||
# content {
|
||||
# sid = "KeyUsage"
|
||||
# actions = [
|
||||
# "kms:Encrypt",
|
||||
# "kms:Decrypt",
|
||||
# "kms:ReEncrypt*",
|
||||
# "kms:GenerateDataKey*",
|
||||
# "kms:DescribeKey",
|
||||
# ]
|
||||
# resources = ["*"]
|
||||
#
|
||||
# principals {
|
||||
# type = "AWS"
|
||||
# identifiers = var.key_users
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# # Key service users - https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-service-integration
|
||||
# dynamic "statement" {
|
||||
# for_each = length(var.key_service_users) > 0 ? [1] : []
|
||||
#
|
||||
# content {
|
||||
# sid = "KeyServiceUsage"
|
||||
# actions = [
|
||||
# "kms:CreateGrant",
|
||||
# "kms:ListGrants",
|
||||
# "kms:RevokeGrant",
|
||||
# ]
|
||||
# resources = ["*"]
|
||||
#
|
||||
# principals {
|
||||
# type = "AWS"
|
||||
# identifiers = var.key_service_users
|
||||
# }
|
||||
#
|
||||
# condition {
|
||||
# test = "Bool"
|
||||
# variable = "kms:GrantIsForAWSResource"
|
||||
# values = [true]
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# # Key cryptographic operations - https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-default.html#key-policy-users-crypto
|
||||
# dynamic "statement" {
|
||||
# for_each = length(var.key_symmetric_encryption_users) > 0 ? [1] : []
|
||||
#
|
||||
# content {
|
||||
# sid = "KeySymmetricEncryption"
|
||||
# actions = [
|
||||
# "kms:Decrypt",
|
||||
# "kms:DescribeKey",
|
||||
# "kms:Encrypt",
|
||||
# "kms:GenerateDataKey*",
|
||||
# "kms:ReEncrypt*",
|
||||
# ]
|
||||
# resources = ["*"]
|
||||
#
|
||||
# principals {
|
||||
# type = "AWS"
|
||||
# identifiers = var.key_symmetric_encryption_users
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
|
||||
# ################################################################################
|
||||
# # Grant
|
||||
# ################################################################################
|
||||
#
|
||||
# resource "aws_kms_grant" "this" {
|
||||
# for_each = { for k, v in var.grants : k => v if var.create }
|
||||
#
|
||||
# name = try(each.value.name, each.key)
|
||||
# key_id = aws_kms_key.this[0].key_id
|
||||
# grantee_principal = each.value.grantee_principal
|
||||
# operations = each.value.operations
|
||||
#
|
||||
# dynamic "constraints" {
|
||||
# for_each = length(lookup(each.value, "constraints", {})) == 0 ? [] : [each.value.constraints]
|
||||
#
|
||||
# content {
|
||||
# encryption_context_equals = try(constraints.value.encryption_context_equals, null)
|
||||
# encryption_context_subset = try(constraints.value.encryption_context_subset, null)
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# retiring_principal = try(each.value.retiring_principal, null)
|
||||
# grant_creation_tokens = try(each.value.grant_creation_tokens, null)
|
||||
# retire_on_delete = try(each.value.retire_on_delete, null)
|
||||
# }
|
||||
@@ -0,0 +1,33 @@
|
||||
output "cmks" {
|
||||
description = "Customer managed KMS key arns"
|
||||
value = {
|
||||
backup = {
|
||||
alias = aws_kms_alias.backup.*.name
|
||||
arn = aws_kms_key.backup.*.arn
|
||||
},
|
||||
database = {
|
||||
alias = aws_kms_alias.database.*.name
|
||||
arn = aws_kms_key.database.*.arn
|
||||
},
|
||||
allpurpose = {
|
||||
alias = aws_kms_alias.allpurpose.*.name
|
||||
arn = aws_kms_key.allpurpose.*.arn
|
||||
},
|
||||
secret = {
|
||||
alias = aws_kms_alias.secret.*.name
|
||||
arn = aws_kms_key.secret.*.arn
|
||||
},
|
||||
log = {
|
||||
alias = aws_kms_alias.log.*.name
|
||||
arn = aws_kms_key.log.*.arn
|
||||
},
|
||||
notify = {
|
||||
alias = aws_kms_alias.notify.*.name
|
||||
arn = aws_kms_key.notify.*.arn
|
||||
},
|
||||
storage = {
|
||||
alias = aws_kms_alias.storage.*.name
|
||||
arn = aws_kms_key.storage.*.arn
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
variable "create-allpurpose-key" {
|
||||
description = "Create a CMK for general use"
|
||||
type = bool
|
||||
}
|
||||
|
||||
variable "create-storage-key" {
|
||||
description = "Create a CMK for use with storage such as EBS, S3, EFS"
|
||||
type = bool
|
||||
}
|
||||
|
||||
variable "create-eksebs-key" {
|
||||
description = "Create a CMK for use with ENS volumes on EKS nodes"
|
||||
type = bool
|
||||
}
|
||||
|
||||
variable "create-database-key" {
|
||||
description = "Create a CMK for use with databases such as RDS, DynamoDB, Redis"
|
||||
type = bool
|
||||
}
|
||||
|
||||
variable "create-backup-key" {
|
||||
description = "Create a CMK for use with AWS backup"
|
||||
type = bool
|
||||
}
|
||||
|
||||
variable "create-secret-key" {
|
||||
description = "Create a CMK for use with secretsmanager"
|
||||
type = bool
|
||||
}
|
||||
|
||||
variable "create-log-key" {
|
||||
description = "Create a CMK for use with logging such as CloudwatchLogs and Cloudtrail"
|
||||
type = bool
|
||||
}
|
||||
|
||||
variable "create-notify-key" {
|
||||
description = "Create a CMK for use with notification and events"
|
||||
type = bool
|
||||
}
|
||||
|
||||
variable "name-prefix" {
|
||||
description = "Assign a name prefix for key alias"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "bypass_policy_lockout_safety_check" {
|
||||
description = "A flag to indicate whether to bypass the key policy lockout safety check. Setting this value to true increases the risk that the KMS key becomes unmanageable"
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "customer_master_key_spec" {
|
||||
description = "Specifies whether the key contains a symmetric key or an asymmetric key pair and the encryption algorithms or signing algorithms that the key supports. Valid values: `SYMMETRIC_DEFAULT`, `RSA_2048`, `RSA_3072`, `RSA_4096`, `HMAC_256`, `ECC_NIST_P256`, `ECC_NIST_P384`, `ECC_NIST_P521`, or `ECC_SECG_P256K1`. Defaults to `SYMMETRIC_DEFAULT`"
|
||||
type = string
|
||||
default = "SYMMETRIC_DEFAULT"
|
||||
}
|
||||
|
||||
variable "deletion_window_in_days" {
|
||||
description = "The waiting period, specified in number of days. After the waiting period ends, AWS KMS deletes the KMS key. If you specify a value, it must be between `7` and `30`, inclusive. If you do not specify a value, it defaults to `30`"
|
||||
type = number
|
||||
default = 30
|
||||
}
|
||||
|
||||
variable "description" {
|
||||
description = "The description of the key as viewed in AWS console"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "enable_key_rotation" {
|
||||
description = "Specifies whether key rotation is enabled. Defaults to `true`"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "is_enabled" {
|
||||
description = "Specifies whether the key is enabled. Defaults to `true`"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "key_usage" {
|
||||
description = "Specifies the intended use of the key. Valid values: `ENCRYPT_DECRYPT` or `SIGN_VERIFY`. Defaults to `ENCRYPT_DECRYPT`"
|
||||
type = string
|
||||
default = "ENCRYPT_DECRYPT"
|
||||
}
|
||||
|
||||
variable "multi_region" {
|
||||
description = "Indicates whether the KMS key is a multi-Region (`true`) or regional (`false`) key. Defaults to `false`"
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "policy" {
|
||||
description = "A valid policy JSON document. Although this is a key policy, not an IAM policy, an `aws_iam_policy_document`, in the form that designates a principal, can be used"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "enable_default_policy" {
|
||||
description = "Specifies whether to enable the default key policy. Defaults to `true`"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "key_administrator_arn" {
|
||||
description = "IAM user/group/role with highest permissions. If none is specified, access will be granted to this account"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Grant
|
||||
################################################################################
|
||||
|
||||
variable "grants" {
|
||||
description = "A map of grant definitions to create"
|
||||
type = any
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "rotation_period_in_days" {
|
||||
description = "rotation period in days"
|
||||
type = number
|
||||
default = 365
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<!-- This readme file is generated with terraform-docs -->
|
||||
# SecretRotationReminder
|
||||
Deploy lambda function which takes secret rotation event from secretsmanager
|
||||
and send reminders to users using SNS.
|
||||
This function can be used by any number of secrets
|
||||
Secret ARN is obtained from the secretsmanager event
|
||||
|
||||
This function overrides the blueprint function from AWS. Instead of rotating the secret value,
|
||||
it sends a reminder to user who will manually rotate the secret.
|
||||
|
||||
## Requirements
|
||||
|
||||
No requirements.
|
||||
|
||||
## Providers
|
||||
|
||||
| Name | Version |
|
||||
|------|---------|
|
||||
| archive | n/a |
|
||||
| aws | n/a |
|
||||
|
||||
## Modules
|
||||
|
||||
No modules.
|
||||
|
||||
## Resources
|
||||
|
||||
| Name | Type |
|
||||
|------|------|
|
||||
| [aws_cloudwatch_log_group.rotation-reminder](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource |
|
||||
| [aws_iam_policy.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
|
||||
| [aws_iam_role.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
|
||||
| [aws_iam_role_policy_attachment.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
|
||||
| [aws_lambda_function.rotation-reminder](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function) | resource |
|
||||
| [aws_lambda_permission.rotation-reminder](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource |
|
||||
| [aws_security_group.rotation-reminder](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
|
||||
| [aws_sns_topic.reminder](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic) | resource |
|
||||
| [aws_sns_topic_subscription.reminder](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic_subscription) | resource |
|
||||
| [archive_file.payload](https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file) | data source |
|
||||
| [aws_iam_policy_document.assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
|
||||
| [aws_subnet.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet) | data source |
|
||||
|
||||
## Inputs
|
||||
|
||||
| Name | Description | Type | Default | Required |
|
||||
|------|-------------|------|---------|:--------:|
|
||||
| lambda-subnet-ids | List of subnets to place lambda function | `list(string)` | n/a | yes |
|
||||
| logs-cmk-arn | ARN of cloudwatch logs encryption CMK | `string` | n/a | yes |
|
||||
| prefix | Resource prefix. e.g. whk1-bea-icc-mbk | `string` | n/a | yes |
|
||||
| rotation-reminder-recipients | SNS recipients for secret rotation reminders | `list(string)` | n/a | yes |
|
||||
| sns-cmk-arn | ARN of SNS encryption CMK | `string` | n/a | yes |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Name | Description |
|
||||
|------|-------------|
|
||||
| function-arn | n/a |
|
||||
|
||||
---
|
||||
## Authorship
|
||||
This module was developed by Rackspace.
|
||||
@@ -0,0 +1,17 @@
|
||||
module "secret-rotation-reminder" {
|
||||
source = "../"
|
||||
sns-cmk-arn = "arn:aws:kms:ap-east-1:111122223333:key/e13912c7-54d3-4d77-9a52-c482bcaf3209"
|
||||
logs-cmk-arn = "arn:aws:kms:ap-east-1:111122223333:key/143d0178-8ad2-458b-90b3-0fa6b3e62fc4"
|
||||
rotation-reminder-recipients = ["foo@bar.local"]
|
||||
prefix = "prod-project1"
|
||||
lambda-subnet-ids = ["subnet-001", "subnet-002"]
|
||||
}
|
||||
|
||||
resource "aws_secretsmanager_secret_rotation" "secret-rotation" {
|
||||
secret_id = "your-secret-id"
|
||||
rotation_lambda_arn = module.secret-rotation-reminder.function-arn
|
||||
rotate_immediately = false
|
||||
rotation_rules {
|
||||
automatically_after_days = 365
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* # SecretRotationReminder
|
||||
* Deploy lambda function which takes secret rotation event from secretsmanager
|
||||
* and send reminders to users using SNS.
|
||||
* This function can be used by any number of secrets
|
||||
* Secret ARN is obtained from the secretsmanager event
|
||||
*
|
||||
* This function overrides the blueprint function from AWS. Instead of rotating the secret value,
|
||||
* it sends a reminder to user who will manually rotate the secret.
|
||||
*/
|
||||
|
||||
resource "aws_sns_topic" "reminder" {
|
||||
name = "${var.prefix}-SecretRotationReminder"
|
||||
kms_master_key_id = var.sns-cmk-arn
|
||||
}
|
||||
|
||||
resource "aws_sns_topic_subscription" "reminder" {
|
||||
for_each = toset(var.rotation-reminder-recipients)
|
||||
topic_arn = aws_sns_topic.reminder.arn
|
||||
protocol = "email"
|
||||
endpoint = each.value
|
||||
}
|
||||
|
||||
data "archive_file" "payload" {
|
||||
type = "zip"
|
||||
source_file = "${path.module}/rotation_reminder.py"
|
||||
output_path = "payload.zip"
|
||||
}
|
||||
|
||||
data "aws_subnet" "this" {
|
||||
id = var.lambda-subnet-ids[0]
|
||||
}
|
||||
|
||||
resource "aws_security_group" "rotation-reminder" {
|
||||
name = "${var.prefix}-SecretRotationReminder"
|
||||
description = "Allow access to VPC endpoint"
|
||||
vpc_id = data.aws_subnet.this.vpc_id
|
||||
|
||||
egress {
|
||||
description = "Access to VPC endpoints"
|
||||
from_port = 443
|
||||
to_port = 443
|
||||
protocol = "tcp"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_lambda_function" "rotation-reminder" {
|
||||
function_name = "${var.prefix}-SecretRotationReminder"
|
||||
description = "Sends secret rotation reminder"
|
||||
role = aws_iam_role.lambda.arn
|
||||
handler = "rotation_reminder.lambda_handler"
|
||||
filename = data.archive_file.payload.output_path
|
||||
source_code_hash = data.archive_file.payload.output_base64sha256
|
||||
runtime = "python3.13"
|
||||
timeout = 180
|
||||
vpc_config {
|
||||
subnet_ids = var.lambda-subnet-ids
|
||||
security_group_ids = [aws_security_group.rotation-reminder.id]
|
||||
}
|
||||
|
||||
environment {
|
||||
variables = {
|
||||
SNS_TOPIC_ARN = aws_sns_topic.reminder.arn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_lambda_permission" "rotation-reminder" {
|
||||
statement_id = "SecretRotationReminderPermission"
|
||||
action = "lambda:InvokeFunction"
|
||||
function_name = aws_lambda_function.rotation-reminder.function_name
|
||||
principal = "secretsmanager.amazonaws.com"
|
||||
# this function should be allowed to send reminders for all secrets # source_arn = module.mock-secret.secret_arn
|
||||
}
|
||||
|
||||
|
||||
resource "aws_cloudwatch_log_group" "rotation-reminder" {
|
||||
name = "/aws/lambda/whk1-bea-icc-obk-SecretRotationReminder"
|
||||
retention_in_days = 400 # intentionally set to longer than 1 year as rotation may happen yearly
|
||||
kms_key_id = var.logs-cmk-arn
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "lambda" {
|
||||
name = "${var.prefix}-SecretRotationReminderFunctionRole"
|
||||
assume_role_policy = data.aws_iam_policy_document.assume_role.json
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "lambda" {
|
||||
for_each = { for k, v in [
|
||||
"arn:aws:iam::aws:policy/AWSLambdaExecute",
|
||||
aws_iam_policy.lambda.arn
|
||||
] : k => v }
|
||||
role = aws_iam_role.lambda.name
|
||||
policy_arn = each.value
|
||||
}
|
||||
|
||||
resource "aws_iam_policy" "lambda" {
|
||||
name_prefix = "SecretRotationPolicy"
|
||||
policy = jsonencode(
|
||||
{
|
||||
"Version" : "2012-10-17",
|
||||
"Statement" : [
|
||||
{
|
||||
"Sid" : "AllowAccessToSecretSnsVpc",
|
||||
"Effect" : "Allow",
|
||||
"Action" : [
|
||||
"SNS:Publish",
|
||||
"secretsmanager:DescribeSecret",
|
||||
"secretsmanager:ListSecretVersionIds",
|
||||
"secretsmanager:UpdateSecretVersionStage",
|
||||
"secretsmanager:GetSecretValue",
|
||||
"secretsmanager:PutSecretValue",
|
||||
"ec2:CreateNetworkInterface",
|
||||
"ec2:DescribeNetworkInterfaces",
|
||||
"ec2:DescribeSubnets",
|
||||
"ec2:DeleteNetworkInterface",
|
||||
"ec2:AssignPrivateIpAddresses",
|
||||
"ec2:UnassignPrivateIpAddresses",
|
||||
"ec2:DescribeSecurityGroups",
|
||||
"ec2:DescribeSubnets",
|
||||
"ec2:DescribeVpcs",
|
||||
"ec2:GetSecurityGroupsForVpc"
|
||||
],
|
||||
"Resource" : "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "assume_role" {
|
||||
statement {
|
||||
effect = "Allow"
|
||||
|
||||
principals {
|
||||
type = "Service"
|
||||
identifiers = ["lambda.amazonaws.com"]
|
||||
}
|
||||
|
||||
actions = ["sts:AssumeRole"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
output function-arn {
|
||||
value = aws_lambda_function.rotation-reminder.arn
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
"""
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
This function is designed to send a reminder through SNS
|
||||
when secretsmanager automatic rotation event arrives. It
|
||||
does not rotate any secret.
|
||||
|
||||
Secretsmanager initiate rotation in 4 steps [1], and some of
|
||||
these steps must be implemented even if the purpose of this
|
||||
function is not to rotate any secret.
|
||||
|
||||
[1] https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotate-secrets_lambda-functions.html
|
||||
"""
|
||||
import boto3
|
||||
import os
|
||||
|
||||
|
||||
# Read sns topic from lambda environment variable
|
||||
SNS_TOPIC_ARN = os.environ['SNS_TOPIC_ARN']
|
||||
|
||||
# lambda handler
|
||||
def lambda_handler(event, context):
|
||||
# debug use
|
||||
# print(f"DEBUG: Event received by Lambda: {event}")
|
||||
|
||||
secret_id = event['SecretId']
|
||||
token = event['ClientRequestToken']
|
||||
step = event['Step']
|
||||
|
||||
# Secretsmanager sends 4 rotation events, but we will only use the createSecret event
|
||||
# and the finishSecret event
|
||||
if step == "createSecret":
|
||||
# send notification and create a new secret from existing secret
|
||||
send_notification(secret_id, token)
|
||||
elif step == "finishSecret":
|
||||
# set new secret with version AWSCURRENT
|
||||
swap_current_version(secret_id, token)
|
||||
else:
|
||||
print(f"Steps other than createSecret and finishSecret will be ignored: {step}")
|
||||
|
||||
def send_notification(secret_id, token):
|
||||
print(f"Clone secret and send notification for {secret_id}")
|
||||
sm_client = boto3.client('secretsmanager')
|
||||
"""
|
||||
A new secret version is required by rotation workflow
|
||||
We will simply copy existing version to a new version
|
||||
label it AWSPENDING
|
||||
"""
|
||||
orig_secret = sm_client.get_secret_value(
|
||||
SecretId=secret_id,
|
||||
VersionStage='AWSCURRENT'
|
||||
)['SecretString']
|
||||
|
||||
response = sm_client.put_secret_value(
|
||||
SecretId=secret_id,
|
||||
ClientRequestToken=token,
|
||||
SecretString=orig_secret,
|
||||
VersionStages=['AWSPENDING']
|
||||
)
|
||||
print(f"Retrieved existing secret and saved it as AWSPENDING. Version id is {response['VersionId']}")
|
||||
|
||||
# Send out reminder about secret rotation
|
||||
sns_client = boto3.client('sns')
|
||||
sns_client.publish(
|
||||
TopicArn=SNS_TOPIC_ARN,
|
||||
Message=f"""Hello Cloud Operation team,
|
||||
|
||||
The following secret is due for update. Please perform the following steps to rotate the secret:
|
||||
|
||||
{secret_id}
|
||||
|
||||
1. Open the secret on secretsmanager.
|
||||
2. Click the Retrieve secret value botton to reveal the Edit button.
|
||||
3. Click on Edit and change the secret string to a new one. Click save to commit your change.
|
||||
4. Update the password for the underlying resource (e.g. redis or rds)
|
||||
5. Optionally update your application configuration with the new credential if it does not fetch from secretsmanager automatically
|
||||
|
||||
""",
|
||||
Subject='Secret rotation reminder for ' + secret_id.split(":")[6]
|
||||
)
|
||||
print(f"Notification sent to {SNS_TOPIC_ARN}")
|
||||
|
||||
def swap_current_version(secret_id, token):
|
||||
sm_client = boto3.client('secretsmanager')
|
||||
metadata = sm_client.describe_secret(SecretId=secret_id)
|
||||
pending_version_id = None
|
||||
current_version_id = None
|
||||
for version in metadata["VersionIdsToStages"]:
|
||||
if "AWSCURRENT" in metadata["VersionIdsToStages"][version]:
|
||||
current_version_id = version
|
||||
elif "AWSPENDING" in metadata["VersionIdsToStages"][version]:
|
||||
pending_version_id = version
|
||||
|
||||
print(f"Remove {current_version_id} from AWSCURRENT and point AWSCURRENT to {pending_version_id}")
|
||||
sm_client.update_secret_version_stage(
|
||||
SecretId=secret_id,
|
||||
VersionStage="AWSCURRENT",
|
||||
MoveToVersionId=pending_version_id,
|
||||
RemoveFromVersionId=current_version_id
|
||||
)
|
||||
|
||||
print(f"Remove AWSPENDING from {pending_version_id}")
|
||||
sm_client.update_secret_version_stage(
|
||||
SecretId=secret_id,
|
||||
VersionStage='AWSPENDING',
|
||||
RemoveFromVersionId=pending_version_id
|
||||
)
|
||||
@@ -0,0 +1,24 @@
|
||||
variable "rotation-reminder-recipients" {
|
||||
type = list(string)
|
||||
description = "SNS recipients for secret rotation reminders"
|
||||
}
|
||||
|
||||
variable "sns-cmk-arn" {
|
||||
type = string
|
||||
description = "ARN of SNS encryption CMK"
|
||||
}
|
||||
|
||||
variable "prefix" {
|
||||
type = string
|
||||
description = "Resource prefix. e.g. whk1-bea-icc-mbk"
|
||||
}
|
||||
|
||||
variable "lambda-subnet-ids" {
|
||||
type = list(string)
|
||||
description = "List of subnets to place lambda function"
|
||||
}
|
||||
|
||||
variable "logs-cmk-arn" {
|
||||
type = string
|
||||
description = "ARN of cloudwatch logs encryption CMK"
|
||||
}
|
||||
@@ -0,0 +1,601 @@
|
||||
##################################################################################
|
||||
#
|
||||
# Conformance Pack:
|
||||
# Operational Best Practices for CIS AWS Foundations Benchmark Level 1
|
||||
#
|
||||
# This conformance pack helps verify compliance with CIS AWS Foundations Benchmark Level 1 requirements.
|
||||
#
|
||||
# See Parameters section for names and descriptions of required parameters.
|
||||
#
|
||||
##################################################################################
|
||||
|
||||
Parameters:
|
||||
AccessKeysRotatedParamMaxAccessKeyAge:
|
||||
Default: '90'
|
||||
Type: String
|
||||
IamPasswordPolicyParamMaxPasswordAge:
|
||||
Default: '90'
|
||||
Type: String
|
||||
IamPasswordPolicyParamMinimumPasswordLength:
|
||||
Default: '14'
|
||||
Type: String
|
||||
IamPasswordPolicyParamPasswordReusePrevention:
|
||||
Default: '24'
|
||||
Type: String
|
||||
IamPasswordPolicyParamRequireLowercaseCharacters:
|
||||
Default: 'true'
|
||||
Type: String
|
||||
IamPasswordPolicyParamRequireNumbers:
|
||||
Default: 'true'
|
||||
Type: String
|
||||
IamPasswordPolicyParamRequireSymbols:
|
||||
Default: 'true'
|
||||
Type: String
|
||||
IamPasswordPolicyParamRequireUppercaseCharacters:
|
||||
Default: 'true'
|
||||
Type: String
|
||||
IamPolicyInUseParamPolicyARN:
|
||||
Default: arn:aws:iam::aws:policy/AWSSupportAccess
|
||||
Type: String
|
||||
IamUserUnusedCredentialsCheckParamMaxCredentialUsageAge:
|
||||
Default: '45'
|
||||
Type: String
|
||||
RestrictedIncomingTrafficParamBlockedPort3:
|
||||
Default: '3389'
|
||||
Type: String
|
||||
S3AccountLevelPublicAccessBlocksPeriodicParamBlockPublicAcls:
|
||||
Default: 'True'
|
||||
Type: String
|
||||
S3AccountLevelPublicAccessBlocksPeriodicParamBlockPublicPolicy:
|
||||
Default: 'True'
|
||||
Type: String
|
||||
S3AccountLevelPublicAccessBlocksPeriodicParamIgnorePublicAcls:
|
||||
Default: 'True'
|
||||
Type: String
|
||||
S3AccountLevelPublicAccessBlocksPeriodicParamRestrictPublicBuckets:
|
||||
Default: 'True'
|
||||
Type: String
|
||||
S3BucketVersioningEnabledParamIsMfaDeleteEnabled:
|
||||
Default: 'TRUE'
|
||||
Type: String
|
||||
Resources:
|
||||
AccessKeysRotated:
|
||||
Properties:
|
||||
ConfigRuleName: access-keys-rotated
|
||||
InputParameters:
|
||||
maxAccessKeyAge:
|
||||
Fn::If:
|
||||
- accessKeysRotatedParamMaxAccessKeyAge
|
||||
- Ref: AccessKeysRotatedParamMaxAccessKeyAge
|
||||
- Ref: AWS::NoValue
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: ACCESS_KEYS_ROTATED
|
||||
Type: AWS::Config::ConfigRule
|
||||
CloudTrailCloudWatchLogsEnabled:
|
||||
Properties:
|
||||
ConfigRuleName: cloud-trail-cloud-watch-logs-enabled
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: CLOUD_TRAIL_CLOUD_WATCH_LOGS_ENABLED
|
||||
Type: AWS::Config::ConfigRule
|
||||
Ec2EbsEncryptionByDefault:
|
||||
Properties:
|
||||
ConfigRuleName: ec2-ebs-encryption-by-default
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: EC2_EBS_ENCRYPTION_BY_DEFAULT
|
||||
Type: AWS::Config::ConfigRule
|
||||
EncryptedVolumes:
|
||||
Properties:
|
||||
ConfigRuleName: encrypted-volumes
|
||||
Scope:
|
||||
ComplianceResourceTypes:
|
||||
- AWS::EC2::Volume
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: ENCRYPTED_VOLUMES
|
||||
Type: AWS::Config::ConfigRule
|
||||
IamNoInlinePolicyCheck:
|
||||
Properties:
|
||||
ConfigRuleName: iam-no-inline-policy-check
|
||||
Scope:
|
||||
ComplianceResourceTypes:
|
||||
- AWS::IAM::User
|
||||
- AWS::IAM::Role
|
||||
- AWS::IAM::Group
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: IAM_NO_INLINE_POLICY_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
IamPasswordPolicy:
|
||||
Properties:
|
||||
ConfigRuleName: iam-password-policy
|
||||
InputParameters:
|
||||
MaxPasswordAge:
|
||||
Fn::If:
|
||||
- iamPasswordPolicyParamMaxPasswordAge
|
||||
- Ref: IamPasswordPolicyParamMaxPasswordAge
|
||||
- Ref: AWS::NoValue
|
||||
MinimumPasswordLength:
|
||||
Fn::If:
|
||||
- iamPasswordPolicyParamMinimumPasswordLength
|
||||
- Ref: IamPasswordPolicyParamMinimumPasswordLength
|
||||
- Ref: AWS::NoValue
|
||||
PasswordReusePrevention:
|
||||
Fn::If:
|
||||
- iamPasswordPolicyParamPasswordReusePrevention
|
||||
- Ref: IamPasswordPolicyParamPasswordReusePrevention
|
||||
- Ref: AWS::NoValue
|
||||
RequireLowercaseCharacters:
|
||||
Fn::If:
|
||||
- iamPasswordPolicyParamRequireLowercaseCharacters
|
||||
- Ref: IamPasswordPolicyParamRequireLowercaseCharacters
|
||||
- Ref: AWS::NoValue
|
||||
RequireNumbers:
|
||||
Fn::If:
|
||||
- iamPasswordPolicyParamRequireNumbers
|
||||
- Ref: IamPasswordPolicyParamRequireNumbers
|
||||
- Ref: AWS::NoValue
|
||||
RequireSymbols:
|
||||
Fn::If:
|
||||
- iamPasswordPolicyParamRequireSymbols
|
||||
- Ref: IamPasswordPolicyParamRequireSymbols
|
||||
- Ref: AWS::NoValue
|
||||
RequireUppercaseCharacters:
|
||||
Fn::If:
|
||||
- iamPasswordPolicyParamRequireUppercaseCharacters
|
||||
- Ref: IamPasswordPolicyParamRequireUppercaseCharacters
|
||||
- Ref: AWS::NoValue
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: IAM_PASSWORD_POLICY
|
||||
Type: AWS::Config::ConfigRule
|
||||
IamPolicyInUse:
|
||||
Properties:
|
||||
ConfigRuleName: iam-policy-in-use
|
||||
InputParameters:
|
||||
policyARN:
|
||||
Fn::If:
|
||||
- iamPolicyInUseParamPolicyARN
|
||||
- Ref: IamPolicyInUseParamPolicyARN
|
||||
- Ref: AWS::NoValue
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: IAM_POLICY_IN_USE
|
||||
Type: AWS::Config::ConfigRule
|
||||
IamPolicyNoStatementsWithAdminAccess:
|
||||
Properties:
|
||||
ConfigRuleName: iam-policy-no-statements-with-admin-access
|
||||
Scope:
|
||||
ComplianceResourceTypes:
|
||||
- AWS::IAM::Policy
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: IAM_POLICY_NO_STATEMENTS_WITH_ADMIN_ACCESS
|
||||
Type: AWS::Config::ConfigRule
|
||||
IamRootAccessKeyCheck:
|
||||
Properties:
|
||||
ConfigRuleName: iam-root-access-key-check
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: IAM_ROOT_ACCESS_KEY_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
IamUserGroupMembershipCheck:
|
||||
Properties:
|
||||
ConfigRuleName: iam-user-group-membership-check
|
||||
Scope:
|
||||
ComplianceResourceTypes:
|
||||
- AWS::IAM::User
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: IAM_USER_GROUP_MEMBERSHIP_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
IamUserNoPoliciesCheck:
|
||||
Properties:
|
||||
ConfigRuleName: iam-user-no-policies-check
|
||||
Scope:
|
||||
ComplianceResourceTypes:
|
||||
- AWS::IAM::User
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: IAM_USER_NO_POLICIES_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
IamUserUnusedCredentialsCheck:
|
||||
Properties:
|
||||
ConfigRuleName: iam-user-unused-credentials-check
|
||||
InputParameters:
|
||||
maxCredentialUsageAge:
|
||||
Fn::If:
|
||||
- iamUserUnusedCredentialsCheckParamMaxCredentialUsageAge
|
||||
- Ref: IamUserUnusedCredentialsCheckParamMaxCredentialUsageAge
|
||||
- Ref: AWS::NoValue
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: IAM_USER_UNUSED_CREDENTIALS_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
IncomingSshDisabled:
|
||||
Properties:
|
||||
ConfigRuleName: restricted-ssh
|
||||
Scope:
|
||||
ComplianceResourceTypes:
|
||||
- AWS::EC2::SecurityGroup
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: INCOMING_SSH_DISABLED
|
||||
Type: AWS::Config::ConfigRule
|
||||
MfaEnabledForIamConsoleAccess:
|
||||
Properties:
|
||||
ConfigRuleName: mfa-enabled-for-iam-console-access
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: MFA_ENABLED_FOR_IAM_CONSOLE_ACCESS
|
||||
Type: AWS::Config::ConfigRule
|
||||
MultiRegionCloudTrailEnabled:
|
||||
Properties:
|
||||
ConfigRuleName: multi-region-cloudtrail-enabled
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: MULTI_REGION_CLOUD_TRAIL_ENABLED
|
||||
Type: AWS::Config::ConfigRule
|
||||
RdsSnapshotEncrypted:
|
||||
Properties:
|
||||
ConfigRuleName: rds-snapshot-encrypted
|
||||
Scope:
|
||||
ComplianceResourceTypes:
|
||||
- AWS::RDS::DBSnapshot
|
||||
- AWS::RDS::DBClusterSnapshot
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: RDS_SNAPSHOT_ENCRYPTED
|
||||
Type: AWS::Config::ConfigRule
|
||||
RdsStorageEncrypted:
|
||||
Properties:
|
||||
ConfigRuleName: rds-storage-encrypted
|
||||
Scope:
|
||||
ComplianceResourceTypes:
|
||||
- AWS::RDS::DBInstance
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: RDS_STORAGE_ENCRYPTED
|
||||
Type: AWS::Config::ConfigRule
|
||||
RestrictedIncomingTraffic:
|
||||
Properties:
|
||||
ConfigRuleName: restricted-common-ports
|
||||
InputParameters:
|
||||
blockedPort3:
|
||||
Fn::If:
|
||||
- restrictedIncomingTrafficParamBlockedPort3
|
||||
- Ref: RestrictedIncomingTrafficParamBlockedPort3
|
||||
- Ref: AWS::NoValue
|
||||
Scope:
|
||||
ComplianceResourceTypes:
|
||||
- AWS::EC2::SecurityGroup
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: RESTRICTED_INCOMING_TRAFFIC
|
||||
Type: AWS::Config::ConfigRule
|
||||
RootAccountMfaEnabled:
|
||||
Properties:
|
||||
ConfigRuleName: root-account-mfa-enabled
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: ROOT_ACCOUNT_MFA_ENABLED
|
||||
Type: AWS::Config::ConfigRule
|
||||
S3AccountLevelPublicAccessBlocksPeriodic:
|
||||
Properties:
|
||||
ConfigRuleName: s3-account-level-public-access-blocks-periodic
|
||||
InputParameters:
|
||||
BlockPublicAcls:
|
||||
Fn::If:
|
||||
- s3AccountLevelPublicAccessBlocksPeriodicParamBlockPublicAcls
|
||||
- Ref: S3AccountLevelPublicAccessBlocksPeriodicParamBlockPublicAcls
|
||||
- Ref: AWS::NoValue
|
||||
BlockPublicPolicy:
|
||||
Fn::If:
|
||||
- s3AccountLevelPublicAccessBlocksPeriodicParamBlockPublicPolicy
|
||||
- Ref: S3AccountLevelPublicAccessBlocksPeriodicParamBlockPublicPolicy
|
||||
- Ref: AWS::NoValue
|
||||
IgnorePublicAcls:
|
||||
Fn::If:
|
||||
- s3AccountLevelPublicAccessBlocksPeriodicParamIgnorePublicAcls
|
||||
- Ref: S3AccountLevelPublicAccessBlocksPeriodicParamIgnorePublicAcls
|
||||
- Ref: AWS::NoValue
|
||||
RestrictPublicBuckets:
|
||||
Fn::If:
|
||||
- s3AccountLevelPublicAccessBlocksPeriodicParamRestrictPublicBuckets
|
||||
- Ref: S3AccountLevelPublicAccessBlocksPeriodicParamRestrictPublicBuckets
|
||||
- Ref: AWS::NoValue
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: S3_ACCOUNT_LEVEL_PUBLIC_ACCESS_BLOCKS_PERIODIC
|
||||
Type: AWS::Config::ConfigRule
|
||||
S3BucketLevelPublicAccessProhibited:
|
||||
Properties:
|
||||
ConfigRuleName: s3-bucket-level-public-access-prohibited
|
||||
Scope:
|
||||
ComplianceResourceTypes:
|
||||
- AWS::S3::Bucket
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: S3_BUCKET_LEVEL_PUBLIC_ACCESS_PROHIBITED
|
||||
Type: AWS::Config::ConfigRule
|
||||
S3BucketLoggingEnabled:
|
||||
Properties:
|
||||
ConfigRuleName: s3-bucket-logging-enabled
|
||||
Scope:
|
||||
ComplianceResourceTypes:
|
||||
- AWS::S3::Bucket
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: S3_BUCKET_LOGGING_ENABLED
|
||||
Type: AWS::Config::ConfigRule
|
||||
S3BucketPublicReadProhibited:
|
||||
Properties:
|
||||
ConfigRuleName: s3-bucket-public-read-prohibited
|
||||
Scope:
|
||||
ComplianceResourceTypes:
|
||||
- AWS::S3::Bucket
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: S3_BUCKET_PUBLIC_READ_PROHIBITED
|
||||
Type: AWS::Config::ConfigRule
|
||||
S3BucketPublicWriteProhibited:
|
||||
Properties:
|
||||
ConfigRuleName: s3-bucket-public-write-prohibited
|
||||
Scope:
|
||||
ComplianceResourceTypes:
|
||||
- AWS::S3::Bucket
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: S3_BUCKET_PUBLIC_WRITE_PROHIBITED
|
||||
Type: AWS::Config::ConfigRule
|
||||
S3BucketVersioningEnabled:
|
||||
Properties:
|
||||
ConfigRuleName: s3-bucket-versioning-enabled
|
||||
InputParameters:
|
||||
isMfaDeleteEnabled:
|
||||
Fn::If:
|
||||
- s3BucketVersioningEnabledParamIsMfaDeleteEnabled
|
||||
- Ref: S3BucketVersioningEnabledParamIsMfaDeleteEnabled
|
||||
- Ref: AWS::NoValue
|
||||
Scope:
|
||||
ComplianceResourceTypes:
|
||||
- AWS::S3::Bucket
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: S3_BUCKET_VERSIONING_ENABLED
|
||||
Type: AWS::Config::ConfigRule
|
||||
AccountContactDetailsConfigured:
|
||||
Properties:
|
||||
ConfigRuleName: account-contact-details-configured
|
||||
Description: Ensure the contact email and telephone number for AWS accounts are current and map to more than one individual in your organization. Within the My Account section of the console ensure correct information is specified in the Contact Information section.
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: AWS_CONFIG_PROCESS_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
AccountSecurityContactConfigured:
|
||||
Properties:
|
||||
ConfigRuleName: account-security-contact-configured
|
||||
Description: Ensure the contact email and telephone number for the your organizations security team are current. Within the My Account section of the AWS Management Console ensure the correct information is specified in the Security section.
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: AWS_CONFIG_PROCESS_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
AccountSecurityQuestionsConfigured:
|
||||
Properties:
|
||||
ConfigRuleName: account-security-questions-configured
|
||||
Description: Ensure the security questions that can be used to authenticate individuals calling AWS customer service for support are configured. Within the My Account section of the AWS Management Console ensure three security challenge questions are configured.
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: AWS_CONFIG_PROCESS_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
RootAccountRegularUse:
|
||||
Properties:
|
||||
ConfigRuleName: root-account-regular-use
|
||||
Description: Ensure the use of the root account is avoided for everyday tasks. Within IAM, run a credential report to examine when the root user was last used.
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: AWS_CONFIG_PROCESS_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
IAMUserConsoleAndAPIAccessAtCreation:
|
||||
Properties:
|
||||
ConfigRuleName: iam-user-console-and-api-access-at-creation
|
||||
Description: Ensure access keys are not setup during the initial user setup for all IAM users that have a console password. For all IAM users with console access, compare the user 'Creation time` to the Access Key `Created` date.
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: AWS_CONFIG_PROCESS_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
IAMUserSingleAccessKey:
|
||||
Properties:
|
||||
ConfigRuleName: iam-user-single-access-key
|
||||
Description: Ensure there is only one active access key available for any single IAM user. For all IAM users check that there is only one active key used within the Security Credentials tab for each user within IAM.
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: AWS_CONFIG_PROCESS_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
IAMExpiredCertificates:
|
||||
Properties:
|
||||
ConfigRuleName: iam-expired-certificates
|
||||
Description: Ensure that all the expired SSL/TLS certificates stored in IAM are removed. From the command line with the installed AWS CLI run the 'aws iam list-server-certificates' command and determine if there are any expired server certificates.
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: AWS_CONFIG_PROCESS_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
IAMAccessAnalyzerEnabled:
|
||||
Properties:
|
||||
ConfigRuleName: iam-access-analyzer-enabled
|
||||
Description: Ensure that IAM Access analyzer is enabled. Within the IAM section of the console, select Access analyzer and ensure that the STATUS is set to Active.
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: AWS_CONFIG_PROCESS_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
AlarmUnauthorizedAPIcalls:
|
||||
Properties:
|
||||
ConfigRuleName: alarm-unauthorized-api-calls
|
||||
Description: Ensure a log metric filter and an alarm exists for unauthorized API calls.
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: AWS_CONFIG_PROCESS_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
AlarmSignInWithoutMFA:
|
||||
Properties:
|
||||
ConfigRuleName: alarm-sign-in-without-mfa
|
||||
Description: Ensure a log metric filter and an alarm exists for AWS Management Console sign-in without Multi-Factor Authentication (MFA).
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: AWS_CONFIG_PROCESS_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
AlarmRootAccountUse:
|
||||
Properties:
|
||||
ConfigRuleName: alarm-root-account-use
|
||||
Description: Ensure a log metric filter and an alarm exists for usage of the root account.
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: AWS_CONFIG_PROCESS_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
AlarmIAMpolicyChange:
|
||||
Properties:
|
||||
ConfigRuleName: alarm-iam-policy-change
|
||||
Description: Ensure a log metric filter and an alarm exists for IAM policy changes.
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: AWS_CONFIG_PROCESS_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
AlarmCloudtrailConfigChange:
|
||||
Properties:
|
||||
ConfigRuleName: alarm-cloudtrail-config-change
|
||||
Description: Ensure a log metric filter and an alarm exists for AWS CloudTrail configuration changes.
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: AWS_CONFIG_PROCESS_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
AlarmS3BucketPolicyChange:
|
||||
Properties:
|
||||
ConfigRuleName: alarm-s3-bucket-policy-change
|
||||
Description: Ensure a log metric filter and an alarm exists for Amazon S3 bucket policy changes.
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: AWS_CONFIG_PROCESS_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
AlarmVPCNetworkGatewayChange:
|
||||
Properties:
|
||||
ConfigRuleName: alarm-vpc-network-gateway-change
|
||||
Description: Ensure a log metric filter and an alarm exists for changes to network gateways.
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: AWS_CONFIG_PROCESS_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
AlarmVPCroutetableChange:
|
||||
Properties:
|
||||
ConfigRuleName: alarm-vpc-route-table-change
|
||||
Description: Ensure a log metric filter and an alarm exists for route table changes.
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: AWS_CONFIG_PROCESS_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
AlarmVPCChange:
|
||||
Properties:
|
||||
ConfigRuleName: alarm-vpc-change
|
||||
Description: Ensure a log metric filter and an alarm exists for Amazon Virtual Private Cloud (VPC) changes.
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: AWS_CONFIG_PROCESS_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
AlarmOrganizationsChange:
|
||||
Properties:
|
||||
ConfigRuleName: alarm-organizations-change
|
||||
Description: Ensure a log metric filter and an alarm exists for AWS Organizations changes.
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: AWS_CONFIG_PROCESS_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
VPCNetworkACLOpenAdminPorts:
|
||||
Properties:
|
||||
ConfigRuleName: vpc-networkacl-open-admin-ports
|
||||
Description: Ensure no network ACLs allow public ingress to the remote server administration ports. Within the VPC section of the console, ensure there are network ACLs with a source of '0.0.0.0/0' with allowing ports or port ranges including remote server admin ports.
|
||||
Source:
|
||||
Owner: AWS
|
||||
SourceIdentifier: AWS_CONFIG_PROCESS_CHECK
|
||||
Type: AWS::Config::ConfigRule
|
||||
Conditions:
|
||||
accessKeysRotatedParamMaxAccessKeyAge:
|
||||
Fn::Not:
|
||||
- Fn::Equals:
|
||||
- ''
|
||||
- Ref: AccessKeysRotatedParamMaxAccessKeyAge
|
||||
iamPasswordPolicyParamMaxPasswordAge:
|
||||
Fn::Not:
|
||||
- Fn::Equals:
|
||||
- ''
|
||||
- Ref: IamPasswordPolicyParamMaxPasswordAge
|
||||
iamPasswordPolicyParamMinimumPasswordLength:
|
||||
Fn::Not:
|
||||
- Fn::Equals:
|
||||
- ''
|
||||
- Ref: IamPasswordPolicyParamMinimumPasswordLength
|
||||
iamPasswordPolicyParamPasswordReusePrevention:
|
||||
Fn::Not:
|
||||
- Fn::Equals:
|
||||
- ''
|
||||
- Ref: IamPasswordPolicyParamPasswordReusePrevention
|
||||
iamPasswordPolicyParamRequireLowercaseCharacters:
|
||||
Fn::Not:
|
||||
- Fn::Equals:
|
||||
- ''
|
||||
- Ref: IamPasswordPolicyParamRequireLowercaseCharacters
|
||||
iamPasswordPolicyParamRequireNumbers:
|
||||
Fn::Not:
|
||||
- Fn::Equals:
|
||||
- ''
|
||||
- Ref: IamPasswordPolicyParamRequireNumbers
|
||||
iamPasswordPolicyParamRequireSymbols:
|
||||
Fn::Not:
|
||||
- Fn::Equals:
|
||||
- ''
|
||||
- Ref: IamPasswordPolicyParamRequireSymbols
|
||||
iamPasswordPolicyParamRequireUppercaseCharacters:
|
||||
Fn::Not:
|
||||
- Fn::Equals:
|
||||
- ''
|
||||
- Ref: IamPasswordPolicyParamRequireUppercaseCharacters
|
||||
iamPolicyInUseParamPolicyARN:
|
||||
Fn::Not:
|
||||
- Fn::Equals:
|
||||
- ''
|
||||
- Ref: IamPolicyInUseParamPolicyARN
|
||||
iamUserUnusedCredentialsCheckParamMaxCredentialUsageAge:
|
||||
Fn::Not:
|
||||
- Fn::Equals:
|
||||
- ''
|
||||
- Ref: IamUserUnusedCredentialsCheckParamMaxCredentialUsageAge
|
||||
restrictedIncomingTrafficParamBlockedPort3:
|
||||
Fn::Not:
|
||||
- Fn::Equals:
|
||||
- ''
|
||||
- Ref: RestrictedIncomingTrafficParamBlockedPort3
|
||||
s3AccountLevelPublicAccessBlocksPeriodicParamBlockPublicAcls:
|
||||
Fn::Not:
|
||||
- Fn::Equals:
|
||||
- ''
|
||||
- Ref: S3AccountLevelPublicAccessBlocksPeriodicParamBlockPublicAcls
|
||||
s3AccountLevelPublicAccessBlocksPeriodicParamBlockPublicPolicy:
|
||||
Fn::Not:
|
||||
- Fn::Equals:
|
||||
- ''
|
||||
- Ref: S3AccountLevelPublicAccessBlocksPeriodicParamBlockPublicPolicy
|
||||
s3AccountLevelPublicAccessBlocksPeriodicParamIgnorePublicAcls:
|
||||
Fn::Not:
|
||||
- Fn::Equals:
|
||||
- ''
|
||||
- Ref: S3AccountLevelPublicAccessBlocksPeriodicParamIgnorePublicAcls
|
||||
s3AccountLevelPublicAccessBlocksPeriodicParamRestrictPublicBuckets:
|
||||
Fn::Not:
|
||||
- Fn::Equals:
|
||||
- ''
|
||||
- Ref: S3AccountLevelPublicAccessBlocksPeriodicParamRestrictPublicBuckets
|
||||
s3BucketVersioningEnabledParamIsMfaDeleteEnabled:
|
||||
Fn::Not:
|
||||
- Fn::Equals:
|
||||
- ''
|
||||
- Ref: S3BucketVersioningEnabledParamIsMfaDeleteEnabled
|
||||
@@ -0,0 +1,25 @@
|
||||
# Overview
|
||||
This module performs the following tasks:
|
||||
|
||||
- Enable AWS config in all regions
|
||||
- Deploy [CIS1.4 level 1 conformance pack](https://docs.aws.amazon.com/config/latest/developerguide/operational-best-practices-for-cis_aws_benchmark_level_1.html). Rules file Cis14Level1.yaml is downloaded from https://raw.githubusercontent.com/awslabs/aws-config-rules/master/aws-config-conformance-packs/Operational-Best-Practices-for-CIS-AWS-v1.4-Level1.yaml
|
||||
- Set Config retention period
|
||||
- Setup Config aggregator, aggregate Config in all regions into primary region
|
||||
- Create s3 bucket for config use
|
||||
|
||||
## Inputs:
|
||||
| Name | Description | Type | Default | Required |
|
||||
|--------------------|-------------------------------------------------------------|------|---------|:-----:|
|
||||
| application | name of application | string | none | yes |
|
||||
| environment | capacity of environment (prd/dev/lab) | string | none | yes |
|
||||
| customer-name | owner of aws resources | string | none | yes |
|
||||
| project | name of project | string | none | yes |
|
||||
| default-tags | tags to be added to resources | list | none | yes |
|
||||
| aws-region-short | short name of aws region (e.g. apne1) | string | none | yes |
|
||||
| primary-aws-region | name of primary region where global events will be recorded | string | none | yes |
|
||||
|
||||
|
||||
# Notes
|
||||
- It takes a while for AWS to process Config changes.
|
||||
- [AWS managed config rules](https://docs.aws.amazon.com/config/latest/developerguide/managed-rules-by-aws-config.html) are automatically applied. Those rule may duplicate with Cis1.4.
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
// Config rules from asecure.cloud https://asecure.cloud/p/monitoring_cis_benchmark/
|
||||
// Conformance pack has not been made available to terraform https://github.com/hashicorp/terraform-provider-aws/issues/11098
|
||||
|
||||
resource "aws_config_config_rule" "ConfigRule1" {
|
||||
name = "mfa-enabled-for-iam-console-access"
|
||||
description = "A Config rule that checks whether AWS Multi-Factor Authentication (MFA) is enabled for all AWS Identity and Access Management (IAM) users that use a console password. The rule is COMPLIANT if MFA is enabled."
|
||||
|
||||
source {
|
||||
owner = "AWS"
|
||||
source_identifier = "MFA_ENABLED_FOR_IAM_CONSOLE_ACCESS"
|
||||
}
|
||||
scope {
|
||||
compliance_resource_types = []
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_config_config_rule" "ConfigRule2" {
|
||||
name = "iam-user-unused-credentials-check"
|
||||
description = "A config rule that checks whether your AWS Identity and Access Management (IAM) users have passwords or active access keys that have not been used within the specified number of days you provided. Re-evaluating this rule within 4 hours of the first eva..."
|
||||
input_parameters = "{\"maxCredentialUsageAge\":\"90\"}"
|
||||
|
||||
source {
|
||||
owner = "AWS"
|
||||
source_identifier = "IAM_USER_UNUSED_CREDENTIALS_CHECK"
|
||||
}
|
||||
scope {
|
||||
compliance_resource_types = []
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_config_config_rule" "ConfigRule3" {
|
||||
name = "access-keys-rotated"
|
||||
description = "A config rule that checks whether the active access keys are rotated within the number of days specified in maxAccessKeyAge. The rule is NON_COMPLIANT if the access keys have not been rotated for more than maxAccessKeyAge number of days."
|
||||
input_parameters = "{\"maxAccessKeyAge\":\"90\"}"
|
||||
|
||||
source {
|
||||
owner = "AWS"
|
||||
source_identifier = "ACCESS_KEYS_ROTATED"
|
||||
}
|
||||
scope {
|
||||
compliance_resource_types = []
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_config_config_rule" "ConfigRule4" {
|
||||
name = "iam-password-policy"
|
||||
description = "A Config rule that checks whether the account password policy for IAM users meets the specified requirements."
|
||||
input_parameters = "{\"RequireUppercaseCharacters\":\"true\",\"RequireLowercaseCharacters\":\"true\",\"RequireSymbols\":\"true\",\"RequireNumbers\":\"true\",\"MinimumPasswordLength\":\"14\",\"PasswordReusePrevention\":\"24\",\"MaxPasswordAge\":\"90\"}"
|
||||
|
||||
source {
|
||||
owner = "AWS"
|
||||
source_identifier = "IAM_PASSWORD_POLICY"
|
||||
}
|
||||
scope {
|
||||
compliance_resource_types = []
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_config_config_rule" "ConfigRule5" {
|
||||
name = "iam-root-access-key-check"
|
||||
description = "A config rule that checks whether the root user access key is available. The rule is COMPLIANT if the user access key does not exist."
|
||||
|
||||
source {
|
||||
owner = "AWS"
|
||||
source_identifier = "IAM_ROOT_ACCESS_KEY_CHECK"
|
||||
}
|
||||
scope {
|
||||
compliance_resource_types = []
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_config_config_rule" "ConfigRule6" {
|
||||
name = "root-account-mfa-enabled"
|
||||
description = "A Config rule that checks whether users of your AWS account require a multi-factor authentication (MFA) device to sign in with root credentials."
|
||||
|
||||
source {
|
||||
owner = "AWS"
|
||||
source_identifier = "ROOT_ACCOUNT_MFA_ENABLED"
|
||||
}
|
||||
scope {
|
||||
compliance_resource_types = []
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_config_config_rule" "ConfigRule7" {
|
||||
name = "root-account-hardware-mfa-enabled"
|
||||
description = "A config rule that checks whether your AWS account is enabled to use multi-factor authentication (MFA) hardware device to sign in with root credentials. The rule is NON_COMPLIANT if any virtual MFA devices are permitted for signing in with root credent..."
|
||||
|
||||
source {
|
||||
owner = "AWS"
|
||||
source_identifier = "ROOT_ACCOUNT_HARDWARE_MFA_ENABLED"
|
||||
}
|
||||
scope {
|
||||
compliance_resource_types = []
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_config_config_rule" "ConfigRule8" {
|
||||
name = "iam-user-no-policies-check"
|
||||
description = "A Config rule that checks that none of your IAM users have policies attached. IAM users must inherit permissions from IAM groups or roles."
|
||||
|
||||
source {
|
||||
owner = "AWS"
|
||||
source_identifier = "IAM_USER_NO_POLICIES_CHECK"
|
||||
}
|
||||
scope {
|
||||
compliance_resource_types = ["AWS::IAM::User"]
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_config_config_rule" "ConfigRule11" {
|
||||
name = "iam-policy-no-statements-with-admin-access"
|
||||
description = "A config rule that checks whether the default version of AWS Identity and Access Management (IAM) policies do not have administrator access. If any statement has 'Effect': 'Allow' with 'Action': '*' over 'Resource': '*', the rule is NON_COMPLIANT."
|
||||
|
||||
source {
|
||||
owner = "AWS"
|
||||
source_identifier = "IAM_POLICY_NO_STATEMENTS_WITH_ADMIN_ACCESS"
|
||||
}
|
||||
scope {
|
||||
compliance_resource_types = ["AWS::IAM::Policy"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
AWS Config Service
|
||||
If config is already enabled, import it with
|
||||
terraform import aws_config_configuration_recorder.config-recorder default
|
||||
*/
|
||||
|
||||
data aws_caller_identity this {}
|
||||
data aws_regions all-regions {}
|
||||
|
||||
resource "aws_iam_service_linked_role" "config" {
|
||||
aws_service_name = "config.amazonaws.com"
|
||||
}
|
||||
|
||||
resource null_resource cli-resource-awsconfig {
|
||||
for_each = data.aws_regions.all-regions.names
|
||||
provisioner "local-exec" {
|
||||
when = create
|
||||
command = <<-EOD
|
||||
aws configservice --region ${each.value} put-configuration-recorder --configuration-recorder name=default,roleARN="${aws_iam_service_linked_role.config.arn}" --recording-group allSupported=true,includeGlobalResourceTypes=false
|
||||
aws configservice --region ${each.value} put-delivery-channel --delivery-channel name=default,s3BucketName=${module.config-bucket.bucket-name},configSnapshotDeliveryProperties={deliveryFrequency=Twelve_Hours}
|
||||
aws configservice --region ${each.value} put-retention-configuration --retention-period-in-days ${var.config-retention-days}
|
||||
aws configservice --region ${each.value} put-conformance-pack --conformance-pack-name Cis14Level1 --template-body file://Cis14Level1.yaml
|
||||
aws configservice --region ${each.value} start-configuration-recorder --configuration-recorder-name default
|
||||
if [ \"${each.value}\" == \"${var.primary-aws-region}\" ]; then
|
||||
aws configservice --region ${each.value} put-configuration-recorder --configuration-recorder name=default,roleARN="${aws_iam_service_linked_role.config.arn}" --recording-group allSupported=true,includeGlobalResourceTypes=true
|
||||
fi
|
||||
EOD
|
||||
}
|
||||
|
||||
// Destroy provisioner does not accept variables. Workaround is to delete recorder in all regions.
|
||||
provisioner "local-exec" {
|
||||
when = destroy
|
||||
on_failure = continue
|
||||
command = <<-EOD
|
||||
aws ec2 describe-regions | jq -cr .Regions[].RegionName | while read r; do
|
||||
aws configservice --region $r describe-configuration-recorders --output text | while read dummy; do
|
||||
aws configservice --region $r stop-configuration-recorder --configuration-recorder-name default
|
||||
aws configservice --region $r delete-delivery-channel --delivery-channel-name default
|
||||
aws configservice --region $r delete-configuration-recorder --configuration-recorder-name default
|
||||
done
|
||||
done
|
||||
EOD
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_config_configuration_aggregator" "config-aggregator" {
|
||||
depends_on = [null_resource.cli-resource-awsconfig]
|
||||
name = "ConfigAggregator"
|
||||
|
||||
account_aggregation_source {
|
||||
account_ids = [data.aws_caller_identity.this.id]
|
||||
all_regions = true
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
resource "aws_config_configuration_recorder" "config-recorder" {
|
||||
name = "${local.resource-prefix}-awsconfig"
|
||||
role_arn = aws_iam_service_linked_role.config.arn
|
||||
|
||||
recording_group {
|
||||
all_supported = true
|
||||
include_global_resource_types = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_config_delivery_channel" "config-delivery-channel" {
|
||||
name = "${local.resource-prefix}-configdeliverychannel"
|
||||
s3_bucket_name = module.config-bucket.bucket-name
|
||||
|
||||
depends_on = [aws_config_configuration_recorder.config-recorder]
|
||||
}
|
||||
|
||||
resource "aws_config_configuration_recorder_status" "main" {
|
||||
name = aws_config_configuration_recorder.config-recorder.name
|
||||
is_enabled = true
|
||||
depends_on = [aws_config_delivery_channel.config-delivery-channel]
|
||||
}
|
||||
*/
|
||||
|
||||
######## Config Bucket - Policy ########
|
||||
|
||||
module config-bucket {
|
||||
source = "../../storage/infra-s3-bucket"
|
||||
bucket-name = "${var.resource-prefix}-configbucket-${data.aws_caller_identity.this.account_id}"
|
||||
add-random-suffix = false
|
||||
bucket-policy-json = data.aws_iam_policy_document.config_bucket_policy.json
|
||||
default-tags = var.default-tags
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "config_bucket_policy" {
|
||||
|
||||
statement {
|
||||
sid = "AWSConfigBucketPermissionsCheck"
|
||||
|
||||
principals {
|
||||
type = "Service"
|
||||
identifiers = ["config.amazonaws.com"]
|
||||
}
|
||||
|
||||
actions = [
|
||||
"s3:GetBucketAcl",
|
||||
]
|
||||
|
||||
resources = [
|
||||
"arn:aws:s3:::${var.resource-prefix}-configbucket-${data.aws_caller_identity.this.account_id}",
|
||||
]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "AWSConfigBucketExistenceCheck"
|
||||
|
||||
principals {
|
||||
type = "Service"
|
||||
identifiers = ["config.amazonaws.com"]
|
||||
}
|
||||
|
||||
actions = [
|
||||
"s3:ListBucket",
|
||||
]
|
||||
|
||||
resources = [
|
||||
"arn:aws:s3:::${var.resource-prefix}-configbucket-${data.aws_caller_identity.this.account_id}",
|
||||
]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "AWSConfigBucketDelivery"
|
||||
|
||||
principals {
|
||||
type = "Service"
|
||||
identifiers = ["config.amazonaws.com"]
|
||||
}
|
||||
|
||||
actions = [
|
||||
"s3:PutObject",
|
||||
]
|
||||
|
||||
resources = [
|
||||
"arn:aws:s3:::${var.resource-prefix}-configbucket-${data.aws_caller_identity.this.account_id}/*",
|
||||
]
|
||||
|
||||
condition {
|
||||
test = "StringEquals"
|
||||
variable = "s3:x-amz-acl"
|
||||
|
||||
values = [
|
||||
"bucket-owner-full-control",
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
terraform {
|
||||
required_version = "~> 1.2.5"
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = "~> 3.75.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
variable "default-tags" {}
|
||||
variable resource-prefix {}
|
||||
|
||||
variable config-retention-days {
|
||||
type = number
|
||||
default = 365
|
||||
}
|
||||
|
||||
variable primary-aws-region {}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Overview
|
||||
This module performs the following tasks:
|
||||
|
||||
- Create KMS key for cloudtrail and CWL encryption
|
||||
- Create s3 bucket for cloudtrail use
|
||||
- Create cloudtrail
|
||||
- Create cloudwatch log group for cloudtrail
|
||||
- Create cloudwatch metric filter for CIS1.1
|
||||
- Create cloudwatch alarm for CIS1.1
|
||||
|
||||
## Inputs:
|
||||
| Name | Description | Type | Default | Required |
|
||||
|------|-------------|------|---------|:-----:|
|
||||
| application | name of application | string | none | yes |
|
||||
| environment | capacity of environment (prd/dev/lab) | string | none | yes |
|
||||
| customer-name | owner of aws resources | string | none | yes |
|
||||
| project | name of project | string | none | yes |
|
||||
| default-tags | tags to be added to resources | list | none | yes |
|
||||
| cloudtrail-retain-days | Days before cloudtrail logs are expired on s3 | number | 90 | yes |
|
||||
| aws-region-short | short name of aws region (e.g. apne1) | string | none | yes |
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
resource "aws_iam_role" "iam_cloudtrial_cloudwatch_role" {
|
||||
name = "${var.resource-prefix}-cwl-role"
|
||||
assume_role_policy = data.aws_iam_policy_document.ct-role-assumerole-policy.json
|
||||
description = "Enables AWS CloudTrail to deliver log to CloudWatch log"
|
||||
tags = var.default-tags
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "iam_cloudtrial_cloudwatach_role_policy" {
|
||||
name = "${var.resource-prefix}-cwl-role-policy"
|
||||
role = aws_iam_role.iam_cloudtrial_cloudwatch_role.id
|
||||
policy = data.aws_iam_policy_document.ct-role-pdoc.json
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "ct-role-assumerole-policy" {
|
||||
statement {
|
||||
effect = "Allow"
|
||||
actions = ["sts:AssumeRole"]
|
||||
|
||||
principals {
|
||||
type = "Service"
|
||||
identifiers = ["cloudtrail.amazonaws.com"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "ct-role-pdoc" {
|
||||
statement {
|
||||
effect = "Allow"
|
||||
actions = ["logs:CreateLogStream"]
|
||||
|
||||
resources = [
|
||||
"${aws_cloudwatch_log_group.ct-cwl.arn}:log-stream:*",
|
||||
]
|
||||
}
|
||||
|
||||
statement {
|
||||
effect = "Allow"
|
||||
actions = ["logs:PutLogEvents"]
|
||||
|
||||
resources = [
|
||||
"${aws_cloudwatch_log_group.ct-cwl.arn}:log-stream:*",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
resource "aws_cloudtrail" "default" {
|
||||
name = "${var.resource-prefix}-trail-001"
|
||||
enable_logging = true
|
||||
s3_bucket_name = local.ct-bucket-name
|
||||
enable_log_file_validation = true
|
||||
is_multi_region_trail = true
|
||||
include_global_service_events = true
|
||||
cloud_watch_logs_role_arn = aws_iam_role.iam_cloudtrial_cloudwatch_role.arn
|
||||
cloud_watch_logs_group_arn = "${aws_cloudwatch_log_group.ct-cwl.arn}:*"
|
||||
tags = var.default-tags
|
||||
kms_key_id = aws_kms_key.ctbucket-key.arn
|
||||
is_organization_trail = false
|
||||
|
||||
event_selector {
|
||||
read_write_type = "All"
|
||||
include_management_events = true
|
||||
|
||||
data_resource {
|
||||
type = "AWS::S3::Object"
|
||||
values = ["arn:aws:s3:::"]
|
||||
}
|
||||
|
||||
data_resource {
|
||||
type = "AWS::Lambda::Function"
|
||||
values = ["arn:aws:lambda"]
|
||||
}
|
||||
}
|
||||
|
||||
#insight_selector {
|
||||
# insight_type = "ApiCallRateInsight"
|
||||
#}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
resource "aws_kms_key" "ctbucket-key" {
|
||||
deletion_window_in_days = 7
|
||||
tags = var.default-tags
|
||||
policy = data.aws_iam_policy_document.key-policy.json
|
||||
enable_key_rotation = true
|
||||
}
|
||||
|
||||
resource "aws_kms_alias" ctbucket-key-aliaas {
|
||||
name = "alias/${var.resource-prefix}-kmskey-default"
|
||||
target_key_id = aws_kms_key.ctbucket-key.key_id
|
||||
}
|
||||
|
||||
# https://gist.github.com/shortjared/4c1e3fe52bdfa47522cfe5b41e5d6f22
|
||||
data "aws_iam_policy_document" "key-policy" {
|
||||
statement {
|
||||
sid = "Key usage by aws services"
|
||||
principals {
|
||||
identifiers = [
|
||||
"autoscaling.amazonaws.com",
|
||||
"cloudtrail.amazonaws.com",
|
||||
"eks.amazonaws.com",
|
||||
"eks-nodegroup.amazonaws.com",
|
||||
"guardduty.amazonaws.com",
|
||||
"delivery.logs.amazonaws.com",
|
||||
"sns.amazonaws.com",
|
||||
"sqs.amazonaws.com",
|
||||
"lambda.amazonaws.com",
|
||||
"backup.amazonaws.com",
|
||||
"events.amazonaws.com",
|
||||
"cloudwatch.amazonaws.com",
|
||||
"s3.amazonaws.com",
|
||||
"logs.amazonaws.com"
|
||||
]
|
||||
type = "Service"
|
||||
}
|
||||
|
||||
actions = [
|
||||
"kms:Encrypt",
|
||||
"kms:Decrypt",
|
||||
"kms:ReEncrypt*",
|
||||
"kms:GenerateDataKey*",
|
||||
"kms:DescribeKey"
|
||||
]
|
||||
|
||||
resources = [
|
||||
"*"
|
||||
]
|
||||
|
||||
effect = "Allow"
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "Key administrator"
|
||||
actions = [
|
||||
"kms:*"
|
||||
]
|
||||
|
||||
resources = [
|
||||
"*"
|
||||
]
|
||||
|
||||
principals {
|
||||
type = "AWS"
|
||||
identifiers = [data.aws_caller_identity.this.account_id]
|
||||
}
|
||||
|
||||
effect = "Allow"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
|
||||
|
||||
data "aws_iam_policy_document" "cloudtrail_bucket_policy" {
|
||||
statement {
|
||||
sid = "AWSCloudTrailAclCheck"
|
||||
|
||||
principals {
|
||||
type = "Service"
|
||||
identifiers = ["cloudtrail.amazonaws.com"]
|
||||
}
|
||||
|
||||
actions = [
|
||||
"s3:GetBucketAcl",
|
||||
]
|
||||
|
||||
resources = [
|
||||
"arn:aws:s3:::${local.ct-bucket-name}",
|
||||
]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "AWSCloudTrailWrite"
|
||||
|
||||
principals {
|
||||
type = "Service"
|
||||
identifiers = ["config.amazonaws.com", "cloudtrail.amazonaws.com"]
|
||||
}
|
||||
|
||||
actions = [
|
||||
"s3:PutObject"
|
||||
]
|
||||
|
||||
resources = [
|
||||
"arn:aws:s3:::${local.ct-bucket-name}/*"
|
||||
]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "ReadAccessForAccountOwner"
|
||||
|
||||
principals {
|
||||
type = "AWS"
|
||||
identifiers = [data.aws_caller_identity.this.account_id]
|
||||
}
|
||||
|
||||
actions = [
|
||||
"s3:Get*"
|
||||
]
|
||||
|
||||
resources = [
|
||||
"arn:aws:s3:::${local.ct-bucket-name}",
|
||||
"arn:aws:s3:::${local.ct-bucket-name}/*"
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module ct-bucket {
|
||||
source = "../../storage/infra-s3-bucket"
|
||||
|
||||
bucket-name = local.ct-bucket-name
|
||||
bucket-policy-json = data.aws_iam_policy_document.cloudtrail_bucket_policy.json
|
||||
default-tags = var.default-tags
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
resource "aws_cloudwatch_log_group" "ct-cwl" {
|
||||
name_prefix = "cloudtrail/"
|
||||
retention_in_days = var.cloudtrail-retain-days
|
||||
kms_key_id = aws_kms_key.ctbucket-key.arn
|
||||
tags = var.default-tags
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_log_metric_filter" "cwl-metric-filter-cis11" {
|
||||
name = "cis11-rootaccess-filter"
|
||||
pattern = <<EOT
|
||||
{$.userIdentity.type="Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType !="AwsServiceEvent"}
|
||||
EOT
|
||||
log_group_name = aws_cloudwatch_log_group.ct-cwl.name
|
||||
|
||||
metric_transformation {
|
||||
name = "cis11-rootaccess-metric"
|
||||
namespace = "LogMetrics"
|
||||
value = "1"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_metric_alarm" "cis11-rootaccess-alarm" {
|
||||
alarm_name = "cis11-rootaccess-alarm"
|
||||
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||
evaluation_periods = "1"
|
||||
metric_name = "cis11-rootaccess-metric"
|
||||
namespace = "LogMetrics"
|
||||
period = "300"
|
||||
statistic = "Average"
|
||||
threshold = "1"
|
||||
alarm_description = "Root access is detected from cloudtrail"
|
||||
treat_missing_data = "notBreaching"
|
||||
|
||||
// alarm_actions = []
|
||||
}
|
||||
|
||||
// CIS 3.x benchmark from asecure.cloud https://asecure.cloud/p/monitoring_cis_benchmark/
|
||||
|
||||
resource "aws_cloudwatch_metric_alarm" "CwAlarm2" {
|
||||
alarm_name = "cis-unauthorized_api_calls"
|
||||
alarm_description = "A CloudWatch Alarm that triggers if Multiple unauthorized actions or logins attempted."
|
||||
metric_name = "UnauthorizedAttemptCount"
|
||||
namespace = "CloudTrailMetrics"
|
||||
statistic = "Sum"
|
||||
period = "60"
|
||||
threshold = "1"
|
||||
evaluation_periods = "1"
|
||||
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||
// alarm_actions = [""]
|
||||
treat_missing_data = "notBreaching"
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_log_metric_filter" "MetricFilter2" {
|
||||
log_group_name = aws_cloudwatch_log_group.ct-cwl.name
|
||||
pattern = "{ ($.errorCode = \"*UnauthorizedOperation\") || ($.errorCode = \"AccessDenied*\") }"
|
||||
name = "UnauthorizedAttemptCount"
|
||||
|
||||
metric_transformation {
|
||||
name = "UnauthorizedAttemptCount"
|
||||
value = "1"
|
||||
namespace = "CloudTrailMetrics"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_metric_alarm" "CwAlarm3" {
|
||||
alarm_name = "cis-no_mfa_console_logins"
|
||||
alarm_description = "A CloudWatch Alarm that triggers if there is a Management Console sign-in without MFA."
|
||||
metric_name = "ConsoleSigninWithoutMFA"
|
||||
namespace = "CloudTrailMetrics"
|
||||
statistic = "Sum"
|
||||
period = "60"
|
||||
threshold = "1"
|
||||
evaluation_periods = "1"
|
||||
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||
// alarm_actions = [""]
|
||||
treat_missing_data = "notBreaching"
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_log_metric_filter" "MetricFilter3" {
|
||||
log_group_name = aws_cloudwatch_log_group.ct-cwl.name
|
||||
pattern = "{($.eventName = \"ConsoleLogin\") && ($.additionalEventData.MFAUsed != \"Yes\") && ($.responseElements.ConsoleLogin != \"Failure\") && ($.additionalEventData.SamlProviderArn NOT EXISTS) }"
|
||||
name = "ConsoleSigninWithoutMFA"
|
||||
|
||||
metric_transformation {
|
||||
name = "ConsoleSigninWithoutMFA"
|
||||
value = "1"
|
||||
namespace = "CloudTrailMetrics"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_metric_alarm" "CwAlarm4" {
|
||||
alarm_name = "cis-iam_policy_changes"
|
||||
alarm_description = "A CloudWatch Alarm that triggers when changes are made to IAM policies. Events include IAM policy creation/deletion/update operations as well as attaching/detaching policies from IAM users, roles or groups."
|
||||
metric_name = "IAMPolicyEventCount"
|
||||
namespace = "CloudTrailMetrics"
|
||||
statistic = "Sum"
|
||||
period = "300"
|
||||
threshold = "1"
|
||||
evaluation_periods = "1"
|
||||
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||
// alarm_actions = [""]
|
||||
treat_missing_data = "notBreaching"
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_log_metric_filter" "MetricFilter4" {
|
||||
log_group_name = aws_cloudwatch_log_group.ct-cwl.name
|
||||
pattern = "{($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventName=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolicy)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=DeletePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersion)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.eventName=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGroupPolicy)||($.eventName=DetachGroupPolicy)}"
|
||||
name = "IAMPolicyEventCount"
|
||||
|
||||
metric_transformation {
|
||||
name = "IAMPolicyEventCount"
|
||||
value = "1"
|
||||
namespace = "CloudTrailMetrics"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_metric_alarm" "CwAlarm5" {
|
||||
alarm_name = "cis-cloudtrail_changes"
|
||||
alarm_description = "A CloudWatch Alarm that triggers when changes are made to CloudTrail."
|
||||
metric_name = "CloudTrailEventCount"
|
||||
namespace = "CloudTrailMetrics"
|
||||
statistic = "Sum"
|
||||
period = "300"
|
||||
threshold = "1"
|
||||
evaluation_periods = "1"
|
||||
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||
// alarm_actions = [""]
|
||||
treat_missing_data = "notBreaching"
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_log_metric_filter" "MetricFilter5" {
|
||||
log_group_name = aws_cloudwatch_log_group.ct-cwl.name
|
||||
pattern = "{ ($.eventName = CreateTrail) || ($.eventName = UpdateTrail) || ($.eventName = DeleteTrail) || ($.eventName = StartLogging) || ($.eventName = StopLogging) }"
|
||||
name = "CloudTrailEventCount"
|
||||
|
||||
metric_transformation {
|
||||
name = "CloudTrailEventCount"
|
||||
value = "1"
|
||||
namespace = "CloudTrailMetrics"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_metric_alarm" "CwAlarm6" {
|
||||
alarm_name = "cis-failed_console_logins"
|
||||
alarm_description = "A CloudWatch Alarm that triggers if there are AWS Management Console authentication failures."
|
||||
metric_name = "ConsoleLoginFailures"
|
||||
namespace = "CloudTrailMetrics"
|
||||
statistic = "Sum"
|
||||
period = "300"
|
||||
threshold = "1"
|
||||
evaluation_periods = "1"
|
||||
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||
// alarm_actions = [""]
|
||||
treat_missing_data = "notBreaching"
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_log_metric_filter" "MetricFilter6" {
|
||||
log_group_name = aws_cloudwatch_log_group.ct-cwl.name
|
||||
pattern = "{ ($.eventName = ConsoleLogin) && ($.errorMessage = \"Failed authentication\") }"
|
||||
name = "ConsoleLoginFailures"
|
||||
|
||||
metric_transformation {
|
||||
name = "ConsoleLoginFailures"
|
||||
value = "1"
|
||||
namespace = "CloudTrailMetrics"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_metric_alarm" "CwAlarm7" {
|
||||
alarm_name = "cis-disabled_deleted_cmks"
|
||||
alarm_description = "A CloudWatch Alarm that triggers if customer created CMKs get disabled or scheduled for deletion."
|
||||
metric_name = "KMSCustomerKeyDeletion"
|
||||
namespace = "CloudTrailMetrics"
|
||||
statistic = "Sum"
|
||||
period = "60"
|
||||
threshold = "1"
|
||||
evaluation_periods = "1"
|
||||
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||
// alarm_actions = [""]
|
||||
treat_missing_data = "notBreaching"
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_log_metric_filter" "MetricFilter7" {
|
||||
log_group_name = aws_cloudwatch_log_group.ct-cwl.name
|
||||
pattern = "{ ($.eventSource = kms.amazonaws.com) && (($.eventName=DisableKey) || ($.eventName=ScheduleKeyDeletion)) }"
|
||||
name = "KMSCustomerKeyDeletion"
|
||||
|
||||
metric_transformation {
|
||||
name = "KMSCustomerKeyDeletion"
|
||||
value = "1"
|
||||
namespace = "CloudTrailMetrics"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_metric_alarm" "CwAlarm8" {
|
||||
alarm_name = "cis-s3_changes"
|
||||
alarm_description = "A CloudWatch Alarm that triggers when changes are made to an S3 Bucket."
|
||||
metric_name = "S3BucketActivityEventCount"
|
||||
namespace = "CloudTrailMetrics"
|
||||
statistic = "Sum"
|
||||
period = "300"
|
||||
threshold = "1"
|
||||
evaluation_periods = "1"
|
||||
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||
// alarm_actions = [""]
|
||||
treat_missing_data = "notBreaching"
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_log_metric_filter" "MetricFilter8" {
|
||||
log_group_name = aws_cloudwatch_log_group.ct-cwl.name
|
||||
pattern = "{ ($.eventSource = s3.amazonaws.com) && (($.eventName = PutBucketAcl) || ($.eventName = PutBucketPolicy) || ($.eventName = PutBucketCors) || ($.eventName = PutBucketLifecycle) || ($.eventName = PutBucketReplication) || ($.eventName = DeleteBucketPolicy) || ($.eventName = DeleteBucketCors) || ($.eventName = DeleteBucketLifecycle) || ($.eventName = DeleteBucketReplication)) }"
|
||||
name = "S3BucketActivityEventCount"
|
||||
|
||||
metric_transformation {
|
||||
name = "S3BucketActivityEventCount"
|
||||
value = "1"
|
||||
namespace = "CloudTrailMetrics"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_metric_alarm" "CwAlarm9" {
|
||||
alarm_name = "cis-config_changes"
|
||||
alarm_description = "A CloudWatch Alarm that triggers when changes are made to AWS Config."
|
||||
metric_name = "CloudTrailEventCount"
|
||||
namespace = "CloudTrailMetrics"
|
||||
statistic = "Sum"
|
||||
period = "300"
|
||||
threshold = "1"
|
||||
evaluation_periods = "1"
|
||||
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||
// alarm_actions = [""]
|
||||
treat_missing_data = "notBreaching"
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_log_metric_filter" "MetricFilter9" {
|
||||
log_group_name = aws_cloudwatch_log_group.ct-cwl.name
|
||||
pattern = "{ ($.eventName = PutConfigurationRecorder) || ($.eventName = StopConfigurationRecorder) || ($.eventName = DeleteDeliveryChannel) || ($.eventName = PutDeliveryChannel) }"
|
||||
name = "CloudTrailEventCount"
|
||||
|
||||
metric_transformation {
|
||||
name = "CloudTrailEventCount"
|
||||
value = "1"
|
||||
namespace = "CloudTrailMetrics"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_metric_alarm" "CwAlarm10" {
|
||||
alarm_name = "cis-securitygroup_changes"
|
||||
alarm_description = "A CloudWatch Alarm that triggers when changes are made to Security Groups."
|
||||
metric_name = "SecurityGroupEventCount"
|
||||
namespace = "CloudTrailMetrics"
|
||||
statistic = "Sum"
|
||||
period = "300"
|
||||
threshold = "1"
|
||||
evaluation_periods = "1"
|
||||
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||
// alarm_actions = [""]
|
||||
treat_missing_data = "notBreaching"
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_log_metric_filter" "MetricFilter10" {
|
||||
log_group_name = aws_cloudwatch_log_group.ct-cwl.name
|
||||
pattern = "{ ($.eventName = AuthorizeSecurityGroupIngress) || ($.eventName = AuthorizeSecurityGroupEgress) || ($.eventName = RevokeSecurityGroupIngress) || ($.eventName = RevokeSecurityGroupEgress) || ($.eventName = CreateSecurityGroup) || ($.eventName = DeleteSecurityGroup) }"
|
||||
name = "SecurityGroupEventCount"
|
||||
|
||||
metric_transformation {
|
||||
name = "SecurityGroupEventCount"
|
||||
value = "1"
|
||||
namespace = "CloudTrailMetrics"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_metric_alarm" "CwAlarm11" {
|
||||
alarm_name = "cis-nacl_changes"
|
||||
alarm_description = "A CloudWatch Alarm that triggers when changes are made to Network ACLs."
|
||||
metric_name = "NetworkAclEventCount"
|
||||
namespace = "CloudTrailMetrics"
|
||||
statistic = "Sum"
|
||||
period = "300"
|
||||
threshold = "1"
|
||||
evaluation_periods = "1"
|
||||
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||
// alarm_actions = [""]
|
||||
treat_missing_data = "notBreaching"
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_log_metric_filter" "MetricFilter11" {
|
||||
log_group_name = aws_cloudwatch_log_group.ct-cwl.name
|
||||
pattern = "{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }"
|
||||
name = "NetworkAclEventCount"
|
||||
|
||||
metric_transformation {
|
||||
name = "NetworkAclEventCount"
|
||||
value = "1"
|
||||
namespace = "CloudTrailMetrics"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_metric_alarm" "CwAlarm12" {
|
||||
alarm_name = "cis-igw_changes"
|
||||
alarm_description = "A CloudWatch Alarm that triggers when changes are made to an Internet Gateway in a VPC."
|
||||
metric_name = "GatewayEventCount"
|
||||
namespace = "CloudTrailMetrics"
|
||||
statistic = "Sum"
|
||||
period = "300"
|
||||
threshold = "1"
|
||||
evaluation_periods = "1"
|
||||
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||
// alarm_actions = [""]
|
||||
treat_missing_data = "notBreaching"
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_log_metric_filter" "MetricFilter12" {
|
||||
log_group_name = aws_cloudwatch_log_group.ct-cwl.name
|
||||
pattern = "{ ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway) || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }"
|
||||
name = "GatewayEventCount"
|
||||
|
||||
metric_transformation {
|
||||
name = "GatewayEventCount"
|
||||
value = "1"
|
||||
namespace = "CloudTrailMetrics"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_metric_alarm" "CwAlarm13" {
|
||||
alarm_name = "cis-vpc_routetable_changes"
|
||||
alarm_description = "A CloudWatch Alarm that triggers when changes are made to a VPC's Route Table."
|
||||
metric_name = "VpcRouteTableEventCount"
|
||||
namespace = "CloudTrailMetrics"
|
||||
statistic = "Sum"
|
||||
period = "300"
|
||||
threshold = "1"
|
||||
evaluation_periods = "1"
|
||||
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||
// alarm_actions = [""]
|
||||
treat_missing_data = "notBreaching"
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_log_metric_filter" "MetricFilter13" {
|
||||
log_group_name = aws_cloudwatch_log_group.ct-cwl.name
|
||||
pattern = "{ ($.eventName = AssociateRouteTable) || ($.eventName = CreateRoute) || ($.eventName = CreateRouteTable) || ($.eventName = DeleteRoute) || ($.eventName = DeleteRouteTable) || ($.eventName = ReplaceRoute) || ($.eventName = ReplaceRouteTableAssociation) || ($.eventName = DisassociateRouteTable) }"
|
||||
name = "VpcRouteTableEventCount"
|
||||
|
||||
metric_transformation {
|
||||
name = "VpcRouteTableEventCount"
|
||||
value = "1"
|
||||
namespace = "CloudTrailMetrics"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_metric_alarm" "CwAlarm14" {
|
||||
alarm_name = "cis-vpc_changes"
|
||||
alarm_description = "A CloudWatch Alarm that triggers when changes are made to a VPC."
|
||||
metric_name = "VpcEventCount"
|
||||
namespace = "CloudTrailMetrics"
|
||||
statistic = "Sum"
|
||||
period = "300"
|
||||
threshold = "1"
|
||||
evaluation_periods = "1"
|
||||
comparison_operator = "GreaterThanOrEqualToThreshold"
|
||||
// alarm_actions = [""]
|
||||
treat_missing_data = "notBreaching"
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_log_metric_filter" "MetricFilter14" {
|
||||
log_group_name = aws_cloudwatch_log_group.ct-cwl.name
|
||||
pattern = "{ ($.eventName = CreateVpc) || ($.eventName = DeleteVpc) || ($.eventName = ModifyVpcAttribute) || ($.eventName = AcceptVpcPeeringConnection) || ($.eventName = CreateVpcPeeringConnection) || ($.eventName = DeleteVpcPeeringConnection) || ($.eventName = RejectVpcPeeringConnection) || ($.eventName = AttachClassicLinkVpc) || ($.eventName = DetachClassicLinkVpc) || ($.eventName = DisableVpcClassicLink) || ($.eventName = EnableVpcClassicLink) }"
|
||||
name = "VpcEventCount"
|
||||
|
||||
metric_transformation {
|
||||
name = "VpcEventCount"
|
||||
value = "1"
|
||||
namespace = "CloudTrailMetrics"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
data "aws_caller_identity" "this" {}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
variable "customer-name" {}
|
||||
variable "environment" {}
|
||||
variable "project" {}
|
||||
variable "application" {}
|
||||
*/
|
||||
variable resource-prefix {}
|
||||
variable "default-tags" {}
|
||||
variable "cloudtrail-retain-days" {
|
||||
type = number
|
||||
default = 90
|
||||
}
|
||||
|
||||
data aws_region this-region {}
|
||||
|
||||
locals {
|
||||
ct-bucket-name = "${var.resource-prefix}-ctbucket-${data.aws_caller_identity.this.account_id}"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
|
||||
resource "aws_directory_service_directory" "connector" {
|
||||
name = var.adc-domainname
|
||||
enable_sso = false # enabling this results in error when terraform is ran in member accounts
|
||||
password = var.adc-service-account-password
|
||||
size = var.adc-size
|
||||
type = "ADConnector"
|
||||
description = "ADConnector"
|
||||
tags = var.default-tags
|
||||
|
||||
connect_settings {
|
||||
customer_dns_ips = var.adc-dns-ips
|
||||
customer_username = var.adc-service-account-username
|
||||
subnet_ids = var.adc-subnet-ids
|
||||
vpc_id = var.adc-vpc-id
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
output directory-id {
|
||||
value = aws_directory_service_directory.connector.id
|
||||
}
|
||||
|
||||
output security-group-id {
|
||||
value = aws_directory_service_directory.connector.security_group_id
|
||||
}
|
||||
|
||||
output customer-dns-ip {
|
||||
value = flatten(aws_directory_service_directory.connector.connect_settings[*].customer_dns_ips)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
variable "adc-domainname" {}
|
||||
variable "adc-service-account-password" {}
|
||||
variable "adc-size" {}
|
||||
variable "adc-dns-ips" {}
|
||||
variable "adc-service-account-username" {}
|
||||
variable "adc-subnet-ids" {}
|
||||
variable "adc-vpc-id" {}
|
||||
variable "default-tags" {}
|
||||
@@ -0,0 +1,13 @@
|
||||
# five-deployer-roles
|
||||
This module create IAM roles for use with IAC execution. 5 roles are created and each role has permissions to perform
|
||||
different tasks. The 5 roles are:
|
||||
|
||||
* NetworkDeployer: Role with access to manage network related resources
|
||||
* SecurityDeployer: Role with access to manage IAM related resources
|
||||
* DatabaseDeployer: Role with access to manage database related resources
|
||||
* StorageDeployer: Role with access to manage storage related resources
|
||||
* CommonDeployer: Role with access to manage all resources, excluding access granted to the 4 other roles
|
||||
|
||||
# Changelog
|
||||
* 20230313: Initial release
|
||||
* 20230929: Added iam:PassRole to NetworkDeployer for creating vpc flowlogs
|
||||
@@ -0,0 +1 @@
|
||||
data "aws_caller_identity" "this" {}
|
||||
@@ -0,0 +1,639 @@
|
||||
data "aws_default_tags" "this" {
|
||||
lifecycle {
|
||||
postcondition {
|
||||
condition = length(self.tags) >= 1
|
||||
error_message = "Validation failed: Provider default_tags not set."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "assume-role-policy" {
|
||||
statement {
|
||||
actions = ["sts:AssumeRole"]
|
||||
|
||||
principals {
|
||||
type = "AWS"
|
||||
identifiers = [var.role-trusted-entity-arn]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "SecurityDeployer" {
|
||||
name = "SecurityDeployer"
|
||||
description = "Admin access to IAM, KMS, SecretsManager, ec2 Key Pair"
|
||||
max_session_duration = var.max_session_duration
|
||||
assume_role_policy = data.aws_iam_policy_document.assume-role-policy.json
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "SecurityDeployerPolicy" {
|
||||
name = "SecurityDeployerPolicy"
|
||||
policy = jsonencode({
|
||||
"Version" : "2012-10-17",
|
||||
"Statement" : [
|
||||
{
|
||||
"Effect" : "Allow",
|
||||
"Action" : [
|
||||
"iam:*",
|
||||
"secretsmanager:*",
|
||||
"ec2:ImportKeyPair",
|
||||
"kms:*",
|
||||
"ec2:CreateKeyPair",
|
||||
"ec2:DescribeKeyPairs",
|
||||
"ec2:DeleteKeyPair",
|
||||
"acm:*",
|
||||
"config:*",
|
||||
"guardduty:*",
|
||||
"inspector2:*",
|
||||
"securityhub:*",
|
||||
"shield:*",
|
||||
"sso:*",
|
||||
"organizations:*"
|
||||
],
|
||||
"Resource" : "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
role = aws_iam_role.SecurityDeployer.id
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "NetworkDeployer" {
|
||||
name = "NetworkDeployer"
|
||||
description = "Admin access to VPC, SecurityGroup, Route53"
|
||||
max_session_duration = var.max_session_duration
|
||||
assume_role_policy = data.aws_iam_policy_document.assume-role-policy.json
|
||||
}
|
||||
|
||||
# iam:PassRole required to create flowlogs
|
||||
resource "aws_iam_role_policy" "NetworkDeployerPolicy" {
|
||||
name = "NetworkDeployerPolicy"
|
||||
policy = jsonencode({
|
||||
"Version" : "2012-10-17",
|
||||
"Statement" : [
|
||||
{
|
||||
"Effect" : "Allow",
|
||||
"Action" : [
|
||||
"iam:PassRole",
|
||||
"ec2:AcceptVpcEndpointConnections",
|
||||
"ec2:AllocateAddress",
|
||||
"ec2:AssignIpv6Addresses",
|
||||
"ec2:AssignPrivateIpAddresses",
|
||||
"ec2:AssociateAddress",
|
||||
"ec2:AssociateDhcpOptions",
|
||||
"ec2:AssociateRouteTable",
|
||||
"ec2:AssociateSubnetCidrBlock",
|
||||
"ec2:AssociateVpcCidrBlock",
|
||||
"ec2:AttachInternetGateway",
|
||||
"ec2:AttachNetworkInterface",
|
||||
"ec2:AttachVpnGateway",
|
||||
"ec2:CreateCarrierGateway",
|
||||
"ec2:CreateCustomerGateway",
|
||||
"ec2:CreateDefaultSubnet",
|
||||
"ec2:CreateDefaultVpc",
|
||||
"ec2:CreateDhcpOptions",
|
||||
"ec2:CreateEgressOnlyInternetGateway",
|
||||
"ec2:CreateFlowLogs",
|
||||
"ec2:CreateInternetGateway",
|
||||
"ec2:CreateNatGateway",
|
||||
"ec2:CreateNetworkAcl",
|
||||
"ec2:CreateNetworkAclEntry",
|
||||
"ec2:CreateNetworkInterface",
|
||||
"ec2:CreateNetworkInterfacePermission",
|
||||
"ec2:CreatePlacementGroup",
|
||||
"ec2:CreateRoute",
|
||||
"ec2:CreateRouteTable",
|
||||
"ec2:CreateSecurityGroup",
|
||||
"ec2:CreateSubnet",
|
||||
"ec2:CreateTags",
|
||||
"ec2:CreateVpc",
|
||||
"ec2:CreateVpcEndpoint",
|
||||
"ec2:CreateVpcEndpointConnectionNotification",
|
||||
"ec2:CreateVpcEndpointServiceConfiguration",
|
||||
"ec2:CreateVpnConnection",
|
||||
"ec2:CreateVpnConnectionRoute",
|
||||
"ec2:CreateVpnGateway",
|
||||
"ec2:DeleteCarrierGateway",
|
||||
"ec2:DeleteEgressOnlyInternetGateway",
|
||||
"ec2:DeleteFlowLogs",
|
||||
"ec2:DeleteNatGateway",
|
||||
"ec2:DeleteNetworkInterface",
|
||||
"ec2:DeleteNetworkInterfacePermission",
|
||||
"ec2:DeletePlacementGroup",
|
||||
"ec2:DeleteSubnet",
|
||||
"ec2:DeleteTags",
|
||||
"ec2:DeleteVpc",
|
||||
"ec2:DeleteVpcEndpointConnectionNotifications",
|
||||
"ec2:DeleteVpcEndpointServiceConfigurations",
|
||||
"ec2:DeleteVpcEndpoints",
|
||||
"ec2:DeleteVpnConnection",
|
||||
"ec2:DeleteVpnConnectionRoute",
|
||||
"ec2:DeleteVpnGateway",
|
||||
"ec2:DescribeAccountAttributes",
|
||||
"ec2:DescribeAddresses",
|
||||
"ec2:DescribeAvailabilityZones",
|
||||
"ec2:DescribeCarrierGateways",
|
||||
"ec2:DescribeClassicLinkInstances",
|
||||
"ec2:DescribeCustomerGateways",
|
||||
"ec2:DescribeDhcpOptions",
|
||||
"ec2:DescribeEgressOnlyInternetGateways",
|
||||
"ec2:DescribeFlowLogs",
|
||||
"ec2:DescribeInstances",
|
||||
"ec2:DescribeInternetGateways",
|
||||
"ec2:DescribeKeyPairs",
|
||||
"ec2:DescribeMovingAddresses",
|
||||
"ec2:DescribeNatGateways",
|
||||
"ec2:DescribeNetworkAcls",
|
||||
"ec2:DescribeNetworkInterfaceAttribute",
|
||||
"ec2:DescribeNetworkInterfacePermissions",
|
||||
"ec2:DescribeNetworkInterfaces",
|
||||
"ec2:DescribePlacementGroups",
|
||||
"ec2:DescribePrefixLists",
|
||||
"ec2:DescribeRouteTables",
|
||||
"ec2:DescribeSecurityGroupReferences",
|
||||
"ec2:DescribeSecurityGroupRules",
|
||||
"ec2:DescribeSecurityGroups",
|
||||
"ec2:DescribeStaleSecurityGroups",
|
||||
"ec2:DescribeSubnets",
|
||||
"ec2:DescribeTags",
|
||||
"ec2:DescribeVpcAttribute",
|
||||
"ec2:DescribeVpcClassicLink",
|
||||
"ec2:DescribeVpcClassicLinkDnsSupport",
|
||||
"ec2:DescribeVpcEndpointConnectionNotifications",
|
||||
"ec2:DescribeVpcEndpointConnections",
|
||||
"ec2:DescribeVpcEndpointServiceConfigurations",
|
||||
"ec2:DescribeVpcEndpointServicePermissions",
|
||||
"ec2:DescribeVpcEndpointServices",
|
||||
"ec2:DescribeVpcEndpoints",
|
||||
"ec2:DescribeVpcPeeringConnections",
|
||||
"ec2:DescribeVpcs",
|
||||
"ec2:DescribeVpnConnections",
|
||||
"ec2:DescribeVpnGateways",
|
||||
"ec2:DescribePublicIpv4Pools",
|
||||
"ec2:DescribeIpv6Pools",
|
||||
"ec2:DetachInternetGateway",
|
||||
"ec2:DetachNetworkInterface",
|
||||
"ec2:DetachVpnGateway",
|
||||
"ec2:DisableVgwRoutePropagation",
|
||||
"ec2:DisableVpcClassicLinkDnsSupport",
|
||||
"ec2:DisassociateAddress",
|
||||
"ec2:DisassociateRouteTable",
|
||||
"ec2:DisassociateSubnetCidrBlock",
|
||||
"ec2:DisassociateVpcCidrBlock",
|
||||
"ec2:EnableVgwRoutePropagation",
|
||||
"ec2:EnableVpcClassicLinkDnsSupport",
|
||||
"ec2:ModifyNetworkInterfaceAttribute",
|
||||
"ec2:ModifySecurityGroupRules",
|
||||
"ec2:ModifySubnetAttribute",
|
||||
"ec2:ModifyVpcAttribute",
|
||||
"ec2:ModifyVpcEndpoint",
|
||||
"ec2:ModifyVpcEndpointConnectionNotification",
|
||||
"ec2:ModifyVpcEndpointServiceConfiguration",
|
||||
"ec2:ModifyVpcEndpointServicePermissions",
|
||||
"ec2:ModifyVpcPeeringConnectionOptions",
|
||||
"ec2:ModifyVpcTenancy",
|
||||
"ec2:MoveAddressToVpc",
|
||||
"ec2:RejectVpcEndpointConnections",
|
||||
"ec2:ReleaseAddress",
|
||||
"ec2:ReplaceNetworkAclAssociation",
|
||||
"ec2:ReplaceNetworkAclEntry",
|
||||
"ec2:ReplaceRoute",
|
||||
"ec2:ReplaceRouteTableAssociation",
|
||||
"ec2:ResetNetworkInterfaceAttribute",
|
||||
"ec2:RestoreAddressToClassic",
|
||||
"ec2:UnassignIpv6Addresses",
|
||||
"ec2:UnassignPrivateIpAddresses",
|
||||
"ec2:UpdateSecurityGroupRuleDescriptionsEgress",
|
||||
"ec2:UpdateSecurityGroupRuleDescriptionsIngress",
|
||||
"ec2:AcceptVpcPeeringConnection",
|
||||
"ec2:AttachClassicLinkVpc",
|
||||
"ec2:AuthorizeSecurityGroupEgress",
|
||||
"ec2:AuthorizeSecurityGroupIngress",
|
||||
"ec2:CreateVpcPeeringConnection",
|
||||
"ec2:DeleteCustomerGateway",
|
||||
"ec2:DeleteDhcpOptions",
|
||||
"ec2:DeleteInternetGateway",
|
||||
"ec2:DeleteNetworkAcl",
|
||||
"ec2:DeleteNetworkAclEntry",
|
||||
"ec2:DeleteRoute",
|
||||
"ec2:DeleteRouteTable",
|
||||
"ec2:DeleteSecurityGroup",
|
||||
"ec2:DeleteVolume",
|
||||
"ec2:DeleteVpcPeeringConnection",
|
||||
"ec2:DetachClassicLinkVpc",
|
||||
"ec2:DisableVpcClassicLink",
|
||||
"ec2:EnableVpcClassicLink",
|
||||
"ec2:GetConsoleScreenshot",
|
||||
"ec2:RejectVpcPeeringConnection",
|
||||
"ec2:RevokeSecurityGroupEgress",
|
||||
"ec2:RevokeSecurityGroupIngress",
|
||||
"ec2:CreateLocalGatewayRoute",
|
||||
"ec2:CreateLocalGatewayRouteTableVpcAssociation",
|
||||
"ec2:DeleteLocalGatewayRoute",
|
||||
"ec2:DeleteLocalGatewayRouteTableVpcAssociation",
|
||||
"ec2:DescribeLocalGatewayRouteTableVirtualInterfaceGroupAssociations",
|
||||
"ec2:DescribeLocalGatewayRouteTableVpcAssociations",
|
||||
"ec2:DescribeLocalGatewayRouteTables",
|
||||
"ec2:DescribeLocalGatewayVirtualInterfaceGroups",
|
||||
"ec2:DescribeLocalGatewayVirtualInterfaces",
|
||||
"ec2:DescribeLocalGateways",
|
||||
"ec2:SearchLocalGatewayRoutes",
|
||||
"ec2:AcceptTransitGatewayVpcAttachment",
|
||||
"ec2:AssociateTransitGatewayRouteTable",
|
||||
"ec2:CreateTransitGateway",
|
||||
"ec2:CreateTransitGatewayRoute",
|
||||
"ec2:CreateTransitGatewayRouteTable",
|
||||
"ec2:CreateTransitGatewayVpcAttachment",
|
||||
"ec2:DeleteTransitGateway",
|
||||
"ec2:DeleteTransitGatewayRoute",
|
||||
"ec2:DeleteTransitGatewayRouteTable",
|
||||
"ec2:DeleteTransitGatewayVpcAttachment",
|
||||
"ec2:DescribeTransitGatewayAttachments",
|
||||
"ec2:DescribeTransitGatewayRouteTables",
|
||||
"ec2:DescribeTransitGatewayVpcAttachments",
|
||||
"ec2:DescribeTransitGateways",
|
||||
"ec2:DisableTransitGatewayRouteTablePropagation",
|
||||
"ec2:DisassociateTransitGatewayRouteTable",
|
||||
"ec2:EnableTransitGatewayRouteTablePropagation",
|
||||
"ec2:ExportTransitGatewayRoutes",
|
||||
"ec2:GetTransitGatewayAttachmentPropagations",
|
||||
"ec2:GetTransitGatewayRouteTableAssociations",
|
||||
"ec2:GetTransitGatewayRouteTablePropagations",
|
||||
"ec2:ModifyTransitGateway",
|
||||
"ec2:ModifyTransitGatewayVpcAttachment",
|
||||
"ec2:RejectTransitGatewayVpcAttachment",
|
||||
"ec2:ReplaceTransitGatewayRoute",
|
||||
"ec2:SearchTransitGatewayRoutes",
|
||||
"route53domains:*",
|
||||
"route53resolver:*",
|
||||
"route53:*",
|
||||
"directconnect:*"
|
||||
],
|
||||
"Resource" : "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
role = aws_iam_role.NetworkDeployer.id
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "DatabaseDeployer" {
|
||||
name = "DatabaseDeployer"
|
||||
description = "Admin access to databases"
|
||||
max_session_duration = var.max_session_duration
|
||||
assume_role_policy = data.aws_iam_policy_document.assume-role-policy.json
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "DatabaseDeployerPolicy" {
|
||||
name = "DatabaseDeployerPolicy"
|
||||
policy = jsonencode(
|
||||
{
|
||||
"Version" : "2012-10-17",
|
||||
"Statement" : [
|
||||
{
|
||||
"Effect" : "Allow",
|
||||
"Action" : [
|
||||
"rds:*",
|
||||
"redshift:*",
|
||||
"elasticache:*",
|
||||
"kms:Get*",
|
||||
"kms:List*",
|
||||
"kms:Describe*"
|
||||
],
|
||||
"Resource" : "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
role = aws_iam_role.DatabaseDeployer.id
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "StorageDeployer" {
|
||||
name = "StorageDeployer"
|
||||
description = "Admin access to S3, RDS, ElastiCache, ECR"
|
||||
max_session_duration = var.max_session_duration
|
||||
assume_role_policy = data.aws_iam_policy_document.assume-role-policy.json
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "StorageDeployerPolicy" {
|
||||
name = "StorageDeployerPolicy"
|
||||
policy = jsonencode(
|
||||
{
|
||||
"Version" : "2012-10-17",
|
||||
"Statement" : [
|
||||
{
|
||||
"Effect" : "Allow",
|
||||
"Action" : [
|
||||
"s3:*",
|
||||
"ecr:*",
|
||||
"elasticfilesystem:*",
|
||||
"fsx:*",
|
||||
"kms:Get*",
|
||||
"kms:List*",
|
||||
"kms:Describe*"
|
||||
],
|
||||
"Resource" : "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
role = aws_iam_role.StorageDeployer.id
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "CommonDeployer" {
|
||||
name = "CommonDeployer"
|
||||
description = "Admin access to all services except those allowed in other deployer roles"
|
||||
max_session_duration = var.max_session_duration
|
||||
assume_role_policy = data.aws_iam_policy_document.assume-role-policy.json
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "CommonDeployerPolicy" {
|
||||
name = "CommonDeployerPolicy"
|
||||
policy = jsonencode(
|
||||
{
|
||||
"Version" : "2012-10-17",
|
||||
"Statement" : [
|
||||
{
|
||||
"Sid" : "NegateSecurityDeployerPermissions",
|
||||
"Effect" : "Allow",
|
||||
"NotAction" : [
|
||||
"iam:*",
|
||||
"secretsmanager:*",
|
||||
"ec2:ImportKeyPair",
|
||||
"kms:EnableKey",
|
||||
"kms:ImportKeyMaterial",
|
||||
"kms:Decrypt",
|
||||
"kms:GenerateRandom",
|
||||
"kms:PutKeyPolicy",
|
||||
"kms:GenerateDataKeyWithoutPlaintext",
|
||||
"kms:Verify",
|
||||
"kms:CancelKeyDeletion",
|
||||
"kms:ReplicateKey",
|
||||
"kms:GenerateDataKeyPair",
|
||||
"kms:SynchronizeMultiRegionKey",
|
||||
"kms:DeleteCustomKeyStore",
|
||||
"kms:GenerateMac",
|
||||
"kms:UpdatePrimaryRegion",
|
||||
"kms:UpdateCustomKeyStore",
|
||||
"kms:Encrypt",
|
||||
"kms:ScheduleKeyDeletion",
|
||||
"kms:ReEncryptTo",
|
||||
"kms:CreateKey",
|
||||
"kms:ConnectCustomKeyStore",
|
||||
"kms:Sign",
|
||||
"kms:CreateGrant",
|
||||
"kms:EnableKeyRotation",
|
||||
"kms:UpdateKeyDescription",
|
||||
"kms:DeleteImportedKeyMaterial",
|
||||
"kms:GenerateDataKeyPairWithoutPlaintext",
|
||||
"kms:DisableKey",
|
||||
"kms:ReEncryptFrom",
|
||||
"kms:DisableKeyRotation",
|
||||
"kms:RetireGrant",
|
||||
"kms:VerifyMac",
|
||||
"kms:UpdateAlias",
|
||||
"kms:CreateCustomKeyStore",
|
||||
"kms:RevokeGrant",
|
||||
"kms:GenerateDataKey",
|
||||
"kms:CreateAlias",
|
||||
"kms:DisconnectCustomKeyStore",
|
||||
"kms:DeleteAlias",
|
||||
"ec2:CreateKeyPair",
|
||||
"ec2:DescribeKeyPairs",
|
||||
"ec2:DeleteKeyPair",
|
||||
"acm:*",
|
||||
"config:*",
|
||||
"guardduty:*",
|
||||
"inspector2:*",
|
||||
"securityhub:*",
|
||||
"shield:*",
|
||||
"sso:*",
|
||||
"organizations:*"
|
||||
],
|
||||
"Resource" : "*"
|
||||
},
|
||||
{
|
||||
"Sid" : "NegateNetworkDeployerPermissions",
|
||||
"Effect" : "Allow",
|
||||
"NotAction" : [
|
||||
"ec2:AcceptVpcEndpointConnections",
|
||||
"ec2:AllocateAddress",
|
||||
"ec2:AssignIpv6Addresses",
|
||||
"ec2:AssignPrivateIpAddresses",
|
||||
"ec2:AssociateAddress",
|
||||
"ec2:AssociateDhcpOptions",
|
||||
"ec2:AssociateRouteTable",
|
||||
"ec2:AssociateSubnetCidrBlock",
|
||||
"ec2:AssociateVpcCidrBlock",
|
||||
"ec2:AttachInternetGateway",
|
||||
"ec2:AttachNetworkInterface",
|
||||
"ec2:AttachVpnGateway",
|
||||
"ec2:CreateCarrierGateway",
|
||||
"ec2:CreateCustomerGateway",
|
||||
"ec2:CreateDefaultSubnet",
|
||||
"ec2:CreateDefaultVpc",
|
||||
"ec2:CreateDhcpOptions",
|
||||
"ec2:CreateEgressOnlyInternetGateway",
|
||||
"ec2:CreateFlowLogs",
|
||||
"ec2:CreateInternetGateway",
|
||||
"ec2:CreateNatGateway",
|
||||
"ec2:CreateNetworkAcl",
|
||||
"ec2:CreateNetworkAclEntry",
|
||||
"ec2:CreateNetworkInterface",
|
||||
"ec2:CreateNetworkInterfacePermission",
|
||||
"ec2:CreatePlacementGroup",
|
||||
"ec2:CreateRoute",
|
||||
"ec2:CreateRouteTable",
|
||||
"ec2:CreateSecurityGroup",
|
||||
"ec2:CreateSubnet",
|
||||
"ec2:CreateTags",
|
||||
"ec2:CreateVpc",
|
||||
"ec2:CreateVpcEndpoint",
|
||||
"ec2:CreateVpcEndpointConnectionNotification",
|
||||
"ec2:CreateVpcEndpointServiceConfiguration",
|
||||
"ec2:CreateVpnConnection",
|
||||
"ec2:CreateVpnConnectionRoute",
|
||||
"ec2:CreateVpnGateway",
|
||||
"ec2:DeleteCarrierGateway",
|
||||
"ec2:DeleteEgressOnlyInternetGateway",
|
||||
"ec2:DeleteFlowLogs",
|
||||
"ec2:DeleteNatGateway",
|
||||
"ec2:DeleteNetworkInterface",
|
||||
"ec2:DeleteNetworkInterfacePermission",
|
||||
"ec2:DeletePlacementGroup",
|
||||
"ec2:DeleteSubnet",
|
||||
"ec2:DeleteTags",
|
||||
"ec2:DeleteVpc",
|
||||
"ec2:DeleteVpcEndpointConnectionNotifications",
|
||||
"ec2:DeleteVpcEndpointServiceConfigurations",
|
||||
"ec2:DeleteVpcEndpoints",
|
||||
"ec2:DeleteVpnConnection",
|
||||
"ec2:DeleteVpnConnectionRoute",
|
||||
"ec2:DeleteVpnGateway",
|
||||
"ec2:DescribeAccountAttributes",
|
||||
"ec2:DescribeAddresses",
|
||||
"ec2:DescribeAvailabilityZones",
|
||||
"ec2:DescribeCarrierGateways",
|
||||
"ec2:DescribeClassicLinkInstances",
|
||||
"ec2:DescribeCustomerGateways",
|
||||
"ec2:DescribeDhcpOptions",
|
||||
"ec2:DescribeEgressOnlyInternetGateways",
|
||||
"ec2:DescribeFlowLogs",
|
||||
"ec2:DescribeInstances",
|
||||
"ec2:DescribeInternetGateways",
|
||||
"ec2:DescribeKeyPairs",
|
||||
"ec2:DescribeMovingAddresses",
|
||||
"ec2:DescribeNatGateways",
|
||||
"ec2:DescribeNetworkAcls",
|
||||
"ec2:DescribeNetworkInterfaceAttribute",
|
||||
"ec2:DescribeNetworkInterfacePermissions",
|
||||
"ec2:DescribeNetworkInterfaces",
|
||||
"ec2:DescribePlacementGroups",
|
||||
"ec2:DescribePrefixLists",
|
||||
"ec2:DescribeRouteTables",
|
||||
"ec2:DescribeSecurityGroupReferences",
|
||||
"ec2:DescribeSecurityGroupRules",
|
||||
"ec2:DescribeSecurityGroups",
|
||||
"ec2:DescribeStaleSecurityGroups",
|
||||
"ec2:DescribeSubnets",
|
||||
"ec2:DescribeTags",
|
||||
"ec2:DescribeVpcAttribute",
|
||||
"ec2:DescribeVpcClassicLink",
|
||||
"ec2:DescribeVpcClassicLinkDnsSupport",
|
||||
"ec2:DescribeVpcEndpointConnectionNotifications",
|
||||
"ec2:DescribeVpcEndpointConnections",
|
||||
"ec2:DescribeVpcEndpointServiceConfigurations",
|
||||
"ec2:DescribeVpcEndpointServicePermissions",
|
||||
"ec2:DescribeVpcEndpointServices",
|
||||
"ec2:DescribeVpcEndpoints",
|
||||
"ec2:DescribeVpcPeeringConnections",
|
||||
"ec2:DescribeVpcs",
|
||||
"ec2:DescribeVpnConnections",
|
||||
"ec2:DescribeVpnGateways",
|
||||
"ec2:DescribePublicIpv4Pools",
|
||||
"ec2:DescribeIpv6Pools",
|
||||
"ec2:DetachInternetGateway",
|
||||
"ec2:DetachNetworkInterface",
|
||||
"ec2:DetachVpnGateway",
|
||||
"ec2:DisableVgwRoutePropagation",
|
||||
"ec2:DisableVpcClassicLinkDnsSupport",
|
||||
"ec2:DisassociateAddress",
|
||||
"ec2:DisassociateRouteTable",
|
||||
"ec2:DisassociateSubnetCidrBlock",
|
||||
"ec2:DisassociateVpcCidrBlock",
|
||||
"ec2:EnableVgwRoutePropagation",
|
||||
"ec2:EnableVpcClassicLinkDnsSupport",
|
||||
"ec2:ModifyNetworkInterfaceAttribute",
|
||||
"ec2:ModifySecurityGroupRules",
|
||||
"ec2:ModifySubnetAttribute",
|
||||
"ec2:ModifyVpcAttribute",
|
||||
"ec2:ModifyVpcEndpoint",
|
||||
"ec2:ModifyVpcEndpointConnectionNotification",
|
||||
"ec2:ModifyVpcEndpointServiceConfiguration",
|
||||
"ec2:ModifyVpcEndpointServicePermissions",
|
||||
"ec2:ModifyVpcPeeringConnectionOptions",
|
||||
"ec2:ModifyVpcTenancy",
|
||||
"ec2:MoveAddressToVpc",
|
||||
"ec2:RejectVpcEndpointConnections",
|
||||
"ec2:ReleaseAddress",
|
||||
"ec2:ReplaceNetworkAclAssociation",
|
||||
"ec2:ReplaceNetworkAclEntry",
|
||||
"ec2:ReplaceRoute",
|
||||
"ec2:ReplaceRouteTableAssociation",
|
||||
"ec2:ResetNetworkInterfaceAttribute",
|
||||
"ec2:RestoreAddressToClassic",
|
||||
"ec2:UnassignIpv6Addresses",
|
||||
"ec2:UnassignPrivateIpAddresses",
|
||||
"ec2:UpdateSecurityGroupRuleDescriptionsEgress",
|
||||
"ec2:UpdateSecurityGroupRuleDescriptionsIngress",
|
||||
"ec2:AcceptVpcPeeringConnection",
|
||||
"ec2:AttachClassicLinkVpc",
|
||||
"ec2:AuthorizeSecurityGroupEgress",
|
||||
"ec2:AuthorizeSecurityGroupIngress",
|
||||
"ec2:CreateVpcPeeringConnection",
|
||||
"ec2:DeleteCustomerGateway",
|
||||
"ec2:DeleteDhcpOptions",
|
||||
"ec2:DeleteInternetGateway",
|
||||
"ec2:DeleteNetworkAcl",
|
||||
"ec2:DeleteNetworkAclEntry",
|
||||
"ec2:DeleteRoute",
|
||||
"ec2:DeleteRouteTable",
|
||||
"ec2:DeleteSecurityGroup",
|
||||
"ec2:DeleteVolume",
|
||||
"ec2:DeleteVpcPeeringConnection",
|
||||
"ec2:DetachClassicLinkVpc",
|
||||
"ec2:DisableVpcClassicLink",
|
||||
"ec2:EnableVpcClassicLink",
|
||||
"ec2:GetConsoleScreenshot",
|
||||
"ec2:RejectVpcPeeringConnection",
|
||||
"ec2:RevokeSecurityGroupEgress",
|
||||
"ec2:RevokeSecurityGroupIngress",
|
||||
"ec2:CreateLocalGatewayRoute",
|
||||
"ec2:CreateLocalGatewayRouteTableVpcAssociation",
|
||||
"ec2:DeleteLocalGatewayRoute",
|
||||
"ec2:DeleteLocalGatewayRouteTableVpcAssociation",
|
||||
"ec2:DescribeLocalGatewayRouteTableVirtualInterfaceGroupAssociations",
|
||||
"ec2:DescribeLocalGatewayRouteTableVpcAssociations",
|
||||
"ec2:DescribeLocalGatewayRouteTables",
|
||||
"ec2:DescribeLocalGatewayVirtualInterfaceGroups",
|
||||
"ec2:DescribeLocalGatewayVirtualInterfaces",
|
||||
"ec2:DescribeLocalGateways",
|
||||
"ec2:SearchLocalGatewayRoutes",
|
||||
"ec2:AcceptTransitGatewayVpcAttachment",
|
||||
"ec2:AssociateTransitGatewayRouteTable",
|
||||
"ec2:CreateTransitGateway",
|
||||
"ec2:CreateTransitGatewayRoute",
|
||||
"ec2:CreateTransitGatewayRouteTable",
|
||||
"ec2:CreateTransitGatewayVpcAttachment",
|
||||
"ec2:DeleteTransitGateway",
|
||||
"ec2:DeleteTransitGatewayRoute",
|
||||
"ec2:DeleteTransitGatewayRouteTable",
|
||||
"ec2:DeleteTransitGatewayVpcAttachment",
|
||||
"ec2:DescribeTransitGatewayAttachments",
|
||||
"ec2:DescribeTransitGatewayRouteTables",
|
||||
"ec2:DescribeTransitGatewayVpcAttachments",
|
||||
"ec2:DescribeTransitGateways",
|
||||
"ec2:DisableTransitGatewayRouteTablePropagation",
|
||||
"ec2:DisassociateTransitGatewayRouteTable",
|
||||
"ec2:EnableTransitGatewayRouteTablePropagation",
|
||||
"ec2:ExportTransitGatewayRoutes",
|
||||
"ec2:GetTransitGatewayAttachmentPropagations",
|
||||
"ec2:GetTransitGatewayRouteTableAssociations",
|
||||
"ec2:GetTransitGatewayRouteTablePropagations",
|
||||
"ec2:ModifyTransitGateway",
|
||||
"ec2:ModifyTransitGatewayVpcAttachment",
|
||||
"ec2:RejectTransitGatewayVpcAttachment",
|
||||
"ec2:ReplaceTransitGatewayRoute",
|
||||
"ec2:SearchTransitGatewayRoutes",
|
||||
"route53domains:*",
|
||||
"route53resolver:*",
|
||||
"route53:*",
|
||||
"directconnect:*"
|
||||
],
|
||||
"Resource" : "*"
|
||||
},
|
||||
{
|
||||
"Sid" : "NegateDatabaseDeployerPermissions",
|
||||
"Effect" : "Allow",
|
||||
"NotAction" : [
|
||||
"rds:*",
|
||||
"redshift:*",
|
||||
"elasticache:*"
|
||||
],
|
||||
"Resource" : "*"
|
||||
},
|
||||
{
|
||||
"Sid" : "NegateStorageDeployerPermissions",
|
||||
"Effect" : "Allow",
|
||||
"NotAction" : [
|
||||
"s3:*",
|
||||
"ecr:*",
|
||||
"elasticfilesystem:*",
|
||||
"fsx:*"
|
||||
],
|
||||
"Resource" : "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
role = aws_iam_role.CommonDeployer.id
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
output "devsecops-roles" {
|
||||
value = [
|
||||
aws_iam_role.CommonDeployer.arn,
|
||||
aws_iam_role.DatabaseDeployer.arn,
|
||||
aws_iam_role.NetworkDeployer.arn,
|
||||
aws_iam_role.DatabaseDeployer.arn,
|
||||
aws_iam_role.StorageDeployer.arn
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
terraform {
|
||||
required_version = ">= 1.0"
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 3.25"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/* variable "aws-region" {}
|
||||
variable "aws-region-short" {}
|
||||
variable "customer-name" {}
|
||||
variable "environment" {}
|
||||
variable "project" {}
|
||||
variable "application" {}
|
||||
*/
|
||||
variable max_session_duration {
|
||||
type = number
|
||||
default = 14400
|
||||
}
|
||||
variable role-trusted-entity-arn {}
|
||||
@@ -0,0 +1,17 @@
|
||||
# Overview
|
||||
This module performs the following tasks:
|
||||
|
||||
- Enable AWS config
|
||||
- Create AWS config files for CIS benchmark
|
||||
- Create s3 bucket for config use
|
||||
|
||||
## Inputs:
|
||||
| Name | Description | Type | Default | Required |
|
||||
|------|-------------|------|---------|:-----:|
|
||||
| application | name of application | string | none | yes |
|
||||
| environment | capacity of environment (prd/dev/lab) | string | none | yes |
|
||||
| customer-name | owner of aws resources | string | none | yes |
|
||||
| project | name of project | string | none | yes |
|
||||
| default-tags | tags to be added to resources | list | none | yes |
|
||||
| aws-region-short | short name of aws region (e.g. apne1) | string | none | yes |
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
data aws_caller_identity this {}
|
||||
|
||||
resource aws_guardduty_detector gd {
|
||||
enable = true
|
||||
finding_publishing_frequency = "ONE_HOUR"
|
||||
tags = var.default-tags
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
output guardduty-arn {
|
||||
value = aws_guardduty_detector.gd.arn
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
variable "default-tags" {}
|
||||
@@ -0,0 +1,24 @@
|
||||
# iam-user module
|
||||
Module for creating IAM user. Credentials, if any, will be stored in secretsmanager
|
||||
|
||||
## Example
|
||||
```terraform
|
||||
module iam-user {
|
||||
source = "../../modules/security_identity_compliance/iam-user"
|
||||
|
||||
default-tags = local.default-tags
|
||||
iam-user-name = var.iam-user-name
|
||||
iam-user-policy = ""
|
||||
iam-user-policy-name = "SelfServicePermissions"
|
||||
create-access-key = false
|
||||
create-password = false
|
||||
managed-policy-arns = ["arn:aws:iam::aws:policy/job-function/ViewOnlyAccess"]
|
||||
create-group = true
|
||||
add-to-groups = []
|
||||
iam-group-name = var.iam-group-name
|
||||
}
|
||||
|
||||
output iam-user-arn {
|
||||
value = module.iam-user.iam-user-arn
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,17 @@
|
||||
resource "aws_iam_group" "iam-group" {
|
||||
name = var.iam-group-name
|
||||
}
|
||||
|
||||
resource "aws_iam_group_policy" "iam-group-policy-new-group" {
|
||||
count = var.iam-group-policy != "" ? 1 : 0
|
||||
name = var.iam-group-policy-name
|
||||
group = aws_iam_group.iam-group.name
|
||||
policy = var.iam-group-policy
|
||||
}
|
||||
|
||||
resource "aws_iam_group_policy_attachment" "iam-group-managed-policies" {
|
||||
count = length(var.managed-policy-arns) > 0 ? 1 : 0
|
||||
group = aws_iam_group.iam-group.name
|
||||
policy_arn = var.managed-policy-arns[count.index]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
output iam-group-name {
|
||||
value = aws_iam_group.iam-group.name
|
||||
}
|
||||
|
||||
output iam-group-arn {
|
||||
value = aws_iam_group.iam-group.arn
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
variable managed-policy-arns {}
|
||||
variable iam-group-name {}
|
||||
variable iam-group-policy {}
|
||||
variable iam-group-policy-name {}
|
||||
@@ -0,0 +1,9 @@
|
||||
terraform {
|
||||
required_version = ">= 1.3.9"
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
BSD Zero Clause License
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
@@ -0,0 +1,52 @@
|
||||
<!-- This readme file is generated with terraform-docs -->
|
||||
## Requirements
|
||||
|
||||
| Name | Version |
|
||||
|------|---------|
|
||||
| terraform | >= 1.3.0 |
|
||||
| aws | ~> 5.0 |
|
||||
|
||||
## Providers
|
||||
|
||||
| Name | Version |
|
||||
|------|---------|
|
||||
| aws | ~> 5.0 |
|
||||
|
||||
## Modules
|
||||
|
||||
No modules.
|
||||
|
||||
## Resources
|
||||
|
||||
| Name | Type |
|
||||
|------|------|
|
||||
| [aws_iam_instance_profile.ip](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource |
|
||||
| [aws_iam_policy.p](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
|
||||
| [aws_iam_role.r](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
|
||||
| [aws_iam_role_policy_attachment.pa](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
|
||||
|
||||
## Inputs
|
||||
|
||||
| Name | Description | Type | Default | Required |
|
||||
|------|-------------|------|---------|:--------:|
|
||||
| create-instance-profile | Determines whether instance profile will be created | `bool` | `false` | no |
|
||||
| description | Description of IAM role | `string` | n/a | yes |
|
||||
| max-session-duration | Max session duration in seconds | `number` | `3600` | no |
|
||||
| path | Path of IAM role. Defaults to /Customer/ | `string` | `"/Customer/"` | no |
|
||||
| policies | Map of policies to be created and attached | <pre>map(<br> object(<br> {<br> description = string<br> policy = string<br> }<br> )<br> )</pre> | `{}` | no |
|
||||
| role-name | Name of IAM role | `string` | n/a | yes |
|
||||
| tags | Tags additional to default tags | `map(string)` | `{}` | no |
|
||||
| trusted-entity | AWS service allowed to assume this role or a full assume role policy | `string` | n/a | yes |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Name | Description |
|
||||
|------|-------------|
|
||||
| instance-profile-arn | ARN of IAM instance profile |
|
||||
| name | Name of IAM role |
|
||||
| profile-name | Name of IAM instance profile |
|
||||
| role-arn | IAM role ARN |
|
||||
|
||||
---
|
||||
## Authorship
|
||||
This module was developed by xpk.
|
||||
@@ -0,0 +1,50 @@
|
||||
# Assume role policy can be provided as-is, or built using the trusted-entity variable
|
||||
locals {
|
||||
assume-role-policy = endswith(var.trusted-entity, ".com") ? jsonencode(
|
||||
{
|
||||
"Version" : "2012-10-17",
|
||||
"Statement" : [
|
||||
{
|
||||
"Effect" : "Allow",
|
||||
"Principal" : {
|
||||
"Service" : [
|
||||
var.trusted-entity
|
||||
]
|
||||
},
|
||||
"Action" : "sts:AssumeRole"
|
||||
}
|
||||
]
|
||||
}
|
||||
) : var.trusted-entity
|
||||
}
|
||||
|
||||
resource "aws_iam_instance_profile" "ip" {
|
||||
count = var.create-instance-profile ? 1 : 0
|
||||
name = "${var.role-name}-profile"
|
||||
role = aws_iam_role.r.name
|
||||
path = var.path
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "r" {
|
||||
name = var.role-name
|
||||
description = var.description
|
||||
assume_role_policy = local.assume-role-policy
|
||||
force_detach_policies = true
|
||||
path = var.path
|
||||
max_session_duration = var.max-session-duration
|
||||
tags = var.tags
|
||||
}
|
||||
|
||||
resource "aws_iam_policy" "p" {
|
||||
for_each = var.policies
|
||||
description = each.value.description
|
||||
name = each.key
|
||||
policy = each.value.policy
|
||||
tags = var.tags
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "pa" {
|
||||
for_each = aws_iam_policy.p
|
||||
role = aws_iam_role.r.name
|
||||
policy_arn = each.value.arn
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
output "profile-name" {
|
||||
description = "Name of IAM instance profile"
|
||||
value = aws_iam_instance_profile.ip[*].name
|
||||
}
|
||||
|
||||
output "role-arn" {
|
||||
description = "IAM role ARN"
|
||||
value = aws_iam_role.r.arn
|
||||
}
|
||||
|
||||
output "name" {
|
||||
description = "Name of IAM role"
|
||||
value = aws_iam_role.r.name
|
||||
}
|
||||
|
||||
output "instance-profile-arn" {
|
||||
description = "ARN of IAM instance profile"
|
||||
value = aws_iam_instance_profile.ip.*.arn
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
variable "create-instance-profile" {
|
||||
description = "Determines whether instance profile will be created"
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "description" {
|
||||
description = "Description of IAM role"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "policies" {
|
||||
description = "Map of policies to be created and attached"
|
||||
type = map(
|
||||
object(
|
||||
{
|
||||
description = string
|
||||
policy = string
|
||||
}
|
||||
)
|
||||
)
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "role-name" {
|
||||
description = "Name of IAM role"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "path" {
|
||||
description = "Path of IAM role. Defaults to /Customer/"
|
||||
type = string
|
||||
default = "/Customer/"
|
||||
}
|
||||
|
||||
variable "trusted-entity" {
|
||||
description = "AWS service allowed to assume this role or a full assume role policy"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "max-session-duration" {
|
||||
description = "Max session duration in seconds"
|
||||
type = number
|
||||
default = 3600
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
description = "Tags additional to default tags"
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
BSD Zero Clause License
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
@@ -0,0 +1,51 @@
|
||||
<!-- This readme file is generated with terraform-docs -->
|
||||
Inline policy for IAM role is not supported by this module. Use managed policies instead.
|
||||
|
||||
## Requirements
|
||||
|
||||
| Name | Version |
|
||||
|------|---------|
|
||||
| terraform | >= 1.3.0 |
|
||||
| aws | >= 5.4.0 |
|
||||
|
||||
## Providers
|
||||
|
||||
| Name | Version |
|
||||
|------|---------|
|
||||
| aws | >= 5.4.0 |
|
||||
|
||||
## 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 |
|
||||
|
||||
## Inputs
|
||||
|
||||
| Name | Description | Type | Default | Required |
|
||||
|------|-------------|------|---------|:--------:|
|
||||
| assume-role-policy | The actual assume role policy if trusted-entity is not provided. | `string` | `null` | no |
|
||||
| create-instance-profile | Determines whether instance profile will be created | `bool` | `false` | no |
|
||||
| description | Description of IAM role | `string` | n/a | yes |
|
||||
| managed-policy-arns | List of managed policies to be attached to role | `list(string)` | `null` | no |
|
||||
| path | Path of IAM role. Defaults to /Customer/ | `string` | `"/Customer/"` | no |
|
||||
| role-name | Name of IAM role | `string` | n/a | yes |
|
||||
| trusted-entity | AWS service allowed to assume this role. Either this or assume-role-policy must be provided. | `string` | n/a | yes |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Name | Description |
|
||||
|------|-------------|
|
||||
| instance-profile-arn | ARN of IAM instance profile |
|
||||
| name | Name of IAM role |
|
||||
| profile-name | Name of IAM instance profile |
|
||||
| role-arn | IAM role ARN |
|
||||
|
||||
---
|
||||
## Authorship
|
||||
This module was developed by xpk.
|
||||
@@ -0,0 +1,40 @@
|
||||
# Assume role policy can be provided as-is, or built using the trusted-entity variable
|
||||
locals {
|
||||
assume-role-policy = var.assume-role-policy != null ? var.assume-role-policy : jsonencode(
|
||||
{
|
||||
"Version" : "2012-10-17",
|
||||
"Statement" : [
|
||||
{
|
||||
"Effect" : "Allow",
|
||||
"Principal" : {
|
||||
"Service" : [
|
||||
var.trusted-entity
|
||||
]
|
||||
},
|
||||
"Action" : "sts:AssumeRole"
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
resource "aws_iam_instance_profile" "this" {
|
||||
count = var.create-instance-profile ? 1 : 0
|
||||
name = "${var.role-name}-profile"
|
||||
role = aws_iam_role.this.name
|
||||
path = var.path
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "this" {
|
||||
name = var.role-name
|
||||
description = var.description
|
||||
assume_role_policy = local.assume-role-policy
|
||||
managed_policy_arns = var.managed-policy-arns
|
||||
force_detach_policies = true
|
||||
path = var.path
|
||||
# disable use of inline policy
|
||||
# inline_policy {
|
||||
# name = var.inline-policy-name
|
||||
# policy = var.inline-policy
|
||||
# }
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
output "profile-name" {
|
||||
description = "Name of IAM instance profile"
|
||||
value = aws_iam_instance_profile.this[*].name
|
||||
}
|
||||
|
||||
output "role-arn" {
|
||||
description = "IAM role ARN"
|
||||
value = aws_iam_role.this.arn
|
||||
}
|
||||
|
||||
output "name" {
|
||||
description = "Name of IAM role"
|
||||
value = aws_iam_role.this.name
|
||||
}
|
||||
|
||||
output "instance-profile-arn" {
|
||||
description = "ARN of IAM instance profile"
|
||||
value = aws_iam_instance_profile.this.*.arn
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.4.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
variable "create-instance-profile" {
|
||||
description = "Determines whether instance profile will be created"
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "description" {
|
||||
description = "Description of IAM role"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "managed-policy-arns" {
|
||||
description = "List of managed policies to be attached to role"
|
||||
type = list(string)
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "role-name" {
|
||||
description = "Name of IAM role"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "path" {
|
||||
description = "Path of IAM role. Defaults to /Customer/"
|
||||
type = string
|
||||
default = "/Customer/"
|
||||
}
|
||||
|
||||
variable "trusted-entity" {
|
||||
description = "AWS service allowed to assume this role. Set this to null if assume-role-policy is to be provided."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "assume-role-policy" {
|
||||
description = "The actual assume role policy if trusted-entity is not provided."
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<!-- This readme file is generated with terraform-docs -->
|
||||
## Requirements
|
||||
|
||||
No requirements.
|
||||
|
||||
## Providers
|
||||
|
||||
| Name | Version |
|
||||
|------|---------|
|
||||
| aws | n/a |
|
||||
| random | n/a |
|
||||
|
||||
## Modules
|
||||
|
||||
No modules.
|
||||
|
||||
## Resources
|
||||
|
||||
| Name | Type |
|
||||
|------|------|
|
||||
| [aws_iam_access_key.iam-user-access-key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key) | resource |
|
||||
| [aws_iam_group_membership.group-membership](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_group_membership) | resource |
|
||||
| [aws_iam_user.iam-user](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user) | resource |
|
||||
| [aws_iam_user_login_profile.iam-user-profile](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_login_profile) | resource |
|
||||
| [aws_iam_user_policy.iam-user-policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_policy) | resource |
|
||||
| [aws_iam_user_policy.iam-user-selfservice-policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_policy) | resource |
|
||||
| [aws_iam_user_policy_attachment.iam-user-managed-policies](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_policy_attachment) | resource |
|
||||
| [aws_secretsmanager_secret.secretmanager](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret) | resource |
|
||||
| [aws_secretsmanager_secret_version.iam-user-secret](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_version) | resource |
|
||||
| [random_id.secrets-random-id](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource |
|
||||
| [random_password.iam-user-pass](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource |
|
||||
| [aws_iam_policy_document.user-policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
|
||||
|
||||
## Inputs
|
||||
|
||||
| Name | Description | Type | Default | Required |
|
||||
|------|-------------|------|---------|:--------:|
|
||||
| add-to-groups | n/a | `list(string)` | `[]` | no |
|
||||
| create-access-key | n/a | `bool` | n/a | yes |
|
||||
| create-password | n/a | `bool` | n/a | yes |
|
||||
| iam-user-name | n/a | `any` | n/a | yes |
|
||||
| iam-user-policy | n/a | `string` | `""` | no |
|
||||
| iam-user-policy-name | n/a | `string` | `""` | no |
|
||||
| managed-policy-arns | n/a | `any` | n/a | yes |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Name | Description |
|
||||
|------|-------------|
|
||||
| iam-user-access-key | n/a |
|
||||
| iam-user-arn | n/a |
|
||||
| iam-user-name | n/a |
|
||||
|
||||
---
|
||||
## Authorship
|
||||
This module was developed by xpk.
|
||||
@@ -0,0 +1,99 @@
|
||||
resource "aws_iam_user" "iam-user" {
|
||||
name = var.iam-user-name
|
||||
force_destroy = true
|
||||
}
|
||||
|
||||
resource "aws_iam_access_key" "iam-user-access-key" {
|
||||
count = var.create-access-key ? 1 : 0
|
||||
user = aws_iam_user.iam-user.name
|
||||
}
|
||||
|
||||
resource "aws_iam_user_policy" "iam-user-policy" {
|
||||
count = var.iam-user-policy != "" ? 1 : 0
|
||||
name = var.iam-user-policy-name
|
||||
user = aws_iam_user.iam-user.name
|
||||
policy = var.iam-user-policy
|
||||
}
|
||||
|
||||
resource "aws_iam_user_policy" "iam-user-selfservice-policy" {
|
||||
name = "SelfServicePermissions"
|
||||
user = aws_iam_user.iam-user.name
|
||||
policy = data.aws_iam_policy_document.user-policy.json
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "user-policy" {
|
||||
statement {
|
||||
sid = "ManageOwnCredentials"
|
||||
|
||||
actions = [
|
||||
"iam:ChangePassword",
|
||||
"iam:UpdateLoginProfile",
|
||||
"iam:CreateAccessKey",
|
||||
"iam:DeleteAccessKey",
|
||||
"iam:ListAccessKeys",
|
||||
"iam:CreateVirtualMFADevice",
|
||||
"iam:EnableMFADevice",
|
||||
"iam:ListMFA*",
|
||||
"iam:ListVirtualMFA*",
|
||||
"iam:ResyncMFADevice",
|
||||
"iam:GetUser"
|
||||
]
|
||||
|
||||
effect = "Allow"
|
||||
resources = ["arn:aws:iam::*:user/$${aws:username}"]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "GetBasicUserInfo"
|
||||
actions = [
|
||||
"iam:GetAccountPasswordPolicy",
|
||||
"iam:GetAccessKeyLastUsed",
|
||||
"iam:GetUserPolicy"
|
||||
]
|
||||
effect = "Allow"
|
||||
resources = ["*"]
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_user_policy_attachment" "iam-user-managed-policies" {
|
||||
count = length(var.add-to-groups) > 0 ? 0 : length(var.managed-policy-arns)
|
||||
user = aws_iam_user.iam-user.name
|
||||
policy_arn = var.managed-policy-arns[count.index]
|
||||
}
|
||||
|
||||
resource "aws_iam_user_login_profile" "iam-user-profile" {
|
||||
count = var.create-password ? 1 : 0
|
||||
user = aws_iam_user.iam-user.name
|
||||
password_length = 20
|
||||
pgp_key = null
|
||||
}
|
||||
|
||||
resource "random_id" "secrets-random-id" {
|
||||
byte_length = 2
|
||||
}
|
||||
|
||||
resource "aws_secretsmanager_secret" "secretmanager" {
|
||||
count = var.create-access-key || var.create-password ? 1 : 0
|
||||
name = "IamUserCredential-${random_id.secrets-random-id.dec}-${var.iam-user-name}"
|
||||
description = "AWS resource credential"
|
||||
}
|
||||
|
||||
resource "aws_secretsmanager_secret_version" "iam-user-secret" {
|
||||
count = var.create-access-key || var.create-password ? 1 : 0
|
||||
secret_id = aws_secretsmanager_secret.secretmanager[0].id
|
||||
secret_string = jsonencode(
|
||||
{
|
||||
"ConsolePassword" : length(aws_iam_user_login_profile.iam-user-profile[0].password) > 0 ? aws_iam_user_login_profile.iam-user-profile[0].password : "NotSet",
|
||||
"AccessKeyId" : length(aws_iam_access_key.iam-user-access-key) > 0 ? aws_iam_access_key.iam-user-access-key[0].id : "NotSet",
|
||||
"KeySecret" : length(aws_iam_access_key.iam-user-access-key) > 0 ? aws_iam_access_key.iam-user-access-key[0].secret : "NotSet"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
resource "aws_iam_group_membership" "group-membership" {
|
||||
for_each = toset(var.add-to-groups)
|
||||
name = "MembershipToExistingGroups"
|
||||
group = each.value
|
||||
users = [aws_iam_user.iam-user.name]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
output "iam-user-name" {
|
||||
value = aws_iam_user.iam-user.name
|
||||
}
|
||||
|
||||
output "iam-user-arn" {
|
||||
value = aws_iam_user.iam-user.arn
|
||||
}
|
||||
|
||||
output "iam-user-access-key" {
|
||||
value = try(aws_iam_access_key.iam-user-access-key[0].id, "none")
|
||||
}
|
||||
|
||||
output "iam-user-secret-arn" {
|
||||
value = try(aws_secretsmanager_secret_version.iam-user-secret[0].arn, "none")
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
variable "iam-user-name" {}
|
||||
variable "iam-user-policy" {
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
variable "iam-user-policy-name" {
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
variable "create-access-key" {
|
||||
type = bool
|
||||
}
|
||||
variable "create-password" {
|
||||
type = bool
|
||||
}
|
||||
variable "managed-policy-arns" {}
|
||||
variable "add-to-groups" {
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
terraform {
|
||||
required_version = ">= 1.3.9"
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# inspector2 module
|
||||
Via awscli, enable inspector2 scanning of ECR repositories
|
||||
@@ -0,0 +1,11 @@
|
||||
resource "null_resource" "cli-inspector2" {
|
||||
provisioner "local-exec" {
|
||||
when = create
|
||||
command = "/bin/bash -c 'aws inspector2 enable --resource-types \"ECR\"'"
|
||||
}
|
||||
|
||||
provisioner "local-exec" {
|
||||
when = destroy
|
||||
command = "/bin/bash -c 'aws inspector2 disable --resource-types \"ECR\"; sleep 30; aws inspector2 disable'"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
resource "aws_s3_account_public_access_block" "default-s3-public-access-settings" {
|
||||
block_public_acls = true
|
||||
block_public_policy = true
|
||||
ignore_public_acls = true
|
||||
restrict_public_buckets = true
|
||||
lifecycle { ignore_changes = all }
|
||||
}
|
||||
|
||||
resource "aws_ebs_encryption_by_default" "default-ebs-encryption-setting" {
|
||||
enabled = true
|
||||
lifecycle { ignore_changes = all }
|
||||
}
|
||||
|
||||
resource "aws_iam_account_password_policy" "password-policy1" {
|
||||
minimum_password_length = 14
|
||||
require_lowercase_characters = true
|
||||
require_numbers = true
|
||||
require_uppercase_characters = true
|
||||
require_symbols = true
|
||||
allow_users_to_change_password = true
|
||||
max_password_age = 90
|
||||
password_reuse_prevention = 24
|
||||
hard_expiry = true
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
# Overview
|
||||
This module performs the following tasks
|
||||
|
||||
- Create IAM roles based on job functions
|
||||
- Create IAM password policy
|
||||
- Enable IAM access analyzer
|
||||
|
||||
## Inputs:
|
||||
| Name | Description | Type | Default | Required |
|
||||
|------|-------------|------|---------|:-----:|
|
||||
| application | name of application | string | none | yes |
|
||||
| environment | capacity of environment (prd/dev/lab) | string | none | yes |
|
||||
| customer-name | owner of aws resources | string | none | yes |
|
||||
| project | name of project | string | none | yes |
|
||||
| default-tags | tags to be added to resources | list | none | yes |
|
||||
| aws-region-short | short name of aws region (e.g. apne1) | string | none | yes |
|
||||
| create-cloudhealth-resources | create cloudhealth role | bool | none | yes |
|
||||
| cloudheath-ext-id1 | cloudhealth role external id for sts | string | none | no |
|
||||
| cloudheath-ext-id2 | cloudhealth role external id for sts | string | none | no |
|
||||
@@ -0,0 +1,4 @@
|
||||
resource "aws_accessanalyzer_analyzer" "iam-aa" {
|
||||
analyzer_name = "IAMAcecssAnalyzer"
|
||||
tags = var.default-tags
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
resource "aws_iam_role" "cloudhealth-role" {
|
||||
count = var.create-cloudhealth-resources ? 1 : 0
|
||||
name = "CloudHealth-Role"
|
||||
tags = var.default-tags
|
||||
assume_role_policy = <<EOF
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"AWS": "arn:aws:iam::454464851268:root"
|
||||
},
|
||||
"Action": "sts:AssumeRole",
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"sts:ExternalId": [
|
||||
"${var.cloudheath-ext-id1}",
|
||||
"${var.cloudheath-ext-id2}"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
resource "aws_iam_policy" "CloudHealth-Policy" {
|
||||
count = var.create-cloudhealth-resources ? 1 : 0
|
||||
name = "CloudHealthPolicy"
|
||||
policy = jsonencode(
|
||||
{
|
||||
"Version" : "2012-10-17",
|
||||
"Statement" : [
|
||||
{
|
||||
"Sid" : "CloudhealthAccess",
|
||||
"Action" : [
|
||||
"autoscaling:Describe*",
|
||||
"cloudformation:ListStacks",
|
||||
"cloudformation:ListStackResources",
|
||||
"cloudformation:DescribeStacks",
|
||||
"cloudformation:DescribeStackEvents",
|
||||
"cloudformation:DescribeStackResources",
|
||||
"cloudformation:GetTemplate",
|
||||
"cloudfront:Get*",
|
||||
"cloudfront:List*",
|
||||
"cloudtrail:DescribeTrails",
|
||||
"cloudtrail:ListTags",
|
||||
"cloudtrail:Get*",
|
||||
"cloudwatch:Describe*",
|
||||
"cloudwatch:Get*",
|
||||
"cloudwatch:List*",
|
||||
"config:Get*",
|
||||
"config:Describe*",
|
||||
"config:Deliver*",
|
||||
"config:List*",
|
||||
"cur:Describe*",
|
||||
"dms:Describe*",
|
||||
"dms:List*",
|
||||
"dynamodb:DescribeTable",
|
||||
"dynamodb:List*",
|
||||
"ec2:Describe*",
|
||||
"ec2:DescribeRegions",
|
||||
"ec2:GetReservedInstancesExchangeQuote",
|
||||
"ecs:List*",
|
||||
"ecs:Describe*",
|
||||
"elasticache:Describe*",
|
||||
"elasticache:ListTagsForResource",
|
||||
"elasticbeanstalk:Check*",
|
||||
"elasticbeanstalk:Describe*",
|
||||
"elasticbeanstalk:List*",
|
||||
"elasticbeanstalk:RequestEnvironmentInfo",
|
||||
"elasticbeanstalk:RetrieveEnvironmentInfo",
|
||||
"elasticfilesystem:Describe*",
|
||||
"elasticloadbalancing:Describe*",
|
||||
"elasticmapreduce:Describe*",
|
||||
"elasticmapreduce:List*",
|
||||
"es:List*",
|
||||
"es:Describe*",
|
||||
"es:DescribeReservedElasticsearchInstances",
|
||||
"firehose:ListDeliveryStreams",
|
||||
"firehose:DescribeDeliveryStream",
|
||||
"fsx:Describe*",
|
||||
"iam:List*",
|
||||
"iam:Get*",
|
||||
"iam:GenerateCredentialReport",
|
||||
"kinesis:Describe*",
|
||||
"kinesis:List*",
|
||||
"kms:DescribeKey",
|
||||
"kms:GetKeyRotationStatus",
|
||||
"kms:ListKeys",
|
||||
"lambda:List*",
|
||||
"logs:Describe*",
|
||||
"logs:List*",
|
||||
"organizations:ListAccounts",
|
||||
"organizations:ListTagsForResource",
|
||||
"redshift:Describe*",
|
||||
"route53:Get*",
|
||||
"route53:List*",
|
||||
"rds:Describe*",
|
||||
"rds:ListTagsForResource",
|
||||
"s3:GetAccountPublicAccessBlock",
|
||||
"s3:GetBucketAcl",
|
||||
"s3:GetBucketLocation",
|
||||
"s3:GetBucketLogging",
|
||||
"s3:GetBucketPolicy",
|
||||
"s3:GetBucketPolicyStatus",
|
||||
"s3:GetBucketPublicAccessBlock",
|
||||
"s3:GetBucketTagging",
|
||||
"s3:GetBucketVersioning",
|
||||
"s3:GetBucketWebsite",
|
||||
"s3:List*",
|
||||
"sagemaker:Describe*",
|
||||
"sagemaker:List*",
|
||||
"savingsplans:DescribeSavingsPlans",
|
||||
"sdb:GetAttributes",
|
||||
"sdb:List*",
|
||||
"ses:Get*",
|
||||
"ses:List*",
|
||||
"sns:Get*",
|
||||
"sns:List*",
|
||||
"sqs:GetQueueAttributes",
|
||||
"sqs:ListQueues",
|
||||
"storagegateway:List*",
|
||||
"storagegateway:Describe*",
|
||||
"workspaces:Describe*"
|
||||
],
|
||||
"Resource" : "*",
|
||||
"Effect" : "Allow"
|
||||
},
|
||||
{
|
||||
"Sid" : "FineGrainedBillingAccess",
|
||||
"Action" : [
|
||||
"account:Get*",
|
||||
"billing:Get*",
|
||||
"billing:List*",
|
||||
"ce:Describe*",
|
||||
"ce:Get*",
|
||||
"ce:List*",
|
||||
"consolidatedbilling:GetAccountBillingRole",
|
||||
"consolidatedbilling:ListLinkedAccounts",
|
||||
"cur:Get*",
|
||||
"cur:ValidateReportDestination",
|
||||
"freetier:Get*",
|
||||
"invoicing:Get*",
|
||||
"invoicing:List*",
|
||||
"payments:Get*",
|
||||
"payments:List*",
|
||||
"purchase-orders:Get*",
|
||||
"purchase-orders:List*",
|
||||
"tax:Get*",
|
||||
"tax:List*"
|
||||
],
|
||||
"Resource" : "*",
|
||||
"Effect" : "Allow"
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "cloudhealth-role-policy-attach" {
|
||||
count = var.create-cloudhealth-resources ? 1 : 0
|
||||
role = aws_iam_role.cloudhealth-role[1].name
|
||||
policy_arn = aws_iam_policy.CloudHealth-Policy[1].arn
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
resource "aws_iam_account_password_policy" "password-policy1" {
|
||||
minimum_password_length = 14
|
||||
require_lowercase_characters = true
|
||||
require_numbers = true
|
||||
require_uppercase_characters = true
|
||||
require_symbols = true
|
||||
allow_users_to_change_password = true
|
||||
max_password_age = 90
|
||||
password_reuse_prevention = 24
|
||||
hard_expiry = true
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
Create IAM roles based on job functions
|
||||
https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_job-functions.html
|
||||
|
||||
- Administrator
|
||||
- Billing
|
||||
- Database admin
|
||||
- Network admin
|
||||
- Developers
|
||||
- Readonly and support
|
||||
*/
|
||||
|
||||
data aws_caller_identity this {}
|
||||
|
||||
data aws_iam_policy_document assume-role-policy {
|
||||
statement {
|
||||
sid = "AllowMyAccount"
|
||||
effect = "Allow"
|
||||
actions = ["sts:AssumeRole"]
|
||||
principals {
|
||||
identifiers = [data.aws_caller_identity.this.account_id]
|
||||
type = "AWS"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource aws_iam_role administrator-role {
|
||||
name = "${var.customer-name}-awsadmin"
|
||||
description = "Provides full access to AWS services and resources."
|
||||
tags = var.default-tags
|
||||
assume_role_policy = data.aws_iam_policy_document.assume-role-policy.json
|
||||
path = "/${var.customer-name}/"
|
||||
max_session_duration = 7200
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "administrator-role-policy-attach" {
|
||||
role = aws_iam_role.administrator-role.name
|
||||
policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
|
||||
}
|
||||
|
||||
resource aws_iam_role billing-role {
|
||||
name = "${var.customer-name}-billing"
|
||||
description = "Grants permissions for billing and cost management."
|
||||
tags = var.default-tags
|
||||
assume_role_policy = data.aws_iam_policy_document.assume-role-policy.json
|
||||
path = "/${var.customer-name}/"
|
||||
max_session_duration = 3600
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "billing-role-policy-attach" {
|
||||
role = aws_iam_role.billing-role.name
|
||||
policy_arn = "arn:aws:iam::aws:policy/job-function/Billing"
|
||||
}
|
||||
|
||||
resource aws_iam_role dba-role {
|
||||
name = "${var.customer-name}-dba"
|
||||
description = "AWS database admin role"
|
||||
tags = var.default-tags
|
||||
assume_role_policy = data.aws_iam_policy_document.assume-role-policy.json
|
||||
path = "/${var.customer-name}/"
|
||||
max_session_duration = 7200
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "dba-role-policy-attach" {
|
||||
role = aws_iam_role.dba-role.name
|
||||
policy_arn = "arn:aws:iam::aws:policy/job-function/DatabaseAdministrator"
|
||||
}
|
||||
|
||||
resource aws_iam_role network-admin-role {
|
||||
name = "${var.customer-name}-networkadmin"
|
||||
description = "AWS network admin role"
|
||||
tags = var.default-tags
|
||||
assume_role_policy = data.aws_iam_policy_document.assume-role-policy.json
|
||||
path = "/${var.customer-name}/"
|
||||
max_session_duration = 7200
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "network-admin-role-policy-attach" {
|
||||
role = aws_iam_role.network-admin-role.name
|
||||
policy_arn = "arn:aws:iam::aws:policy/job-function/NetworkAdministrator"
|
||||
}
|
||||
|
||||
resource aws_iam_role developer-role {
|
||||
name = "${var.customer-name}-developer"
|
||||
description = "Provides full access to AWS resources excluding IAM."
|
||||
tags = var.default-tags
|
||||
assume_role_policy = data.aws_iam_policy_document.assume-role-policy.json
|
||||
path = "/${var.customer-name}/"
|
||||
max_session_duration = 7200
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "developer-role-policy-attach1" {
|
||||
role = aws_iam_role.developer-role.name
|
||||
policy_arn = "arn:aws:iam::aws:policy/PowerUserAccess"
|
||||
}
|
||||
|
||||
resource aws_iam_role securityaudit-role {
|
||||
name = "${var.customer-name}-securityaudit"
|
||||
description = "Role to read security configuration metadata."
|
||||
tags = var.default-tags
|
||||
assume_role_policy = data.aws_iam_policy_document.assume-role-policy.json
|
||||
path = "/${var.customer-name}/"
|
||||
max_session_duration = 7200
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "securityaudit-role-policy-attach1" {
|
||||
role = aws_iam_role.securityaudit-role.name
|
||||
policy_arn = "arn:aws:iam::aws:policy/SecurityAudit"
|
||||
}
|
||||
|
||||
resource aws_iam_role support-role {
|
||||
name = "${var.customer-name}-support"
|
||||
description = "Role to troubleshoot and resolve issues in AWS."
|
||||
tags = var.default-tags
|
||||
assume_role_policy = data.aws_iam_policy_document.assume-role-policy.json
|
||||
path = "/${var.customer-name}/"
|
||||
max_session_duration = 7200
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "support-role-policy-attach1" {
|
||||
role = aws_iam_role.support-role.name
|
||||
policy_arn = "arn:aws:iam::aws:policy/job-function/SupportUser"
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "support-role-policy-attach2" {
|
||||
role = aws_iam_role.support-role.name
|
||||
policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
variable "customer-name" {}
|
||||
variable "default-tags" {}
|
||||
variable "cloudtrail-retain-days" {
|
||||
type = number
|
||||
default = 90
|
||||
}
|
||||
|
||||
variable "create-cloudhealth-resources" {
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "cloudheath-ext-id1" {
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
variable "cloudheath-ext-id2" {
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
# secretsmanager-secret module
|
||||
This module creates an entry in secretsmanager, attaching a default access policy if one is
|
||||
not provided from root module. A random suffix is assigned to every secret, as AWS may delay
|
||||
creation of secrets with the same name, after the old one has been destroyed that is.
|
||||
|
||||
The default policy attached to secretsmanager prevents cross-account access.
|
||||
|
||||
To have this module generate a random password, set ```generate_secret``` to true.
|
||||
|
||||
To tag resources, please use provider default_tags.
|
||||
|
||||
## Example
|
||||
```hcl
|
||||
module "secret1" {
|
||||
source = "../../modules/security_identity_compliance/secretsmanager-secret"
|
||||
|
||||
secret_name = "test-secret-name-1"
|
||||
secret_description = "test-secret-desc-1"
|
||||
secret_value = "test-secret-value"
|
||||
}
|
||||
|
||||
module "secret2" {
|
||||
source = "../../modules/security_identity_compliance/secretsmanager-secret"
|
||||
|
||||
secret_name = "test-secret-name-2"
|
||||
secret_description = "test-secret-desc-3"
|
||||
generate_secret = true
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,15 @@
|
||||
module "secret1" {
|
||||
source = "../"
|
||||
|
||||
secret_name = "test-secret-name-1"
|
||||
secret_description = "test-secret-desc-1"
|
||||
secret_value = "test-secret-value"
|
||||
}
|
||||
|
||||
module "secret2" {
|
||||
source = "../"
|
||||
|
||||
secret_name = "test-secret-name-2"
|
||||
secret_description = "test-secret-desc-3"
|
||||
generate_secret = true
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
data "aws_caller_identity" "this" {}
|
||||
|
||||
resource "random_id" "rid" {
|
||||
byte_length = 2
|
||||
}
|
||||
|
||||
resource "aws_secretsmanager_secret" "secret1" {
|
||||
name = "${var.secret_name}-${random_id.rid.dec}"
|
||||
description = var.secret_description
|
||||
kms_key_id = var.kms_key_id == null ? null : var.kms_key_id
|
||||
}
|
||||
|
||||
resource "aws_secretsmanager_secret_version" "this" {
|
||||
secret_id = aws_secretsmanager_secret.secret1.id
|
||||
secret_string = var.generate_secret ? data.aws_secretsmanager_random_password.this.random_password : var.secret_value
|
||||
}
|
||||
|
||||
data "aws_secretsmanager_random_password" "this" {
|
||||
password_length = 22
|
||||
exclude_numbers = false
|
||||
exclude_characters = "o![]\\"
|
||||
exclude_lowercase = false
|
||||
exclude_punctuation = false
|
||||
exclude_uppercase = false
|
||||
include_space = false
|
||||
require_each_included_type = true
|
||||
}
|
||||
|
||||
# resource "random_password" "this" {
|
||||
# count = var.generate_secret ? 1 : 0
|
||||
# length = 22
|
||||
# special = true
|
||||
# }
|
||||
|
||||
resource "aws_secretsmanager_secret_policy" "policy" {
|
||||
secret_arn = aws_secretsmanager_secret.secret1.arn
|
||||
policy = var.secret_policy != null ? var.secret_policy : data.aws_iam_policy_document.policy-file.json
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "policy-file" {
|
||||
statement {
|
||||
sid = "DenyCrossAccountAccess"
|
||||
effect = "Deny"
|
||||
|
||||
principals {
|
||||
identifiers = ["*"]
|
||||
type = "AWS"
|
||||
}
|
||||
|
||||
condition {
|
||||
test = "StringNotEquals"
|
||||
values = [data.aws_caller_identity.this.account_id]
|
||||
variable = "aws:PrincipalAccount"
|
||||
}
|
||||
|
||||
actions = ["secretsmanager:GetSecretValue"]
|
||||
resources = [aws_secretsmanager_secret.secret1.arn]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
output "secret_arn" {
|
||||
value = aws_secretsmanager_secret.secret1.arn
|
||||
}
|
||||
|
||||
output "secret_id" {
|
||||
value = "${var.secret_name}-${random_id.rid.dec}"
|
||||
}
|
||||
|
||||
# output "generated_password" {
|
||||
# value = try(random_password.this[0].result, "None")
|
||||
# sensitive = true
|
||||
# }
|
||||
@@ -0,0 +1,9 @@
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
variable "secret_description" {}
|
||||
variable "secret_name" {}
|
||||
variable "secret_value" {
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
variable "secret_policy" {
|
||||
type = string
|
||||
default = null
|
||||
description = "By default, cross-account access is denied"
|
||||
}
|
||||
|
||||
variable "generate_secret" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "If set to true, a secure password will be generated and saved."
|
||||
}
|
||||
|
||||
variable kms_key_id {
|
||||
type = string
|
||||
default = null
|
||||
description = "Custom kms key id. If not specified, the default key aws/secretmanager key will be used."
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
data aws_region this-region {}
|
||||
|
||||
resource "aws_securityhub_account" "sh-account" {}
|
||||
|
||||
resource "aws_securityhub_standards_subscription" "cis" {
|
||||
depends_on = [aws_securityhub_account.sh-account]
|
||||
standards_arn = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
|
||||
}
|
||||
|
||||
resource "aws_securityhub_standards_subscription" "aws" {
|
||||
depends_on = [aws_securityhub_account.sh-account]
|
||||
standards_arn = "arn:aws:securityhub:${data.aws_region.this-region.name}::standards/aws-foundational-security-best-practices/v/1.0.0"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Module sso-aws-id-store
|
||||
This module creates aws sso user using aws's builtin identity store, and put the user in a group.
|
||||
The group must be created in advance.
|
||||
@@ -0,0 +1,33 @@
|
||||
data "aws_ssoadmin_instances" "sso1" {}
|
||||
|
||||
resource "aws_identitystore_user" "sso-user" {
|
||||
identity_store_id = tolist(data.aws_ssoadmin_instances.sso1.identity_store_ids)[0]
|
||||
display_name = "${var.firstName} ${var.lastName}"
|
||||
user_name = var.username
|
||||
nickname = var.username
|
||||
emails {
|
||||
primary = true
|
||||
value = var.email
|
||||
}
|
||||
|
||||
name {
|
||||
family_name = var.lastName
|
||||
given_name = var.firstName
|
||||
}
|
||||
}
|
||||
|
||||
data "aws_identitystore_group" "sso-group" {
|
||||
identity_store_id = tolist(data.aws_ssoadmin_instances.sso1.identity_store_ids)[0]
|
||||
alternate_identifier {
|
||||
unique_attribute {
|
||||
attribute_path = "DisplayName"
|
||||
attribute_value = var.groupName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_identitystore_group_membership" "sso-group-membership" {
|
||||
identity_store_id = tolist(data.aws_ssoadmin_instances.sso1.identity_store_ids)[0]
|
||||
group_id = data.aws_identitystore_group.sso-group.group_id
|
||||
member_id = aws_identitystore_user.sso-user.user_id
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
variable username {}
|
||||
variable firstName {}
|
||||
variable lastName {}
|
||||
variable email {}
|
||||
variable groupName {}
|
||||
@@ -0,0 +1,33 @@
|
||||
# SSO permission set module
|
||||
|
||||
## Root module example
|
||||
```
|
||||
module sso {
|
||||
source = "../modules/sso"
|
||||
|
||||
for_each = { for item in local.items : item.name => item }
|
||||
|
||||
default-tags = local.default-tags
|
||||
pset-name = each.value.name
|
||||
pset-desc = each.value.desc
|
||||
pset-managed-policy-arn = each.value.mpolicy
|
||||
pset-session-duration = each.value.session
|
||||
|
||||
}
|
||||
|
||||
locals {
|
||||
csv_data = <<-CSV
|
||||
name,desc,mpolicy,session
|
||||
ViewOnly,View only access,arn:aws:iam::aws:policy/job-function/ViewOnlyAccess,PT4H
|
||||
ReadOnly,Read only access,arn:aws:iam::aws:policy/ReadOnlyAccess,PT4H
|
||||
FullAccess,Full admin access,arn:aws:iam::aws:policy/AdministratorAccess,PT4H
|
||||
NetworkAdmin,Network admin access,arn:aws:iam::aws:policy/job-function/NetworkAdministrator,PT4H
|
||||
DatabaseAdmin,Database admin access,arn:aws:iam::aws:policy/job-function/DatabaseAdministrator,PT4H
|
||||
BillingAdmin,Billing admin access,arn:aws:iam::aws:policy/job-function/Billing,PT4H
|
||||
SecurityAudit,Security admin access,arn:aws:iam::aws:policy/SecurityAudit,PT4H
|
||||
PowerUser,Full access excluding IAM,arn:aws:iam::aws:policy/PowerUserAccess,PT4H
|
||||
CSV
|
||||
|
||||
items = csvdecode(local.csv_data)
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
data "aws_ssoadmin_instances" "sso1" {}
|
||||
|
||||
resource "aws_ssoadmin_permission_set" "pset" {
|
||||
name = var.pset-name
|
||||
description = var.pset-desc
|
||||
instance_arn = tolist(data.aws_ssoadmin_instances.sso1.arns)[0]
|
||||
session_duration = var.pset-session-duration
|
||||
tags = var.default-tags
|
||||
}
|
||||
|
||||
resource "aws_ssoadmin_managed_policy_attachment" "psetatt" {
|
||||
instance_arn = tolist(data.aws_ssoadmin_instances.sso1.arns)[0]
|
||||
managed_policy_arn = var.pset-managed-policy-arn
|
||||
permission_set_arn = aws_ssoadmin_permission_set.pset.arn
|
||||
}
|
||||
|
||||
# use inline policy for additional permissions. aws sso will populate this policy to target accounts
|
||||
# automatically. customer managed policies, on the other hand, needs to be created manually in the target accounts.
|
||||
resource "aws_ssoadmin_permission_set_inline_policy" "pset-inline-policy1" {
|
||||
count = length(var.inline-policy-json) > 0 ? 1 : 0
|
||||
instance_arn = tolist(data.aws_ssoadmin_instances.sso1.arns)[0]
|
||||
permission_set_arn = aws_ssoadmin_permission_set.pset.arn
|
||||
inline_policy = var.inline-policy-json
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
output pset-name {
|
||||
value = aws_ssoadmin_permission_set.pset.name
|
||||
}
|
||||
|
||||
output pset-arn {
|
||||
value = aws_ssoadmin_permission_set.pset.arn
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
variable pset-name {}
|
||||
variable pset-desc {}
|
||||
variable pset-session-duration {}
|
||||
variable default-tags {}
|
||||
variable pset-managed-policy-arn {}
|
||||
variable inline-policy-json {}
|
||||
@@ -0,0 +1,96 @@
|
||||
module "terraform-user" {
|
||||
source = "../iam-user"
|
||||
|
||||
create-access-key = true
|
||||
create-password = false
|
||||
default-tags = var.default-tags
|
||||
iam-user-name = "${var.user-name}-${formatdate("YYYYMMDD_hhmm", timestamp())}"
|
||||
managed-policy-arns = lookup(local.CannedPoliciesByServiceCategory, var.service-category)
|
||||
pgp-key = var.gpg-key
|
||||
}
|
||||
|
||||
locals {
|
||||
CannedPoliciesByServiceCategory = {
|
||||
NetworkingContentDelivery = [
|
||||
"arn:aws:iam::aws:policy/NetworkAdministrator",
|
||||
"arn:aws:iam::aws:policy/AmazonRoute53FullAccess",
|
||||
"arn:aws:iam::aws:policy/GlobalAcceleratorFullAccess"
|
||||
]
|
||||
SecurityIdentityCompliance = [
|
||||
"arn:aws:iam::aws:policy/IAMFullAccess",
|
||||
"arn:aws:iam::aws:policy/SecurityAudit",
|
||||
"arn:aws:iam::aws:policy/AWSSecurityHubFullAccess",
|
||||
"arn:aws:iam::aws:policy/AmazonGuardDutyFullAccess",
|
||||
"arn:aws:iam::aws:policy/AmazonInspectorFullAccess",
|
||||
"arn:aws:iam::aws:policy/AWSSSODirectoryAdministrator",
|
||||
"arn:aws:iam::aws:policy/AWSOrganizationsFullAccess",
|
||||
"arn:aws:iam::aws:policy/WellArchitectedConsoleFullAccess",
|
||||
"arn:aws:iam::aws:policy/AWSKeyManagementServicePowerUser",
|
||||
"arn:aws:iam::aws:policy/AWSDirectoryServiceFullAccess"
|
||||
]
|
||||
ManagementGovernance = [
|
||||
"arn:aws:iam::aws:policy/CloudWatchFullAccess",
|
||||
"arn:aws:iam::aws:policy/CloudWatchLogsFullAccess",
|
||||
"arn:aws:iam::aws:policy/CloudWatchEventsFullAccess",
|
||||
"arn:aws:iam::aws:policy/AmazonEventBridgeFullAccess",
|
||||
"arn:aws:iam::aws:policy/AmazonSSMFullAccess",
|
||||
"arn:aws:iam::aws:policy/AWSResourceAccessManagerFullAccess",
|
||||
"arn:aws:iam::aws:policy/AWSOrganizationsFullAccess",
|
||||
"arn:aws:iam::aws:policy/AmazonSQSFullAccess",
|
||||
"arn:aws:iam::aws:policy/AmazonSNSFullAccess",
|
||||
"arn:aws:iam::aws:policy/AWSCloudFormationFullAccess"
|
||||
]
|
||||
Compute = [
|
||||
"arn:aws:iam::aws:policy/AmazonEC2FullAccess",
|
||||
"arn:aws:iam::aws:policy/AmazonWorkSpacesAdmin",
|
||||
"arn:aws:iam::aws:policy/AWSMarketplaceFullAccess",
|
||||
"arn:aws:iam::aws:policy/AutoScalingFullAccess",
|
||||
"arn:aws:iam::aws:policy/AWSImageBuilderFullAccess",
|
||||
"arn:aws:iam::aws:policy/AWSBackupFullAccess"
|
||||
]
|
||||
Containers = [
|
||||
"arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryFullAccess",
|
||||
"arn:aws:iam::aws:policy/AmazonECS_FullAccess",
|
||||
"arn:aws:iam::aws:policy/AmazonEC2FullAccess"
|
||||
]
|
||||
Storage = [
|
||||
"arn:aws:iam::aws:policy/AmazonS3FullAccess",
|
||||
"arn:aws:iam::aws:policy/AmazonEC2FullAccess",
|
||||
"arn:aws:iam::aws:policy/AmazonElasticFileSystemFullAccess",
|
||||
"arn:aws:iam::aws:policy/AmazonFSxFullAccess",
|
||||
"arn:aws:iam::aws:policy/AmazonGlacierFullAccess",
|
||||
"arn:aws:iam::aws:policy/AWSBackupFullAccess"
|
||||
]
|
||||
Database = [
|
||||
"arn:aws:iam::aws:policy/DatabaseAdministrator",
|
||||
"arn:aws:iam::aws:policy/AWSBackupFullAccess"
|
||||
]
|
||||
DeveloperTools = [
|
||||
"arn:aws:iam::aws:policy/AWSCodeCommitFullAccess",
|
||||
"arn:aws:iam::aws:policy/AWSCodeBuildAdminAccess",
|
||||
"arn:aws:iam::aws:policy/AWSCodePipeline_FullAccess"
|
||||
]
|
||||
Analytics = [
|
||||
"arn:aws:iam::aws:policy/AmazonOpenSearchServiceFullAccess",
|
||||
"arn:aws:iam::aws:policy/AmazonMSKFullAccess",
|
||||
"arn:aws:iam::aws:policy/AmazonEMRFullAccessPolicy_v2",
|
||||
"arn:aws:iam::aws:policy/AmazonRedshiftFullAccess"
|
||||
]
|
||||
MachineLearning = [
|
||||
"arn:aws:iam::aws:policy/AmazonSageMakerFullAccess",
|
||||
"arn:aws:iam::aws:policy/AmazonMachineLearningFullAccess",
|
||||
"arn:aws:iam::aws:policy/AWSGlueConsoleFullAccess",
|
||||
"arn:aws:iam::aws:policy/AWSStepFunctionsFullAccess"
|
||||
]
|
||||
Serverless = [
|
||||
"arn:aws:iam::aws:policy/AWSLambda_FullAccess",
|
||||
"arn:aws:iam::aws:policy/AdministratorAccess-AWSElasticBeanstalk",
|
||||
"arn:aws:iam::aws:policy/AmazonAPIGatewayAdministrator",
|
||||
"arn:aws:iam::aws:policy/AWSDirectoryServiceFullAccess",
|
||||
"arn:aws:iam::aws:policy/AmazonSESFullAccess",
|
||||
"arn:aws:iam::aws:policy/AmazonWorkSpacesAdmin"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
output keys {
|
||||
value = {
|
||||
access-key = module.terraform-user.iam-user-access-key-pgp
|
||||
secret-key = module.terraform-user.iam-user-secret-key-pgp
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 4.40"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
variable default-tags {}
|
||||
variable user-name {
|
||||
type = string
|
||||
default = "terraform-role"
|
||||
}
|
||||
variable service-category {}
|
||||
variable gpg-key {}
|
||||
Reference in New Issue
Block a user