# AWS Security Monitoring with Terraform: CloudTrail, GuardDuty, Config and Security Hub

> Enable AWS security services with Terraform or OpenTofu: an organization CloudTrail, GuardDuty, AWS Config and Security Hub, with encrypted log storage.

- Source: https://www.itwonderlab.com/terraform-aws-security-monitoring/
- Published: 2026-05-14
- Updated: 2026-05-14
- Author: Javier Ruiz Jiménez (https://www.javierruizjimenez.com/)
- Site: IT Wonder Lab (https://www.itwonderlab.com/)

---

## The security baseline of an AWS account

Four services form the minimum monitoring of an AWS account. They detect, record and evaluate what happens:

| Service | What it does |
|---|---|
| [CloudTrail](https://www.itwonderlab.com/aws-cloudtrail/) | Records every API call: who did what, when and from where |
| [GuardDuty](https://www.itwonderlab.com/aws-guardduty/) | Detects threats using logs and machine learning (compromised credentials, crypto-mining) |
| [AWS Config](https://www.itwonderlab.com/aws-config/) | Records resource configuration and evaluates rules (is every bucket encrypted?) |
| [Security Hub](https://www.itwonderlab.com/aws-security-hub/) | Collects the findings of the other services and checks standards such as CIS and AWS Foundational Security Best Practices |

In an organization, enable them in every account and region through delegated administrators ([multi-account setup](https://www.itwonderlab.com/terraform-aws-organizations-multi-account/)). The examples here are for a single account.

### Encrypted bucket for the trail

```hcl title="cloudtrail.tf"
data "aws_caller_identity" "current" {}
data "aws_partition" "current" {}

resource "aws_s3_bucket" "trail" {
  bucket = "ditwl-cloudtrail-${data.aws_caller_identity.current.account_id}"
}

resource "aws_s3_bucket_public_access_block" "trail" {
  bucket                  = aws_s3_bucket.trail.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_versioning" "trail" {
  bucket = aws_s3_bucket.trail.id

  versioning_configuration {
    status = "Enabled"
  }
}

data "aws_iam_policy_document" "trail_bucket" {
  statement {
    sid       = "AWSCloudTrailAclCheck"
    actions   = ["s3:GetBucketAcl"]
    resources = [aws_s3_bucket.trail.arn]

    principals {
      type        = "Service"
      identifiers = ["cloudtrail.amazonaws.com"]
    }
  }

  statement {
    sid       = "AWSCloudTrailWrite"
    actions   = ["s3:PutObject"]
    resources = ["${aws_s3_bucket.trail.arn}/AWSLogs/${data.aws_caller_identity.current.account_id}/*"]

    principals {
      type        = "Service"
      identifiers = ["cloudtrail.amazonaws.com"]
    }

    condition {
      test     = "StringEquals"
      variable = "s3:x-amz-acl"
      values   = ["bucket-owner-full-control"]
    }
  }
}

resource "aws_s3_bucket_policy" "trail" {
  bucket = aws_s3_bucket.trail.id
  policy = data.aws_iam_policy_document.trail_bucket.json
}
```

See [S3 with Terraform](https://www.itwonderlab.com/aws-terraform-tutorial-aws-s3/) for encryption and lifecycle rules for this bucket.

### CloudTrail

```hcl title="cloudtrail.tf"
resource "aws_cloudtrail" "main" {
  name                          = "ditwl-main"
  s3_bucket_name                = aws_s3_bucket.trail.id
  is_multi_region_trail         = true
  include_global_service_events = true
  enable_log_file_validation    = true
  kms_key_id                    = aws_kms_key.trail.arn

  depends_on = [aws_s3_bucket_policy.trail]
}
```

- `is_multi_region_trail` records all regions with one trail.
- `enable_log_file_validation` lets you prove that the logs were not modified.
- The first copy of management events is free, and additional trails and data events have a cost.
- The KMS key needs a policy that allows CloudTrail to use it ([KMS and secrets](https://www.itwonderlab.com/terraform-aws-kms-secrets-manager/)).

### GuardDuty

```hcl title="guardduty.tf"
resource "aws_guardduty_detector" "main" {
  enable = true
}

resource "aws_guardduty_detector_feature" "s3" {
  detector_id = aws_guardduty_detector.main.id
  name        = "S3_DATA_EVENTS"
  status      = "ENABLED"
}
```

Optional protection features (S3, EKS, malware, runtime) have separate charges, so enable the ones you use. GuardDuty is per region: repeat it in each region that you use or use a delegated administrator.

### AWS Config

```hcl title="config.tf"
resource "aws_iam_service_linked_role" "config" {
  aws_service_name = "config.amazonaws.com"
}

resource "aws_config_configuration_recorder" "main" {
  name     = "default"
  role_arn = aws_iam_service_linked_role.config.arn

  recording_group {
    all_supported                 = true
    include_global_resource_types = true
  }
}

resource "aws_config_delivery_channel" "main" {
  name           = "default"
  s3_bucket_name = aws_s3_bucket.config.id

  depends_on = [aws_config_configuration_recorder.main]
}

resource "aws_config_configuration_recorder_status" "main" {
  name       = aws_config_configuration_recorder.main.name
  is_enabled = true

  depends_on = [aws_config_delivery_channel.main]
}

resource "aws_config_config_rule" "s3_encrypted" {
  name = "s3-bucket-server-side-encryption-enabled"

  source {
    owner             = "AWS"
    source_identifier = "S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED"
  }

  depends_on = [aws_config_configuration_recorder.main]
}
```

The `aws_s3_bucket.config` bucket needs a bucket policy that allows Config, similar to the one for CloudTrail. Config charges per configuration item recorded, so recording everything in a busy account is not free.

### Security Hub

```hcl title="securityhub.tf"
resource "aws_securityhub_account" "main" {}

data "aws_region" "current" {}

resource "aws_securityhub_standards_subscription" "fsbp" {
  standards_arn = "arn:aws:securityhub:${data.aws_region.current.name}::standards/aws-foundational-security-best-practices/v/1.0.0"

  depends_on = [aws_securityhub_account.main]
}
```

Security Hub depends on Config being enabled to evaluate many controls. Add the CIS standard with its own ARN if you need compliance reports.

### Act on the findings

Send the findings to a team: an EventBridge rule on `GuardDuty Finding` or `Security Hub Findings - Imported` events with severity high that publishes to an [SNS topic](https://www.itwonderlab.com/terraform-aws-sqs-sns/), which notifies by email or chat. Add [CloudWatch alarms](https://www.itwonderlab.com/terraform-aws-cloudwatch-alarms/) for root account usage and unauthorized API calls.

### Costs

All four are paid services with free trials (GuardDuty and Security Hub have 30 days). Costs grow with the number of events and resources. Estimate before enabling them organization-wide, and scan your own Terraform with [security scanners](https://www.itwonderlab.com/terraform-security-scanning-tflint-checkov-trivy/).
