epack install collector aws
Adds to epack.yaml, resolves dependencies, downloads binary.
Run all configured collectors and build a pack:
epack collect
Runs all collectors in epack.yaml and outputs an evidence pack.
Or add manually to epack.yaml:
collectors:
aws:
source: https://github.com/locktivity/epack-collector-aws
Then run epack install to lock and sync.
The AWS collector gathers security posture metrics from AWS accounts, providing visibility into IAM, S3, RDS, network, and account-level security configurations.
| 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) |
| 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 |
| 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 |
| 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 |
| 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.
go test ./...AWS_E2E_RUN=true go test -tags=e2e -v ./internal/collector/...The collector automatically handles AWS's regional vs global services:
Metrics from regional services are aggregated across all regions.
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.
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)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
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"
If no role_arn is specified, the collector uses the AWS SDK's default credential chain:
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)~/.aws/credentials)collectors:
aws:
source: "locktivity/epack-collector-aws@^0.1.0"
config:
regions:
- us-east-1
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": "*"
}
]
}
For organizations with multiple AWS accounts:
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
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_idis ignored in OIDC mode. The OIDC token claims provide equivalent security through repository/branch constraints in the trust policy.
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
The collector automatically retries generating the credential report. If it times out after 10 attempts, check IAM permissions for iam:GenerateCredentialReport.
Some services (like GuardDuty) may not be available in all regions. The collector handles this gracefully and continues with available services.
The AWS SDK automatically handles rate limiting with exponential backoff. For large accounts with many resources, collection may take several minutes.
Collect from the current AWS account using default credentials:
collectors:
aws:
source: "locktivity/epack-collector-aws@^0.1.0"
config: {}
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
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"
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
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"
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
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
}
}
}
]
}
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.
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"
# 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
}
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"
}
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 = "*"
}
]
})
}
**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.15...v0.1.16
**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.14...v0.1.15
**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.13...v0.1.14
**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.12...v0.1.13
**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.11...v0.1.12
**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.10...v0.1.11
**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.9...v0.1.10
**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.8...v0.1.9
**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.7...v0.1.8
**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.6...v0.1.7
**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.4...v0.1.5
**Full Changelog**: https://github.com/locktivity/epack-collector-aws/compare/v0.1.0...v0.1.1