Mapping Your AWS Attack Surface: How aws_public_ips Enumerates Every Internet-Facing Resource
Hook
Your AWS account probably has more public IP addresses than you think. In a recent audit of a mid-sized SaaS company, security engineers discovered 47 internet-facing resources they didn't know existed—including three forgotten RDS instances with public endpoints.
Context
As AWS environments grow, maintaining an accurate inventory of internet-facing resources becomes increasingly difficult. Public IP addresses represent your organization's attack surface—every potential entry point for threat actors. Yet AWS provides no single dashboard or API call to list all public IPs across your account. You'd need to manually check EC2 instances, load balancers, RDS databases, CloudFront distributions, Elastic IPs (even unattached ones), Lightsail instances, API Gateways, and dozens of other services. Each service has different API endpoints, data structures, and ways of exposing public connectivity.
This fragmentation creates blind spots. Security teams preparing for penetration tests need comprehensive asset inventories. Compliance auditors require evidence of internet-facing resources. DevSecOps engineers want to ensure nothing is accidentally exposed. The aws_public_ips tool emerged to solve this specific problem: provide a single command that exhaustively enumerates every public IPv4 and IPv6 address associated with your AWS account, regardless of service or networking model. It's not about monitoring or alerting—it's about visibility at a specific point in time, answering the fundamental question: what can the internet reach in my AWS environment?
Technical Insight
The aws_public_ips tool is architecturally straightforward but operationally comprehensive. Built in Ruby, it wraps the aws-sdk-ruby library and systematically queries describe and list operations across AWS services. The core insight is that each AWS service exposes public IP information differently—EC2 instances have NetworkInterfaces with PublicIp attributes, Classic ELBs expose DNSName fields that resolve to IPs, RDS instances have Endpoint addresses, and CloudFront distributions use domain names backed by AWS's edge network IPs.
The tool's service coverage is impressive. It handles EC2 instances (both classic and VPC), Elastic IPs (including unallocated addresses that still cost money), all three load balancer types (Classic, Application, Network), RDS instances with publicly accessible endpoints, Redshift clusters, ElasticSearch domains, API Gateway endpoints, Lightsail instances, and more. For each service, it executes read-only API calls and parses responses to extract IP addresses or DNS names that resolve to IPs.
Here's how you'd use it in practice:
# Install the gem
# gem install aws_public_ips
# As a CLI tool - simplest invocation
$ aws_public_ips
# Output all IPs as JSON for parsing
$ aws_public_ips --json > aws_ips.json
# Use as a Ruby library for programmatic access
require 'aws_public_ips'
results = AwsPublicIps.check()
# Results grouped by service
results.each do |service_name, ip_addresses|
puts "#{service_name}: #{ip_addresses.length} public IPs"
ip_addresses.each do |ip|
puts " - #{ip}"
end
end
# Example output structure:
# {
# "ec2": ["54.123.45.67", "2600:1f18:1234:5678::1"],
# "elb": ["52.45.67.89"],
# "rds": ["18.234.56.78"]
# }
The authentication model leverages AWS's standard credential chain: environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY), shared credentials file (~/.aws/credentials), IAM instance profiles for EC2, or ECS task roles. This makes it easy to integrate into CI/CD pipelines or run from security scanning infrastructure. The tool automatically iterates across all AWS regions, which is critical since resources in us-west-2 won't appear in us-east-1 API calls.
One clever aspect is how it handles DNS-based services like CloudFront and ELBs. These services don't directly expose IP addresses via APIs—they return DNS names like d111111abcdef8.cloudfront.net. The tool resolves these names to their current IP addresses, though this means CloudFront IPs represent AWS's edge network rather than dedicated addresses. This is still valuable for attack surface mapping since those IPs actively serve your content.
The Docker implementation is particularly useful for organizations that don't run Ruby infrastructure:
# Mount AWS credentials and run
$ docker run --rm \
-v ~/.aws:/root/.aws:ro \
arkadiyt/aws_public_ips --json
# Or use environment variables
$ docker run --rm \
-e AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE \
-e AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY \
arkadiyt/aws_public_ips
The IAM permissions required are minimal—only read-only describe and list operations. A typical IAM policy would include actions like ec2:DescribeInstances, elasticloadbalancing:DescribeLoadBalancers, rds:DescribeDBInstances, and similar read operations across supported services. No write, delete, or modify permissions are needed, making this safe to run with restricted service accounts. For security scanning pipelines, you'd create a dedicated IAM role with these limited permissions and assume it during scans.
Gotcha
The tool has several practical limitations that affect its utility in certain scenarios. First, it only discovers services that expose public IPs through AWS APIs. Services like S3 buckets, CloudWatch endpoints, or ElastiCache clusters that use shared AWS infrastructure IPs won't appear—even if they're publicly accessible. This means you're getting IP addresses, not a complete picture of internet-facing resources. An S3 bucket with public-read permissions is absolutely part of your attack surface but won't show up in the output.
Second, there's no built-in filtering or enrichment. The tool returns every public IP across your entire AWS organization without metadata about resource names, tags, or purpose. In a large AWS account with hundreds of public IPs, you get a flat list that requires additional work to contextualize. You can't filter by specific regions, VPCs, or tags during enumeration—it's all or nothing. For organizations needing more granular control, you'd need to pipe the JSON output to additional tooling for filtering and correlation with other asset databases. Additionally, the tool provides a point-in-time snapshot with no built-in scheduling, diffing, or alerting. If you need continuous monitoring for newly exposed resources, you'd have to build that orchestration separately—perhaps running it on a cron schedule and comparing outputs over time to detect changes.
Verdict
Use if: You're a security engineer conducting attack surface assessments, preparing for penetration tests, or maintaining asset inventories for compliance requirements. It's perfect for periodic audits where you need a comprehensive snapshot of internet-facing AWS resources across all regions and services. Use it if you work in environments where accidental public exposure is a concern and you want a quick verification tool. It's also valuable for FinOps teams hunting for unattached Elastic IPs that generate costs without providing value. The Docker container makes it accessible even if your primary toolchain isn't Ruby-based. Skip if: You need real-time monitoring with alerting on newly exposed resources—this is a scanner, not a monitoring platform. Skip it if you require detailed resource metadata, tagging information, or contextual details beyond IP addresses; you'd need AWS Config or similar tools for that. Also skip if your security requirements focus on non-IP-based internet exposure like S3 bucket policies or IAM misconfigurations, which this tool doesn't address. For comprehensive AWS security posture management, combine aws_public_ips with broader tools like Prowler or ScoutSuite rather than relying on it alone.