aws

v0.1.16 collector

Collects AWS organization security posture metrics

Install

epack install collector aws

Adds to epack.yaml, resolves dependencies, downloads binary.

Usage

Run all configured collectors and build a pack:

epack collect

Runs all collectors in epack.yaml and outputs an evidence pack.

Configuration

Or add manually to epack.yaml:

collectors:
  aws:
    source: https://github.com/locktivity/epack-collector-aws

Then run epack install to lock and sync.

AWS Collector Overview

The AWS collector gathers security posture metrics from AWS accounts, providing visibility into IAM, S3, RDS, network, and account-level security configurations.

What It Collects

IAM Security

Metric Description
iam_users_present Whether at least one IAM user (excluding root) exists in the account
mfa_enabled Percentage of IAM users with MFA enabled
hardware_mfa_enabled Percentage of IAM users with hardware MFA (physical OTP devices or FIDO/U2F security keys)
access_keys_rotated Percentage of access keys rotated within 90 days
root_mfa_enabled Whether the root account has MFA enabled
root_access_keys_exist Whether root has access keys (should be false)

S3 Security

Metric Description
public_access_blocked Percentage of buckets with public access blocked
default_encryption_enabled Percentage of buckets with default encryption
versioning_enabled Percentage of buckets with versioning enabled
logging_enabled Percentage of buckets with access logging
account_public_access_block_enabled Whether account-level public access block is enabled

RDS Security

Metric Description
encrypted_at_rest Percentage of instances/clusters with encryption
publicly_accessible Percentage publicly accessible (should be 0%)
deletion_protection Percentage with deletion protection enabled
backup_retention_adequate Percentage with backup retention >= 7 days
multi_az_enabled Percentage with Multi-AZ deployment

Network Security

Metric Description
open_to_world_ssh Percentage of security groups allowing SSH from 0.0.0.0/0
open_to_world_rdp Percentage allowing RDP from 0.0.0.0/0
flow_logs_enabled Percentage of VPCs with flow logs

Account Security Services

Service Metrics
CloudTrail Enabled, multi-region
AWS Config Enabled, recorder running
GuardDuty Enabled, unremediated high/critical findings >48h
Security Hub Enabled, CIS AWS Foundations Benchmark level 1/2/unknown-level compliance
Inspector Enabled, unpatched server %

For CIS level splits, the collector uses Security Hub finding related_requirements level tags and aggregates to one status per control (FAILED > WARNING > PASSED > NOT_AVAILABLE).
Findings without explicit level tags are reported in an explicit unknown-level bucket.

Interpretation guide:
- security_hub.enabled=true means Security Hub is available in the account/region.
- level_1 and level_2 values are only populated when findings include explicit level tags.
- unknown_level captures CIS controls where Security Hub did not provide a level tag.

Testing

  • Unit tests validate collector math/aggregation and AWS helper logic:
    • go test ./...
  • E2E tests hit live AWS APIs and are opt-in:
    • AWS_E2E_RUN=true go test -tags=e2e -v ./internal/collector/...

Multi-Region Collection

The collector automatically handles AWS's regional vs global services:

  • Global services (IAM, S3 bucket listing, CloudTrail): Collected once
  • Regional services (RDS, EC2, GuardDuty): Collected from all configured regions

Metrics from regional services are aggregated across all regions.

Multi-Account Collection

Configure multiple accounts to collect from all AWS accounts in your organization:

collectors:
  - name: aws
    config:
      accounts:
        - role_arn: "arn:aws:iam::111111111111:role/EpackCollectorRole"
        - role_arn: "arn:aws:iam::222222222222:role/EpackCollectorRole"

Each account's posture is collected independently and included in the output.

AWS Collector Configuration

Authentication

The AWS collector supports multiple authentication methods. The auth_mode option controls how the collector assumes IAM roles:

  • oidc - Uses GitHub Actions OIDC to assume roles via AssumeRoleWithWebIdentity (recommended for GitHub Actions)
  • assume_role - Uses standard AssumeRole with optional external_id (default for backward compatibility)

1. GitHub Actions OIDC (Recommended)

When running in GitHub Actions, OIDC provides secure, credential-free authentication. The collector obtains a JWT token from GitHub and exchanges it for temporary AWS credentials.

Trust Policy (OIDC):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
        },
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:*"
        }
      }
    }
  ]
}

Configuration:

collectors:
  aws:
    source: "locktivity/epack-collector-aws@^0.1.0"
    config:
      auth_mode: "oidc"
      role_arn: "arn:aws:iam::123456789012:role/EpackCollectorRole"
    secrets:
      - ACTIONS_ID_TOKEN_REQUEST_URL
      - ACTIONS_ID_TOKEN_REQUEST_TOKEN

GitHub Actions Workflow:

permissions:
  id-token: write  # Required for OIDC
  contents: read

2. IAM Role with AssumeRole

For environments without OIDC support, use standard AssumeRole with bootstrap credentials.

Trust Policy (AssumeRole):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::COLLECTOR_ACCOUNT:root"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "your-external-id"
        }
      }
    }
  ]
}

Configuration:

collectors:
  aws:
    source: "locktivity/epack-collector-aws@^0.1.0"
    config:
      auth_mode: "assume_role"
      role_arn: "arn:aws:iam::123456789012:role/EpackCollectorRole"
      external_id: "your-external-id"

3. Default Credential Chain

If no role_arn is specified, the collector uses the AWS SDK's default credential chain:

  1. Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
  2. Shared credentials file (~/.aws/credentials)
  3. EC2 instance profile / ECS task role
collectors:
  aws:
    source: "locktivity/epack-collector-aws@^0.1.0"
    config:
      regions:
        - us-east-1

Required IAM Permissions

Create a managed policy with these read-only permissions:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "IAMReadAccess",
      "Effect": "Allow",
      "Action": [
        "iam:GetCredentialReport",
        "iam:GenerateCredentialReport",
        "iam:ListAccountAliases"
      ],
      "Resource": "*"
    },
    {
      "Sid": "S3ReadAccess",
      "Effect": "Allow",
      "Action": [
        "s3:ListAllMyBuckets",
        "s3:GetBucketPublicAccessBlock",
        "s3:GetBucketEncryption",
        "s3:GetBucketVersioning",
        "s3:GetBucketLogging",
        "s3:GetBucketPolicy",
        "s3:GetBucketLocation",
        "s3:GetAccountPublicAccessBlock"
      ],
      "Resource": "*"
    },
    {
      "Sid": "RDSReadAccess",
      "Effect": "Allow",
      "Action": [
        "rds:DescribeDBInstances",
        "rds:DescribeDBClusters"
      ],
      "Resource": "*"
    },
    {
      "Sid": "EC2ReadAccess",
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeVpcs",
        "ec2:DescribeSecurityGroups",
        "ec2:DescribeFlowLogs",
        "ec2:DescribeRegions"
      ],
      "Resource": "*"
    },
    {
      "Sid": "CloudTrailReadAccess",
      "Effect": "Allow",
      "Action": [
        "cloudtrail:DescribeTrails",
        "cloudtrail:GetTrailStatus"
      ],
      "Resource": "*"
    },
    {
      "Sid": "ConfigReadAccess",
      "Effect": "Allow",
      "Action": [
        "config:DescribeConfigurationRecorders",
        "config:DescribeConfigurationRecorderStatus"
      ],
      "Resource": "*"
    },
    {
      "Sid": "GuardDutyReadAccess",
      "Effect": "Allow",
      "Action": [
        "guardduty:ListDetectors",
        "guardduty:GetDetector",
        "guardduty:ListFindings"
      ],
      "Resource": "*"
    },
    {
      "Sid": "SecurityHubReadAccess",
      "Effect": "Allow",
      "Action": [
        "securityhub:DescribeHub",
        "securityhub:GetEnabledStandards",
        "securityhub:ListEnabledProductsForImport",
        "securityhub:GetFindings"
      ],
      "Resource": "*"
    },
    {
      "Sid": "STSReadAccess",
      "Effect": "Allow",
      "Action": [
        "sts:GetCallerIdentity"
      ],
      "Resource": "*"
    }
  ]
}

Multi-Account Setup

For organizations with multiple AWS accounts:

With OIDC (Recommended)

Each target account needs a role trusting the GitHub OIDC provider:

collectors:
  aws:
    source: "locktivity/epack-collector-aws@^0.1.0"
    config:
      auth_mode: "oidc"
      accounts:
        - role_arn: "arn:aws:iam::111111111111:role/EpackCollectorRole"
        - role_arn: "arn:aws:iam::222222222222:role/EpackCollectorRole"
        - role_arn: "arn:aws:iam::333333333333:role/EpackCollectorRole"
      regions:
        - us-east-1
        - us-west-2
        - eu-west-1
    secrets:
      - ACTIONS_ID_TOKEN_REQUEST_URL
      - ACTIONS_ID_TOKEN_REQUEST_TOKEN

With AssumeRole

Each target account needs a role trusting the bootstrap account:

collectors:
  aws:
    source: "locktivity/epack-collector-aws@^0.1.0"
    config:
      auth_mode: "assume_role"
      accounts:
        - role_arn: "arn:aws:iam::111111111111:role/EpackCollectorRole"
          external_id: "prod-123"
        - role_arn: "arn:aws:iam::222222222222:role/EpackCollectorRole"
          external_id: "staging-456"
        - role_arn: "arn:aws:iam::333333333333:role/EpackCollectorRole"
          external_id: "dev-789"
      regions:
        - us-east-1
        - us-west-2
        - eu-west-1

Note: external_id is ignored in OIDC mode. The OIDC token claims provide equivalent security through repository/branch constraints in the trust policy.

Region Configuration

By default, the collector discovers all enabled regions in the account. To limit to specific regions:

collectors:
  aws:
    source: "locktivity/epack-collector-aws@^0.1.0"
    config:
      regions:
        - us-east-1
        - us-west-2

Troubleshooting

"Access Denied" Errors

  1. Verify the IAM role/user has all required permissions
  2. Check that the trust policy allows the collector's identity
  3. Ensure the external ID matches (if configured)

"Credential Report Not Ready"

The collector automatically retries generating the credential report. If it times out after 10 attempts, check IAM permissions for iam:GenerateCredentialReport.

"Service Not Available in Region"

Some services (like GuardDuty) may not be available in all regions. The collector handles this gracefully and continues with available services.

Rate Limiting

The AWS SDK automatically handles rate limiting with exponential backoff. For large accounts with many resources, collection may take several minutes.

AWS Collector Examples

Basic Usage

Collect from the current AWS account using default credentials:

collectors:
  aws:
    source: "locktivity/epack-collector-aws@^0.1.0"
    config: {}

Cross-Account Collection

With OIDC (Recommended for GitHub Actions)

collectors:
  aws:
    source: "locktivity/epack-collector-aws@^0.1.0"
    config:
      auth_mode: "oidc"
      role_arn: "arn:aws:iam::123456789012:role/EpackCollectorRole"
    secrets:
      - ACTIONS_ID_TOKEN_REQUEST_URL
      - ACTIONS_ID_TOKEN_REQUEST_TOKEN

With AssumeRole

collectors:
  aws:
    source: "locktivity/epack-collector-aws@^0.1.0"
    config:
      auth_mode: "assume_role"
      role_arn: "arn:aws:iam::123456789012:role/EpackCollectorRole"
      external_id: "epack-collection-abc123"

Multi-Account Collection

With OIDC

collectors:
  aws:
    source: "locktivity/epack-collector-aws@^0.1.0"
    config:
      auth_mode: "oidc"
      accounts:
        - role_arn: "arn:aws:iam::111111111111:role/EpackCollectorRole"
        - role_arn: "arn:aws:iam::222222222222:role/EpackCollectorRole"
        - role_arn: "arn:aws:iam::333333333333:role/EpackCollectorRole"
    secrets:
      - ACTIONS_ID_TOKEN_REQUEST_URL
      - ACTIONS_ID_TOKEN_REQUEST_TOKEN

With AssumeRole

collectors:
  aws:
    source: "locktivity/epack-collector-aws@^0.1.0"
    config:
      auth_mode: "assume_role"
      accounts:
        - role_arn: "arn:aws:iam::111111111111:role/EpackCollectorRole"
          external_id: "prod"
        - role_arn: "arn:aws:iam::222222222222:role/EpackCollectorRole"
          external_id: "staging"
        - role_arn: "arn:aws:iam::333333333333:role/EpackCollectorRole"
          external_id: "dev"

Specific Regions Only

Limit collection to specific regions:

collectors:
  aws:
    source: "locktivity/epack-collector-aws@^0.1.0"
    config:
      role_arn: "arn:aws:iam::123456789012:role/EpackCollectorRole"
      regions:
        - us-east-1
        - us-west-2
        - eu-west-1

Sample Output

Metrics use percentages (0-100), booleans, and counts where appropriate.

{
  "schema_version": "1.0.0",
  "collected_at": "2024-01-15T10:30:00Z",
  "accounts": [
    {
      "account_id": "123456789012",
      "account_alias": "production",
      "regions": ["us-east-1", "us-west-2"],
      "iam": {
        "iam_users_present": true,
        "mfa_enabled": 95,
        "hardware_mfa_enabled": 0,
        "access_keys_rotated": 80,
        "root_mfa_enabled": true,
        "root_access_keys_exist": false
      },
      "s3": {
        "public_access_blocked": 100,
        "default_encryption_enabled": 95,
        "versioning_enabled": 60,
        "logging_enabled": 40,
        "account_public_access_block_enabled": true
      },
      "rds": {
        "encrypted_at_rest": 100,
        "publicly_accessible": 0,
        "deletion_protection": 90,
        "backup_retention_adequate": 100,
        "multi_az_enabled": 80
      },
      "network": {
        "open_to_world_ssh": 2,
        "open_to_world_rdp": 0,
        "flow_logs_enabled": 100
      },
      "account_security": {
        "cloudtrail": {
          "enabled": true,
          "multi_region_enabled": true
        },
        "config": {
          "enabled": true,
          "recorder_running": true
        },
        "guardduty": {
          "enabled": true,
          "unremediated_findings_over_48h": 1
        },
        "security_hub": {
          "enabled": true,
          "cis_aws_foundations_benchmark_level_1": {
            "enabled": true,
            "compliance_percent": 88,
            "compliance_state": "WARNING",
            "passed_controls": 44,
            "failed_controls": 3,
            "warning_controls": 3,
            "not_available_controls": 1
          },
          "cis_aws_foundations_benchmark_level_2": {
            "enabled": true,
            "compliance_percent": 82,
            "compliance_state": "FAILED",
            "passed_controls": 55,
            "failed_controls": 8,
            "warning_controls": 4,
            "not_available_controls": 2
          },
          "cis_aws_foundations_benchmark_unknown_level": {
            "enabled": true,
            "compliance_percent": 90,
            "compliance_state": "FAILED",
            "passed_controls": 9,
            "failed_controls": 1,
            "warning_controls": 0,
            "not_available_controls": 0
          }
        },
        "inspector": {
          "enabled": true,
          "unpatched_server_percent": 25
        }
      }
    }
  ]
}

CI/CD Integration

GitHub Actions with OIDC (Recommended)

Using OIDC, the collector obtains credentials directly from GitHub without needing aws-actions/configure-aws-credentials:

name: Security Posture Collection

on:
  schedule:
    - cron: '0 6 * * *'  # Daily at 6 AM UTC
  workflow_dispatch:

jobs:
  collect:
    runs-on: ubuntu-latest
    permissions:
      id-token: write  # Required for OIDC token
      contents: read

    steps:
      - name: Install epack
        run: |
          curl -sSL https://install.epack.dev | bash

      - name: Collect AWS posture
        run: |
          epack collect
        # epack.yaml should have:
        # collectors:
        #   aws:
        #     source: "locktivity/epack-collector-aws@^0.1.0"
        #     config:
        #       auth_mode: "oidc"
        #       role_arn: "arn:aws:iam::123456789012:role/EpackCollectorRole"
        #     secrets:
        #       - ACTIONS_ID_TOKEN_REQUEST_URL
        #       - ACTIONS_ID_TOKEN_REQUEST_TOKEN

The secrets field tells epack to pass through the ACTIONS_ID_TOKEN_REQUEST_URL and ACTIONS_ID_TOKEN_REQUEST_TOKEN environment variables (provided by GitHub when id-token: write permission is set). The collector uses these to obtain an OIDC token.

GitHub Actions with AssumeRole

If OIDC isn't available, use aws-actions/configure-aws-credentials to obtain bootstrap credentials:

name: Security Posture Collection

on:
  schedule:
    - cron: '0 6 * * *'
  workflow_dispatch:

jobs:
  collect:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read

    steps:
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::BOOTSTRAP_ACCOUNT:role/BootstrapRole
          aws-region: us-east-1

      - name: Install epack
        run: |
          curl -sSL https://install.epack.dev | bash

      - name: Collect AWS posture
        run: |
          epack collect
        # epack.yaml should have:
        # collectors:
        #   aws:
        #     source: "locktivity/epack-collector-aws@^0.1.0"
        #     config:
        #       auth_mode: "assume_role"
        #       role_arn: "arn:aws:iam::TARGET_ACCOUNT:role/EpackCollectorRole"
        #       external_id: "your-external-id"

Terraform for IAM Role

OIDC Trust Policy (Recommended for GitHub Actions)

# First, create the GitHub OIDC provider (once per account)
resource "aws_iam_openid_connect_provider" "github" {
  url             = "https://token.actions.githubusercontent.com"
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = ["ffffffffffffffffffffffffffffffffffffffff"]
}

resource "aws_iam_role" "epack_collector" {
  name = "EpackCollectorRole"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Federated = aws_iam_openid_connect_provider.github.arn
        }
        Action = "sts:AssumeRoleWithWebIdentity"
        Condition = {
          StringEquals = {
            "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
          }
          StringLike = {
            "token.actions.githubusercontent.com:sub" = "repo:${var.github_org}/${var.github_repo}:*"
          }
        }
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "epack_collector" {
  role       = aws_iam_role.epack_collector.name
  policy_arn = aws_iam_policy.epack_collector.arn
}

variable "github_org" {
  description = "GitHub organization name"
  type        = string
}

variable "github_repo" {
  description = "GitHub repository name"
  type        = string
}

AssumeRole Trust Policy

resource "aws_iam_role" "epack_collector" {
  name = "EpackCollectorRole"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          AWS = "arn:aws:iam::${var.collector_account_id}:root"
        }
        Action = "sts:AssumeRole"
        Condition = {
          StringEquals = {
            "sts:ExternalId" = var.external_id
          }
        }
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "epack_collector" {
  role       = aws_iam_role.epack_collector.name
  policy_arn = aws_iam_policy.epack_collector.arn
}

variable "collector_account_id" {
  description = "AWS account ID where the collector runs"
  type        = string
}

variable "external_id" {
  description = "External ID for assume role"
  type        = string
  default     = "epack-collector"
}

Collector Policy (shared by both)

resource "aws_iam_policy" "epack_collector" {
  name        = "EpackCollectorPolicy"
  description = "Read-only access for epack AWS collector"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "iam:GetAccountPasswordPolicy",
          "iam:GetCredentialReport",
          "iam:GenerateCredentialReport",
          "iam:ListUsers",
          "iam:ListMFADevices",
          "iam:ListRoles",
          "iam:GetRole",
          "iam:ListAccountAliases",
          "s3:ListAllMyBuckets",
          "s3:GetBucket*",
          "s3:GetAccountPublicAccessBlock",
          "rds:DescribeDB*",
          "ec2:DescribeVpcs",
          "ec2:DescribeSecurityGroups",
          "ec2:DescribeFlowLogs",
          "ec2:DescribeRegions",
          "cloudtrail:DescribeTrails",
          "cloudtrail:GetTrailStatus",
          "config:Describe*",
          "guardduty:ListDetectors",
          "guardduty:GetDetector",
          "guardduty:ListFindings",
          "securityhub:Describe*",
          "securityhub:Get*",
          "securityhub:List*",
          "sts:GetCallerIdentity"
        ]
        Resource = "*"
      }
    ]
  })
}
v0.1.16 Latest
2026-04-08

**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.15...v0.1.16

darwin/amd64 darwin/arm64 linux/amd64 linux/arm64
v0.1.15
2026-04-08

**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.14...v0.1.15

darwin/amd64 darwin/arm64 linux/amd64 linux/arm64
v0.1.14
2026-04-08

**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.13...v0.1.14

darwin/amd64 darwin/arm64 linux/amd64 linux/arm64
v0.1.13
2026-04-07

**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.12...v0.1.13

darwin/amd64 darwin/arm64 linux/amd64 linux/arm64
v0.1.12
2026-04-07

**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.11...v0.1.12

darwin/amd64 darwin/arm64 linux/amd64 linux/arm64
v0.1.11
2026-04-07

**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.10...v0.1.11

darwin/amd64 darwin/arm64 linux/amd64 linux/arm64
v0.1.10
2026-04-07

**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.9...v0.1.10

darwin/amd64 darwin/arm64 linux/amd64 linux/arm64
v0.1.9
2026-03-18

**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.8...v0.1.9

darwin/amd64 darwin/arm64 linux/amd64 linux/arm64
v0.1.8
2026-03-03

**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.7...v0.1.8

darwin/amd64 darwin/arm64 linux/amd64 linux/arm64
v0.1.7
2026-03-02

**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.6...v0.1.7

darwin/amd64 darwin/arm64 linux/amd64 linux/arm64
v0.1.5
2026-02-27

**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.4...v0.1.5

darwin/amd64 darwin/arm64 linux/amd64 linux/arm64
v0.1.1
2026-02-26

**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.0...v0.1.1

darwin/amd64 darwin/arm64 linux/amd64 linux/arm64

Details

Publisher
locktivity
Latest
v0.1.16
Protocol
v1

Platforms

darwin/amd64 darwin/arm64 linux/amd64 linux/arm64

Links