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. The
amount of detail it gathers is controlled by an optional level config knob
with three values: trust (default, pass/fail posture only), audit (adds
per-resource breakdowns), and internal (adds raw identifying detail for
breach investigation). See levels.md for the full contract.
This page lists the surfaces and their trust-level signals. Per-level field
breakdowns live in levels.md; the machine-readable shape lives
in schema/v1.0.0.json.
| 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.
| Metric | Description |
|---|---|
enabled |
Whether an IdC instance exists in the primary region |
user_count |
Users in the connected identity store |
group_count |
Groups in the connected identity store |
permission_set_count |
Permission sets provisioned on the instance |
| Metric | Description |
|---|---|
function_count |
Total Lambda functions across regions |
deprecated_runtime_count |
Functions on AWS-deprecated runtimes (Node ≤16, Python ≤3.8, Ruby ≤2.7, Java 8, Go 1.x, .NET Core ≤6) |
| Metric | Description |
|---|---|
instance_count |
Running instances across regions |
imdsv2_required_count |
Instances enforcing IMDSv2 (HttpTokens=required) |
public_ip_count |
Instances with a public IP |
default_vpc_count |
Instances in the default VPC (anti-pattern) |
instances_with_unencrypted_volume_count |
Instances with at least one unencrypted attached EBS volume |
| Metric | Description |
|---|---|
log_group_count |
Log groups across regions |
log_groups_without_retention_count |
Groups with no retention policy (logs accumulate forever) |
log_groups_without_customer_kms_count |
Groups using AWS-managed (vs customer-managed) KMS |
Scoped to CUSTOMER-managed keys only; AWS-managed keys offer no posture lever.
| Metric | Description |
|---|---|
customer_managed_key_count |
Customer-managed keys across regions |
cmks_with_rotation_disabled_count |
Symmetric CMKs without automatic key rotation |
cmks_pending_deletion_count |
Keys scheduled for deletion |
Secret VALUES are NEVER collected — only metadata. Value-reading APIs are forbidden in collector source by build-time lint.
| Metric | Description |
|---|---|
secret_count |
Secrets across regions |
secrets_without_rotation_count |
Secrets without auto-rotation configured |
secrets_without_customer_kms_count |
Secrets using AWS-managed (vs customer-managed) KMS |
secrets_pending_deletion_count |
Secrets scheduled for deletion |
Parameter VALUES are NEVER collected — only metadata. Value-reading APIs are forbidden in collector source by build-time lint.
| Metric | Description |
|---|---|
parameter_count |
Parameters across regions |
secure_string_count |
SecureString-type parameters (the encrypted, sensitive ones) |
secure_strings_without_customer_kms_count |
SecureStrings using alias/aws/ssm (vs a customer-managed key) |
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.16...v0.2.0
**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