Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi region support #26

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
*.swp
.bundle
.idea
coverage/
Gemfile.lock
vendor/bundle
aws_public_ips-*.gem
9 changes: 7 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
### 1.0.8
* Display progress while running by specifying `--progress`
* Include tag data in ec2 records by adding `--include_tags name`
* Multi-region support. Defaults to the region specified by AWS_DEFAULT_REGION or AWS_REGION. Many regions can be specified. `all` will include all regions.

### 1.0.7 (11/7/2019)
* Find all addresses when an EC2 instance has multiple ([@breser](https://github.com/breser))
* Add Docker support
Expand All @@ -7,11 +12,11 @@
* Update dependencies

### 1.0.5 (05/25/2018)
* Output more details in text formatter when using --verbose
* Output more details in text formatter when using `--verbose`

### 1.0.4 (05/20/2018)
* Handle RDS and Redshift instances which are in the process of coming up/down
* Add --help, --version commands to CLI
* Add `--help`, `--version` commands to CLI
* Improve CLI error output
* Fix issue where using the EC2 check with the JSON formatter could output empty entries
* Fix issue with RDS check where the endpoint hostname was not being counted as a public IP
Expand Down
1 change: 1 addition & 0 deletions aws_public_ips.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ require 'aws_public_ips/version'
gem.add_dependency('aws-sdk-lightsail', '~> 1.10.0')
gem.add_dependency('aws-sdk-rds', '~> 1.35.0')
gem.add_dependency('aws-sdk-redshift', '~> 1.13.0')
gem.add_dependency('tty-spinner', '~>0.9.0 ')

gem.add_development_dependency('bundler-audit', '~> 0.6.0')
gem.add_development_dependency('coveralls', '~> 0.8.22')
Expand Down
1 change: 1 addition & 0 deletions lib/aws_public_ips.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module AwsPublicIps

require 'aws_public_ips/checks'
require 'aws_public_ips/cli'
require 'aws_public_ips/cli_options'
require 'aws_public_ips/formatters'
require 'aws_public_ips/utils'
require 'aws_public_ips/version'
30 changes: 15 additions & 15 deletions lib/aws_public_ips/checks/apigateway.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@
module AwsPublicIps
module Checks
module Apigateway
def self.run
client = ::Aws::APIGateway::Client.new
return [] unless ::AwsPublicIps::Utils.has_service?(client)
def self.run(options)
::AwsPublicIps::Utils.probe(::Aws::APIGateway::Client, options[:regions], options[:progress]) do |client|
# TODO(arkadiy) https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-private-integration.html

# TODO(arkadiy) https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-private-integration.html

# APIGateway doesn't return the full domain in the response, we have to build
# it using the api id and region
client.get_rest_apis.flat_map do |response|
response.items.map do |api|
hostname = "#{api.id}.execute-api.#{client.config.region}.amazonaws.com"
{
id: api.id,
hostname: hostname,
ip_addresses: ::AwsPublicIps::Utils.resolve_hostname(hostname)
}
# APIGateway doesn't return the full domain in the response, we have to build
# it using the api id and region
client.get_rest_apis.flat_map do |response|
response.items.map do |api|
hostname = "#{api.id}.execute-api.#{client.config.region}.amazonaws.com"
{
region: client.config.region,
id: api.id,
hostname: hostname,
ip_addresses: ::AwsPublicIps::Utils.resolve_hostname(hostname)
}
end
end
end
end
Expand Down
26 changes: 13 additions & 13 deletions lib/aws_public_ips/checks/cloudfront.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@
module AwsPublicIps
module Checks
module Cloudfront
def self.run
client = ::Aws::CloudFront::Client.new
return [] unless ::AwsPublicIps::Utils.has_service?(client)
def self.run(options)
::AwsPublicIps::Utils.probe(::Aws::CloudFront::Client, options[:regions] & ['us-east-1'],
options[:progress]) do |client|
# Cloudfront distributions are always public, they don't have a concept of VPC
# No "coming up" problem here like with RDS/Redshift

# Cloudfront distrubtions are always public, they don't have a concept of VPC
# No "coming up" problem here like with RDS/Redshift

client.list_distributions.flat_map do |response|
response.distribution_list.items.flat_map do |distribution|
{
id: distribution.id,
hostname: distribution.domain_name,
ip_addresses: ::AwsPublicIps::Utils.resolve_hostname(distribution.domain_name)
}
client.list_distributions.flat_map do |response|
response.distribution_list.items.flat_map do |distribution|
{
id: distribution.id,
hostname: distribution.domain_name,
ip_addresses: ::AwsPublicIps::Utils.resolve_hostname(distribution.domain_name)
}
end
end
end
end
Expand Down
52 changes: 27 additions & 25 deletions lib/aws_public_ips/checks/ec2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,42 @@
module AwsPublicIps
module Checks
module Ec2
def self.run
client = ::Aws::EC2::Client.new
return [] unless ::AwsPublicIps::Utils.has_service?(client)

def self.run(options)
# Iterate over all EC2 instances. This will include those from EC2, ECS, EKS, Fargate, Batch,
# Beanstalk, and NAT Instances
# It will not include NAT Gateways (IPv4) or Egress Only Internet Gateways (IPv6), but they do not allow
# ingress traffic so we skip them anyway
client.describe_instances.flat_map do |response|
response.reservations.flat_map do |reservation|
reservation.instances.flat_map do |instance|
# EC2-Classic instances have a `public_ip_address` and no `network_interfaces`
# EC2-VPC instances both set, so we uniq the ip addresses
ip_addresses = [instance.public_ip_address].compact + instance.network_interfaces.flat_map do |interface|
public_ip = []
::AwsPublicIps::Utils.probe(::Aws::EC2::Client, options[:regions], options[:progress]) do |client|
client.describe_instances.flat_map do |response|
response.reservations.flat_map do |reservation|
reservation.instances.flat_map do |instance|
# EC2-Classic instances have a `public_ip_address` and no `network_interfaces`
# EC2-VPC instances both set, so we uniq the ip addresses
ip_addresses = [instance.public_ip_address].compact + instance.network_interfaces.flat_map do |interface|
public_ip = []

interface.private_ip_addresses.flat_map do |private_ip|
if private_ip.association && private_ip.association.public_ip
public_ip << private_ip.association.public_ip
interface.private_ip_addresses.flat_map do |private_ip|
if private_ip.association && private_ip.association.public_ip
public_ip << private_ip.association.public_ip
end
end
public_ip + interface.ipv_6_addresses.map(&:ipv_6_address)
end
public_ip + interface.ipv_6_addresses.map(&:ipv_6_address)
end

# Don't return an entry if all ips were private
next [] if ip_addresses.empty?
# Don't return an entry if all ips were private
next [] if ip_addresses.empty?

# If hostname is empty string, canonicalize to nil
hostname = instance.public_dns_name.empty? ? nil : instance.public_dns_name
{
id: instance.instance_id,
hostname: hostname,
ip_addresses: ip_addresses.uniq
}
# If hostname is empty string, canonicalize to nil
hostname = instance.public_dns_name.empty? ? nil : instance.public_dns_name
result = {
region: client.config.region,
id: instance.instance_id,
hostname: hostname,
ip_addresses: ip_addresses.uniq
}
::AwsPublicIps::Utils.add_tags(result, instance, options[:tags])
result
end
end
end
end
Expand Down
32 changes: 16 additions & 16 deletions lib/aws_public_ips/checks/elasticsearch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,28 @@
module AwsPublicIps
module Checks
module Elasticsearch
def self.run
client = ::Aws::ElasticsearchService::Client.new
return [] unless ::AwsPublicIps::Utils.has_service?(client)

def self.run(options)
# ElasticSearch instances can be launched into classic into VPCs. Classic instances are public and have a
# `domain_status.endpoint` hostname, and VPC instances have a `domain_status.endpoints['vpc']` hostname.
# However VPC ElasticSearch instances create their own Network Interface and AWS will not allow you
# to associate an Elastic IP to it. As a result VPC ElasticSearch instances are always private, even with an
# internet gateway.
::AwsPublicIps::Utils.probe(::Aws::ElasticsearchService::Client,
options[:regions], options[:progress]) do |client|
client.list_domain_names.flat_map do |response|
response.domain_names.flat_map do |domain_name|
client.describe_elasticsearch_domain(domain_name: domain_name.domain_name).map do |domain|
hostname = domain.domain_status.endpoint
next unless hostname

client.list_domain_names.flat_map do |response|
response.domain_names.flat_map do |domain_name|
client.describe_elasticsearch_domain(domain_name: domain_name.domain_name).map do |domain|
hostname = domain.domain_status.endpoint
next unless hostname

{
id: domain.domain_status.domain_id,
hostname: hostname,
ip_addresses: ::AwsPublicIps::Utils.resolve_hostname(hostname)
}
end.compact
{
region: client.config.region,
id: domain.domain_status.domain_id,
hostname: hostname,
ip_addresses: ::AwsPublicIps::Utils.resolve_hostname(hostname)
}
end.compact
end
end
end
end
Expand Down
29 changes: 15 additions & 14 deletions lib/aws_public_ips/checks/elb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,24 @@
module AwsPublicIps
module Checks
module Elb
def self.run
client = ::Aws::ElasticLoadBalancing::Client.new
return [] unless ::AwsPublicIps::Utils.has_service?(client)

def self.run(options)
# EC2-Classic load balancers are only returned by the 'elasticloadbalancing' API, and
# EC2-VPC ALBs/NLBs are only returned by the 'elasticloadbalancingv2' API
client.describe_load_balancers.flat_map do |response|
response.load_balancer_descriptions.flat_map do |load_balancer|
next [] unless load_balancer.scheme == 'internet-facing'
::AwsPublicIps::Utils.probe(::Aws::ElasticLoadBalancing::Client,
options[:regions], options[:progress]) do |client|
client.describe_load_balancers.flat_map do |response|
response.load_balancer_descriptions.flat_map do |load_balancer|
next [] unless load_balancer.scheme == 'internet-facing'

# EC2-Classic load balancers get IPv6 DNS records created but they are not returned by the API
hostnames = [load_balancer.dns_name, "ipv6.#{load_balancer.dns_name}"]
{
id: load_balancer.canonical_hosted_zone_name_id,
hostname: load_balancer.dns_name,
ip_addresses: ::AwsPublicIps::Utils.resolve_hostnames(hostnames)
}
# EC2-Classic load balancers get IPv6 DNS records created but they are not returned by the API
hostnames = [load_balancer.dns_name, "ipv6.#{load_balancer.dns_name}"]
{
region: client.config.region,
id: load_balancer.canonical_hosted_zone_name_id,
hostname: load_balancer.dns_name,
ip_addresses: ::AwsPublicIps::Utils.resolve_hostnames(hostnames)
}
end
end
end
end
Expand Down
25 changes: 13 additions & 12 deletions lib/aws_public_ips/checks/elbv2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,26 @@
module AwsPublicIps
module Checks
module Elbv2
def self.run
client = ::Aws::ElasticLoadBalancingV2::Client.new
return [] unless ::AwsPublicIps::Utils.has_service?(client)

def self.run(options)
# EC2-Classic load balancers are only returned by the 'elasticloadbalancing' API, and
# EC2-VPC ALBs/NLBs are only returned by the 'elasticloadbalancingv2' API

# NLBs only support IPv4
# ALBs support IPv4 or dualstack. Unlike Classic ELBs which have a separate IPv6 DNS name,
# dualstack ALBs only have a single DNS name
client.describe_load_balancers.flat_map do |response|
response.load_balancers.flat_map do |load_balancer|
next [] unless load_balancer.scheme == 'internet-facing'
::AwsPublicIps::Utils.probe(::Aws::ElasticLoadBalancingV2::Client,
options[:regions], options[:progress]) do |client|
client.describe_load_balancers.flat_map do |response|
response.load_balancers.flat_map do |load_balancer|
next [] unless load_balancer.scheme == 'internet-facing'

{
id: load_balancer.canonical_hosted_zone_id,
hostname: load_balancer.dns_name,
ip_addresses: ::AwsPublicIps::Utils.resolve_hostname(load_balancer.dns_name)
}
{
region: client.config.region,
id: load_balancer.canonical_hosted_zone_id,
hostname: load_balancer.dns_name,
ip_addresses: ::AwsPublicIps::Utils.resolve_hostname(load_balancer.dns_name)
}
end
end
end
end
Expand Down
48 changes: 24 additions & 24 deletions lib/aws_public_ips/checks/lightsail.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,35 @@
module AwsPublicIps
module Checks
module Lightsail
def self.run
client = ::Aws::Lightsail::Client.new
return [] unless ::AwsPublicIps::Utils.has_service?(client)

def self.run(options)
# Lightsail instances are always exposed directly, and can also be put behind a load balancer

instances = client.get_instances.flat_map do |response|
response.instances.map do |instance|
{
# Names are unique
id: instance.name,
hostname: nil,
ip_addresses: [instance.public_ip_address]
}
::AwsPublicIps::Utils.probe(::Aws::Lightsail::Client, options[:regions], options[:progress]) do |client|
instances = client.get_instances.flat_map do |response|
response.instances.map do |instance|
{
# Names are unique
region: client.config.region,
id: instance.name,
hostname: nil,
ip_addresses: [instance.public_ip_address]
}
end
end
end

load_balancers = client.get_load_balancers.flat_map do |response|
response.load_balancers.map do |load_balancer|
{
# Names are unique
id: load_balancer.name,
hostname: load_balancer.dns_name,
ip_addresses: ::AwsPublicIps::Utils.resolve_hostname(load_balancer.dns_name)
}
load_balancers = client.get_load_balancers.flat_map do |response|
response.load_balancers.map do |load_balancer|
{
# Names are unique
region: client.config.region,
id: load_balancer.name,
hostname: load_balancer.dns_name,
ip_addresses: ::AwsPublicIps::Utils.resolve_hostname(load_balancer.dns_name)
}
end
end
end

instances + load_balancers
instances + load_balancers
end
end
end
end
Expand Down
Loading