├── versions.tf ├── .gitignore ├── examples └── simple │ ├── outputs.tf │ ├── variables.tf │ ├── main.tf │ └── README.md ├── .markdownlintrc ├── .github └── workflows │ └── validate.yml ├── outputs.tf ├── .terraform-docs.yml ├── .pre-commit-config.yaml ├── renovate.json ├── LICENSE.txt ├── variables.tf ├── README.md └── main.tf /versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0" 3 | 4 | required_providers { 5 | aws = ">= 3.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .terraform 3 | .terraform.lock.hcl 4 | terraform.tfstate 5 | terraform.tfstate.backup 6 | terraform.tfstate.*.backup 7 | .envrc 8 | -------------------------------------------------------------------------------- /examples/simple/outputs.tf: -------------------------------------------------------------------------------- 1 | output "cloudtrail_arn" { 2 | description = "CloudTrail ARN" 3 | value = module.aws_cloudtrail.cloudtrail_arn 4 | } 5 | -------------------------------------------------------------------------------- /.markdownlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "first-header-h1": false, 4 | "first-line-h1": false, 5 | "line_length": false, 6 | "no-multiple-blanks": false, 7 | "no-inline-html": false, 8 | "no-alt-text": false 9 | } 10 | -------------------------------------------------------------------------------- /examples/simple/variables.tf: -------------------------------------------------------------------------------- 1 | variable "logs_bucket" { 2 | type = string 3 | } 4 | 5 | variable "s3_key_prefix" { 6 | type = string 7 | } 8 | 9 | variable "trail_name" { 10 | type = string 11 | } 12 | 13 | variable "cloudwatch_log_group_name" { 14 | type = string 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/validate.yml: -------------------------------------------------------------------------------- 1 | name: validate-tf 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | validate-tf: 13 | uses: trussworks/shared-actions/.github/workflows/validate-tf.yml@637d269a81479673b19d9e7b02b98d75b1805b46 # v0.1.1 14 | -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "cloudtrail_arn" { 2 | description = "CloudTrail ARN" 3 | value = aws_cloudtrail.main.arn 4 | } 5 | 6 | output "cloudtrail_home_region" { 7 | description = "CloudTrail Home Region" 8 | value = aws_cloudtrail.main.home_region 9 | } 10 | 11 | output "cloudtrail_id" { 12 | description = "CloudTrail ID" 13 | value = aws_cloudtrail.main.id 14 | } 15 | 16 | output "kms_key_arn" { 17 | description = "KMS Key ARN" 18 | value = aws_kms_key.cloudtrail.arn 19 | } 20 | -------------------------------------------------------------------------------- /examples/simple/main.tf: -------------------------------------------------------------------------------- 1 | module "aws_cloudtrail" { 2 | source = "../../" 3 | 4 | trail_name = var.trail_name 5 | 6 | cloudwatch_log_group_name = var.cloudwatch_log_group_name 7 | 8 | s3_bucket_name = module.logs.aws_logs_bucket 9 | s3_key_prefix = var.s3_key_prefix 10 | } 11 | 12 | module "logs" { 13 | source = "trussworks/logs/aws" 14 | version = "~> 12" 15 | 16 | s3_bucket_name = var.logs_bucket 17 | 18 | cloudtrail_logs_prefix = var.s3_key_prefix 19 | allow_cloudtrail = true 20 | 21 | force_destroy = true 22 | } 23 | -------------------------------------------------------------------------------- /.terraform-docs.yml: -------------------------------------------------------------------------------- 1 | version: ">= 0.19.0, < 1.0.0" 2 | 3 | settings: 4 | html: false 5 | anchor: false 6 | escape: false 7 | lockfile: false 8 | hide-empty: true 9 | formatter: "markdown table" 10 | 11 | sort: 12 | enabled: true 13 | by: required 14 | 15 | sections: 16 | show: 17 | - requirements 18 | - providers 19 | - modules 20 | - data-sources 21 | - resources 22 | - inputs 23 | - outputs 24 | 25 | recursive: 26 | enabled: false 27 | include-main: false 28 | 29 | output: 30 | file: README.md 31 | mode: inject 32 | template: |- 33 | 34 | {{ .Content }} 35 | 36 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v5.0.0 4 | hooks: 5 | - id: check-json 6 | - id: check-merge-conflict 7 | - id: check-yaml 8 | - id: detect-private-key 9 | - id: pretty-format-json 10 | args: 11 | - --autofix 12 | - id: trailing-whitespace 13 | - id: check-symlinks 14 | - id: end-of-file-fixer 15 | - id: mixed-line-ending 16 | 17 | - repo: https://github.com/igorshubovych/markdownlint-cli 18 | rev: v0.43.0 19 | hooks: 20 | - id: markdownlint 21 | 22 | - repo: https://github.com/terraform-docs/terraform-docs 23 | rev: "v0.19.0" 24 | hooks: 25 | - id: terraform-docs-system 26 | 27 | - repo: https://github.com/antonbabenko/pre-commit-terraform 28 | rev: v1.96.3 29 | hooks: 30 | - id: terraform_fmt 31 | -------------------------------------------------------------------------------- /examples/simple/README.md: -------------------------------------------------------------------------------- 1 | # simple 2 | 3 | 4 | 5 | ## Modules 6 | 7 | | Name | Source | Version | 8 | | -------------- | ------------------- | ------- | 9 | | aws_cloudtrail | ../../ | n/a | 10 | | logs | trussworks/logs/aws | ~> 12 | 11 | 12 | ## Inputs 13 | 14 | | Name | Description | Type | Default | Required | 15 | | ------------------------- | ----------- | -------- | ------- | :------: | 16 | | cloudwatch_log_group_name | n/a | `string` | n/a | yes | 17 | | logs_bucket | n/a | `string` | n/a | yes | 18 | | s3_key_prefix | n/a | `string` | n/a | yes | 19 | | trail_name | n/a | `string` | n/a | yes | 20 | 21 | ## Outputs 22 | 23 | | Name | Description | 24 | | -------------- | -------------- | 25 | | cloudtrail_arn | CloudTrail ARN | 26 | 27 | 28 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:recommended", 4 | "helpers:pinGitHubActionDigests" 5 | ], 6 | "labels": [ 7 | "dependencies" 8 | ], 9 | "packageRules": [ 10 | { 11 | "automerge": true, 12 | "description": "Automerge all updates except major versions", 13 | "matchUpdateTypes": [ 14 | "patch", 15 | "pin", 16 | "digest", 17 | "minor" 18 | ] 19 | }, 20 | { 21 | "description": "Tag the waddlers Github Team for major updates", 22 | "matchUpdateTypes": [ 23 | "major" 24 | ], 25 | "reviewers": [ 26 | "team:waddlers" 27 | ] 28 | }, 29 | { 30 | "automerge": true, 31 | "description": "Group minor and patch updates into a single PR", 32 | "groupName": "dependencies", 33 | "matchManagers": [ 34 | "terraform", 35 | "pre-commit", 36 | "github-actions" 37 | ], 38 | "matchUpdateTypes": [ 39 | "minor", 40 | "patch" 41 | ] 42 | } 43 | ], 44 | "schedule": [ 45 | "every weekend" 46 | ], 47 | "separateMinorPatch": true, 48 | "timezone": "America/Los_Angeles" 49 | } 50 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, TrussWorks, Inc. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | variable "advanced_event_selectors" { 2 | description = "A list of advanced event selectors for the trail." 3 | default = [] 4 | type = list(object({ 5 | name = string 6 | field_selectors = list(object({ 7 | field = string 8 | equals = optional(list(string)) 9 | starts_with = optional(list(string)) 10 | ends_with = optional(list(string)) 11 | not_equals = optional(list(string)) 12 | not_starts_with = optional(list(string)) 13 | not_ends_with = optional(list(string)) 14 | })) 15 | })) 16 | } 17 | 18 | variable "api_call_rate_insight" { 19 | description = "A measurement of write-only management API calls that occur per minute against a baseline API call volume." 20 | default = false 21 | type = bool 22 | } 23 | 24 | variable "api_error_rate_insight" { 25 | description = "A measurement of management API calls that result in error codes. The error is shown if the API call is unsuccessful." 26 | default = false 27 | type = bool 28 | } 29 | 30 | variable "cloudwatch_log_group_name" { 31 | description = "The name of the CloudWatch Log Group that receives CloudTrail events." 32 | default = "cloudtrail-events" 33 | type = string 34 | } 35 | 36 | variable "enabled" { 37 | description = "Enables logging for the trail. Defaults to true. Setting this to false will pause logging." 38 | default = true 39 | type = bool 40 | } 41 | 42 | variable "iam_policy_name" { 43 | description = "Name for the CloudTrail IAM policy" 44 | default = "cloudtrail-cloudwatch-logs-policy" 45 | type = string 46 | } 47 | 48 | variable "iam_role_name" { 49 | description = "Name for the CloudTrail IAM role" 50 | default = "cloudtrail-cloudwatch-logs-role" 51 | type = string 52 | } 53 | 54 | variable "key_deletion_window_in_days" { 55 | description = "Duration in days after which the key is deleted after destruction of the resource, must be 7-30 days. Default 30 days." 56 | default = 30 57 | type = string 58 | } 59 | 60 | variable "log_retention_days" { 61 | description = "Number of days to keep AWS logs around in specific log group." 62 | default = 90 63 | type = string 64 | } 65 | 66 | variable "org_trail" { 67 | description = "Whether or not this is an organization trail. Only valid in master account." 68 | default = "false" 69 | type = string 70 | } 71 | 72 | variable "s3_bucket_account_id" { 73 | description = "(optional) The AWS account ID which owns the S3 bucket. Only include if the S3 bucket is in a different account than the CloudTrail." 74 | default = null 75 | type = string 76 | } 77 | 78 | variable "s3_bucket_name" { 79 | description = "The name of the AWS S3 bucket." 80 | type = string 81 | } 82 | 83 | variable "s3_key_prefix" { 84 | description = "S3 key prefix for CloudTrail logs" 85 | default = "cloudtrail" 86 | type = string 87 | } 88 | 89 | variable "sns_topic_arn" { 90 | description = "ARN of the SNS topic for notification of log file delivery." 91 | default = "" 92 | type = string 93 | } 94 | 95 | variable "tags" { 96 | description = "A mapping of tags to CloudTrail resources." 97 | default = {} 98 | type = map(string) 99 | } 100 | 101 | variable "trail_name" { 102 | description = "Name for the Cloudtrail" 103 | default = "cloudtrail" 104 | type = string 105 | } 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraform AWS CloudTrail 2 | 3 | This module creates AWS CloudTrail and configures it so that logs go to cloudwatch. 4 | 5 | ## Usage 6 | 7 | ```hcl 8 | module "aws_cloudtrail" { 9 | source = "trussworks/cloudtrail/aws" 10 | s3_bucket_name = "my-company-cloudtrail-logs" 11 | log_retention_days = 90 12 | } 13 | ``` 14 | 15 | ## Upgrade Instructions for v2 -> v3 16 | 17 | Starting in v3, encryption is not optional and will be on for both logs 18 | delivered to S3 and Cloudwatch Logs. The KMS key resource created this 19 | module will be used to encrypt both S3 and Cloudwatch-based logs. 20 | 21 | Because of this change, remove the `encrypt_cloudtrail` parameter from 22 | previous invocations of the module prior to upgrading the version. 23 | 24 | 25 | ## Requirements 26 | 27 | | Name | Version | 28 | |------|---------| 29 | | terraform | >= 1.0 | 30 | | aws | >= 3.0 | 31 | 32 | ## Providers 33 | 34 | | Name | Version | 35 | |------|---------| 36 | | aws | >= 3.0 | 37 | 38 | ## Resources 39 | 40 | | Name | Type | 41 | |------|------| 42 | | [aws_cloudtrail.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudtrail) | resource | 43 | | [aws_cloudwatch_log_group.cloudtrail](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | 44 | | [aws_iam_policy.cloudtrail_cloudwatch_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | 45 | | [aws_iam_policy_attachment.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy_attachment) | resource | 46 | | [aws_iam_role.cloudtrail_cloudwatch_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | 47 | | [aws_kms_alias.cloudtrail](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource | 48 | | [aws_kms_key.cloudtrail](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | 49 | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | 50 | | [aws_iam_policy_document.cloudtrail_assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 51 | | [aws_iam_policy_document.cloudtrail_cloudwatch_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 52 | | [aws_iam_policy_document.cloudtrail_kms_policy_doc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 53 | | [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | 54 | | [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | 55 | 56 | ## Inputs 57 | 58 | | Name | Description | Type | Default | Required | 59 | |------|-------------|------|---------|:--------:| 60 | | s3_bucket_name | The name of the AWS S3 bucket. | `string` | n/a | yes | 61 | | advanced_event_selectors | A list of advanced event selectors for the trail. | ```list(object({ name = string field_selectors = list(object({ field = string equals = optional(list(string)) starts_with = optional(list(string)) ends_with = optional(list(string)) not_equals = optional(list(string)) not_starts_with = optional(list(string)) not_ends_with = optional(list(string)) })) }))``` | `[]` | no | 62 | | api_call_rate_insight | A measurement of write-only management API calls that occur per minute against a baseline API call volume. | `bool` | `false` | no | 63 | | api_error_rate_insight | A measurement of management API calls that result in error codes. The error is shown if the API call is unsuccessful. | `bool` | `false` | no | 64 | | cloudwatch_log_group_name | The name of the CloudWatch Log Group that receives CloudTrail events. | `string` | `"cloudtrail-events"` | no | 65 | | enabled | Enables logging for the trail. Defaults to true. Setting this to false will pause logging. | `bool` | `true` | no | 66 | | iam_policy_name | Name for the CloudTrail IAM policy | `string` | `"cloudtrail-cloudwatch-logs-policy"` | no | 67 | | iam_role_name | Name for the CloudTrail IAM role | `string` | `"cloudtrail-cloudwatch-logs-role"` | no | 68 | | key_deletion_window_in_days | Duration in days after which the key is deleted after destruction of the resource, must be 7-30 days. Default 30 days. | `string` | `30` | no | 69 | | log_retention_days | Number of days to keep AWS logs around in specific log group. | `string` | `90` | no | 70 | | org_trail | Whether or not this is an organization trail. Only valid in master account. | `string` | `"false"` | no | 71 | | s3_bucket_account_id | (optional) The AWS account ID which owns the S3 bucket. Only include if the S3 bucket is in a different account than the CloudTrail. | `string` | `null` | no | 72 | | s3_key_prefix | S3 key prefix for CloudTrail logs | `string` | `"cloudtrail"` | no | 73 | | sns_topic_arn | ARN of the SNS topic for notification of log file delivery. | `string` | `""` | no | 74 | | tags | A mapping of tags to CloudTrail resources. | `map(string)` | `{}` | no | 75 | | trail_name | Name for the Cloudtrail | `string` | `"cloudtrail"` | no | 76 | 77 | ## Outputs 78 | 79 | | Name | Description | 80 | |------|-------------| 81 | | cloudtrail_arn | CloudTrail ARN | 82 | | cloudtrail_home_region | CloudTrail Home Region | 83 | | cloudtrail_id | CloudTrail ID | 84 | | kms_key_arn | KMS Key ARN | 85 | 86 | 87 | ## Developer Setup 88 | 89 | Install dependencies (macOS) 90 | 91 | ```shell 92 | brew install pre-commit go terraform terraform-docs 93 | ``` 94 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | # The AWS region currently being used. 2 | data "aws_region" "current" {} 3 | 4 | # The AWS account id 5 | data "aws_caller_identity" "current" {} 6 | 7 | # The AWS partition (commercial or govcloud) 8 | data "aws_partition" "current" {} 9 | 10 | locals { 11 | s3_bucket_account_id = var.s3_bucket_account_id != null ? var.s3_bucket_account_id : data.aws_caller_identity.current.account_id 12 | } 13 | 14 | # 15 | # CloudTrail - CloudWatch 16 | # 17 | # This section is used for allowing CloudTrail to send logs to CloudWatch. 18 | # 19 | 20 | # This policy allows the CloudTrail service for any account to assume this role. 21 | data "aws_iam_policy_document" "cloudtrail_assume_role" { 22 | statement { 23 | effect = "Allow" 24 | actions = ["sts:AssumeRole"] 25 | 26 | principals { 27 | type = "Service" 28 | identifiers = ["cloudtrail.amazonaws.com"] 29 | } 30 | } 31 | } 32 | 33 | # This role is used by CloudTrail to send logs to CloudWatch. 34 | resource "aws_iam_role" "cloudtrail_cloudwatch_role" { 35 | name = var.iam_role_name 36 | assume_role_policy = data.aws_iam_policy_document.cloudtrail_assume_role.json 37 | } 38 | 39 | # This CloudWatch Group is used for storing CloudTrail logs. 40 | resource "aws_cloudwatch_log_group" "cloudtrail" { 41 | name = var.cloudwatch_log_group_name 42 | retention_in_days = var.log_retention_days 43 | kms_key_id = aws_kms_key.cloudtrail.arn 44 | tags = var.tags 45 | } 46 | 47 | data "aws_iam_policy_document" "cloudtrail_cloudwatch_logs" { 48 | statement { 49 | sid = "WriteCloudWatchLogs" 50 | 51 | effect = "Allow" 52 | 53 | actions = [ 54 | "logs:CreateLogStream", 55 | "logs:PutLogEvents", 56 | ] 57 | 58 | resources = ["arn:${data.aws_partition.current.partition}:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:log-group:${var.cloudwatch_log_group_name}:*"] 59 | } 60 | } 61 | 62 | resource "aws_iam_policy" "cloudtrail_cloudwatch_logs" { 63 | name = var.iam_policy_name 64 | policy = data.aws_iam_policy_document.cloudtrail_cloudwatch_logs.json 65 | } 66 | 67 | resource "aws_iam_policy_attachment" "main" { 68 | name = "${var.iam_policy_name}-attachment" 69 | policy_arn = aws_iam_policy.cloudtrail_cloudwatch_logs.arn 70 | roles = [aws_iam_role.cloudtrail_cloudwatch_role.name] 71 | } 72 | 73 | # 74 | # KMS 75 | # 76 | 77 | # This policy is a translation of the default created by AWS when you 78 | # manually enable CloudTrail; you can see it here: 79 | # https://docs.aws.amazon.com/awscloudtrail/latest/userguide/default-cmk-policy.html 80 | data "aws_iam_policy_document" "cloudtrail_kms_policy_doc" { 81 | statement { 82 | sid = "Enable IAM User Permissions" 83 | effect = "Allow" 84 | actions = ["kms:*"] 85 | 86 | principals { 87 | type = "AWS" 88 | 89 | identifiers = ["arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:root"] 90 | } 91 | 92 | resources = ["*"] 93 | } 94 | 95 | statement { 96 | sid = "Allow CloudTrail to encrypt logs" 97 | effect = "Allow" 98 | actions = ["kms:GenerateDataKey*"] 99 | 100 | principals { 101 | type = "Service" 102 | identifiers = ["cloudtrail.amazonaws.com"] 103 | } 104 | 105 | resources = ["*"] 106 | 107 | condition { 108 | test = "StringLike" 109 | variable = "kms:EncryptionContext:aws:cloudtrail:arn" 110 | values = ["arn:${data.aws_partition.current.partition}:cloudtrail:*:${data.aws_caller_identity.current.account_id}:trail/*"] 111 | } 112 | } 113 | 114 | statement { 115 | sid = "Allow CloudTrail to describe key" 116 | effect = "Allow" 117 | actions = ["kms:DescribeKey"] 118 | 119 | principals { 120 | type = "Service" 121 | identifiers = ["cloudtrail.amazonaws.com"] 122 | } 123 | 124 | resources = ["*"] 125 | } 126 | 127 | statement { 128 | sid = "Allow principals in the account to decrypt log files" 129 | effect = "Allow" 130 | 131 | actions = [ 132 | "kms:Decrypt", 133 | "kms:ReEncryptFrom", 134 | ] 135 | 136 | principals { 137 | type = "AWS" 138 | identifiers = ["*"] 139 | } 140 | 141 | resources = ["*"] 142 | 143 | condition { 144 | test = "StringEquals" 145 | variable = "kms:CallerAccount" 146 | values = [data.aws_caller_identity.current.account_id] 147 | } 148 | 149 | condition { 150 | test = "StringLike" 151 | variable = "kms:EncryptionContext:aws:cloudtrail:arn" 152 | values = ["arn:${data.aws_partition.current.partition}:cloudtrail:*:${data.aws_caller_identity.current.account_id}:trail/*"] 153 | } 154 | } 155 | 156 | statement { 157 | sid = "Allow alias creation during setup" 158 | effect = "Allow" 159 | actions = ["kms:CreateAlias"] 160 | 161 | principals { 162 | type = "AWS" 163 | identifiers = ["*"] 164 | } 165 | 166 | condition { 167 | test = "StringEquals" 168 | variable = "kms:ViaService" 169 | values = ["ec2.${data.aws_region.current.name}.amazonaws.com"] 170 | } 171 | 172 | condition { 173 | test = "StringEquals" 174 | variable = "kms:CallerAccount" 175 | values = [data.aws_caller_identity.current.account_id] 176 | } 177 | 178 | resources = ["*"] 179 | } 180 | 181 | statement { 182 | sid = "Enable cross account log decryption" 183 | effect = "Allow" 184 | 185 | actions = [ 186 | "kms:Decrypt", 187 | "kms:ReEncryptFrom", 188 | ] 189 | 190 | principals { 191 | type = "AWS" 192 | identifiers = ["*"] 193 | } 194 | 195 | condition { 196 | test = "StringEquals" 197 | variable = "kms:CallerAccount" 198 | values = [local.s3_bucket_account_id] 199 | } 200 | 201 | condition { 202 | test = "StringLike" 203 | variable = "kms:EncryptionContext:aws:cloudtrail:arn" 204 | values = ["arn:${data.aws_partition.current.partition}:cloudtrail:*:${data.aws_caller_identity.current.account_id}:trail/*"] 205 | } 206 | 207 | resources = ["*"] 208 | } 209 | 210 | statement { 211 | sid = "Allow logs KMS access" 212 | effect = "Allow" 213 | 214 | principals { 215 | type = "Service" 216 | identifiers = ["logs.${data.aws_region.current.name}.amazonaws.com"] 217 | } 218 | 219 | actions = [ 220 | "kms:Encrypt*", 221 | "kms:Decrypt*", 222 | "kms:ReEncrypt*", 223 | "kms:GenerateDataKey*", 224 | "kms:Describe*", 225 | ] 226 | resources = ["*"] 227 | } 228 | 229 | statement { 230 | sid = "Allow Cloudtrail to decrypt and generate key for sns access" 231 | effect = "Allow" 232 | 233 | principals { 234 | type = "Service" 235 | identifiers = ["cloudtrail.amazonaws.com"] 236 | } 237 | 238 | actions = [ 239 | "kms:Decrypt*", 240 | "kms:GenerateDataKey*", 241 | ] 242 | resources = ["*"] 243 | } 244 | } 245 | 246 | resource "aws_kms_key" "cloudtrail" { 247 | description = "A KMS key used to encrypt CloudTrail log files stored in S3." 248 | deletion_window_in_days = var.key_deletion_window_in_days 249 | enable_key_rotation = "true" 250 | policy = data.aws_iam_policy_document.cloudtrail_kms_policy_doc.json 251 | tags = var.tags 252 | } 253 | 254 | resource "aws_kms_alias" "cloudtrail" { 255 | name = "alias/${var.trail_name}" 256 | target_key_id = aws_kms_key.cloudtrail.key_id 257 | } 258 | 259 | # 260 | # CloudTrail 261 | # 262 | 263 | resource "aws_cloudtrail" "main" { 264 | name = var.trail_name 265 | 266 | # Send logs to CloudWatch Logs 267 | cloud_watch_logs_group_arn = "${aws_cloudwatch_log_group.cloudtrail.arn}:*" 268 | cloud_watch_logs_role_arn = aws_iam_role.cloudtrail_cloudwatch_role.arn 269 | 270 | # Send logs to S3 271 | s3_key_prefix = var.s3_key_prefix 272 | s3_bucket_name = var.s3_bucket_name 273 | 274 | # Note that organization trails can *only* be created in organization 275 | # master accounts; this will fail if run in a non-master account. 276 | is_organization_trail = var.org_trail 277 | 278 | # use a single s3 bucket for all aws regions 279 | is_multi_region_trail = true 280 | 281 | # enable log file validation to detect tampering 282 | enable_log_file_validation = true 283 | 284 | kms_key_id = aws_kms_key.cloudtrail.arn 285 | 286 | # Enables logging for the trail. Defaults to true. Setting this to false will pause logging. 287 | enable_logging = var.enabled 288 | 289 | # Enables SNS log notification 290 | sns_topic_name = var.sns_topic_arn 291 | 292 | # Enable Insights 293 | dynamic "insight_selector" { 294 | for_each = compact([ 295 | var.api_call_rate_insight ? "ApiCallRateInsight" : null, 296 | var.api_error_rate_insight ? "ApiErrorRateInsight" : null, 297 | ]) 298 | content { 299 | insight_type = insight_selector.value 300 | } 301 | } 302 | 303 | dynamic "advanced_event_selector" { 304 | for_each = var.advanced_event_selectors 305 | content { 306 | name = advanced_event_selector.value.name 307 | 308 | dynamic "field_selector" { 309 | for_each = advanced_event_selector.value.field_selectors 310 | content { 311 | field = field_selector.value.field 312 | equals = field_selector.value.equals 313 | starts_with = field_selector.value.starts_with 314 | ends_with = field_selector.value.ends_with 315 | not_equals = field_selector.value.not_equals 316 | not_starts_with = field_selector.value.not_starts_with 317 | not_ends_with = field_selector.value.not_ends_with 318 | } 319 | } 320 | } 321 | } 322 | 323 | tags = var.tags 324 | 325 | depends_on = [ 326 | aws_kms_key.cloudtrail, 327 | aws_kms_alias.cloudtrail, 328 | ] 329 | } 330 | --------------------------------------------------------------------------------