aws-inventory 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.rspec +2 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +661 -0
- data/Guardfile +19 -0
- data/LICENSE.txt +22 -0
- data/README.md +65 -0
- data/Rakefile +6 -0
- data/exe/aws-inventory +14 -0
- data/inventory.gemspec +37 -0
- data/lib/inventory.rb +29 -0
- data/lib/inventory/acm.rb +15 -0
- data/lib/inventory/aws_services.rb +53 -0
- data/lib/inventory/base.rb +74 -0
- data/lib/inventory/cfn.rb +50 -0
- data/lib/inventory/cli.rb +86 -0
- data/lib/inventory/cloudwatch.rb +38 -0
- data/lib/inventory/command.rb +25 -0
- data/lib/inventory/eb.rb +19 -0
- data/lib/inventory/ec2.rb +86 -0
- data/lib/inventory/ecs.rb +10 -0
- data/lib/inventory/ecs/cluster.rb +20 -0
- data/lib/inventory/ecs/service.rb +33 -0
- data/lib/inventory/elb.rb +71 -0
- data/lib/inventory/help.rb +9 -0
- data/lib/inventory/help/acm.md +1 -0
- data/lib/inventory/help/cfn.md +1 -0
- data/lib/inventory/help/cw.md +1 -0
- data/lib/inventory/help/eb.md +1 -0
- data/lib/inventory/help/ec2.md +1 -0
- data/lib/inventory/help/ecs.md +1 -0
- data/lib/inventory/help/elb.md +1 -0
- data/lib/inventory/help/iam.md +15 -0
- data/lib/inventory/help/keypair.md +1 -0
- data/lib/inventory/help/rds.md +5 -0
- data/lib/inventory/help/sg.md +1 -0
- data/lib/inventory/help/vpc.md +1 -0
- data/lib/inventory/iam.rb +13 -0
- data/lib/inventory/iam/group.rb +22 -0
- data/lib/inventory/iam/shared.rb +62 -0
- data/lib/inventory/iam/summary.rb +16 -0
- data/lib/inventory/iam/user.rb +21 -0
- data/lib/inventory/keypair.rb +25 -0
- data/lib/inventory/presenter.rb +20 -0
- data/lib/inventory/presenters/base.rb +5 -0
- data/lib/inventory/presenters/tab.rb +7 -0
- data/lib/inventory/presenters/table.rb +10 -0
- data/lib/inventory/rds.rb +11 -0
- data/lib/inventory/rds/port.rb +53 -0
- data/lib/inventory/rds/shared.rb +43 -0
- data/lib/inventory/rds/summary.rb +23 -0
- data/lib/inventory/route53.rb +29 -0
- data/lib/inventory/security_group.rb +11 -0
- data/lib/inventory/security_group/open.rb +76 -0
- data/lib/inventory/security_group/shared.rb +14 -0
- data/lib/inventory/security_group/summary.rb +17 -0
- data/lib/inventory/shared.rb +16 -0
- data/lib/inventory/version.rb +3 -0
- data/lib/inventory/vpc.rb +55 -0
- data/spec/lib/cli_spec.rb +31 -0
- data/spec/lib/inventory/base_spec.rb +7 -0
- data/spec/lib/inventory/security_group/open_spec.rb +31 -0
- data/spec/spec_helper.rb +24 -0
- metadata +308 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
class Inventory::Iam
|
2
|
+
class User < Inventory::Base
|
3
|
+
include Shared
|
4
|
+
|
5
|
+
def header
|
6
|
+
["User", "Password Last Used"]
|
7
|
+
end
|
8
|
+
|
9
|
+
def data
|
10
|
+
users.map do |user|
|
11
|
+
group_names = group_names(user).join(',')
|
12
|
+
|
13
|
+
[user.user_name, group_names]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def sort(data)
|
18
|
+
data.sort_by { |item| item[1].to_i }.reverse
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Inventory::Keypair < Inventory::Base
|
2
|
+
def header
|
3
|
+
["Key Name", "Instance Count"]
|
4
|
+
end
|
5
|
+
|
6
|
+
def data
|
7
|
+
key_pairs.map do |key|
|
8
|
+
instance_count = instance_count(key)
|
9
|
+
|
10
|
+
[key.key_name, instance_count]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def sort(data)
|
15
|
+
data.sort_by { |i| i[1] }.reverse
|
16
|
+
end
|
17
|
+
|
18
|
+
def key_pairs
|
19
|
+
@key_pairs ||= ec2.describe_key_pairs.key_pairs
|
20
|
+
end
|
21
|
+
|
22
|
+
def instance_count(key)
|
23
|
+
instances.count { |i| i.key_name == key.key_name }
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Inventory::Presenter
|
2
|
+
autoload :Base, "inventory/presenters/base"
|
3
|
+
autoload :Tab, "inventory/presenters/tab"
|
4
|
+
autoload :Table, "inventory/presenters/table"
|
5
|
+
|
6
|
+
def initialize(data)
|
7
|
+
@data = data
|
8
|
+
end
|
9
|
+
|
10
|
+
def display
|
11
|
+
presenter_class = "Inventory::Presenter::#{format.classify}".constantize
|
12
|
+
presenter = presenter_class.new(@data)
|
13
|
+
presenter.display
|
14
|
+
end
|
15
|
+
|
16
|
+
# Formats: tabs, markdown
|
17
|
+
def format
|
18
|
+
ENV['AWS_INVENTORY_FORMAT'] || "table"
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class Inventory::Rds < Inventory::Base
|
2
|
+
autoload :Shared, "inventory/rds/shared"
|
3
|
+
autoload :Summary, "inventory/rds/summary"
|
4
|
+
autoload :Port, "inventory/rds/port"
|
5
|
+
|
6
|
+
# Default is the open report because it seems like the most useful report
|
7
|
+
def report
|
8
|
+
Summary.new(@options).report if show(:summary)
|
9
|
+
Port.new(@options).report if show(:port)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class Inventory::Rds
|
2
|
+
class Port < Inventory::Base
|
3
|
+
include Shared
|
4
|
+
|
5
|
+
def header
|
6
|
+
["RDS Db Name", "Security Group", "Range/Source", "Port"]
|
7
|
+
end
|
8
|
+
|
9
|
+
def data
|
10
|
+
data = []
|
11
|
+
db_instances.each do |db|
|
12
|
+
db_security_groups = vpc_security_groups(db)
|
13
|
+
db_security_groups.each do |sg|
|
14
|
+
|
15
|
+
sg.ip_permissions.each do |permission|
|
16
|
+
data << [
|
17
|
+
db.db_name,
|
18
|
+
"#{sg.group_id} (#{sg.group_name})",
|
19
|
+
ip_range_and_source(permission),
|
20
|
+
port(permission)
|
21
|
+
]
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
data
|
27
|
+
end
|
28
|
+
|
29
|
+
def port(permission)
|
30
|
+
ports = [permission.from_port, permission.to_port].uniq
|
31
|
+
if ports.size > 1
|
32
|
+
raise "TODO: account for port ranges"
|
33
|
+
else
|
34
|
+
ports.first
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def ip_range_and_source(permission)
|
39
|
+
cidr_ips = permission.ip_ranges.map {|range| range.cidr_ip }
|
40
|
+
user_id_group_pairs = permission.user_id_group_pairs.map do |pair|
|
41
|
+
# pair.group_name is always returning nil :( Might be AWS bug
|
42
|
+
# so fetching it from security groups themselves
|
43
|
+
sg = security_groups.find {|sg| sg.group_id == pair.group_id }
|
44
|
+
sg_name = " (#{sg.group_name})" if sg
|
45
|
+
|
46
|
+
"#{pair.group_id}#{sg_name}" # pretty format
|
47
|
+
end
|
48
|
+
result = cidr_ips + user_id_group_pairs
|
49
|
+
result.join(', ')
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Inventory::Rds::Shared
|
2
|
+
# pretty name of vpc
|
3
|
+
def vpc_name(db)
|
4
|
+
group_ids = db.vpc_security_groups.map(&:vpc_security_group_id)
|
5
|
+
resp = ec2.describe_security_groups(group_ids: group_ids)
|
6
|
+
groups = resp.security_groups
|
7
|
+
vpc_ids = groups.map(&:vpc_id)
|
8
|
+
vpc_ids.map do |vpc_id|
|
9
|
+
pretty_vpc_name = lookup_vpc_name(vpc_id)
|
10
|
+
"#{vpc_id} (#{pretty_vpc_name})"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def lookup_vpc_name(vpc_id)
|
15
|
+
inventory_vpc = Inventory::Vpc.new(@options)
|
16
|
+
inventory_vpc.vpc_name(vpc_id)
|
17
|
+
end
|
18
|
+
|
19
|
+
def vpcs
|
20
|
+
@vpcs ||= ec2.describe_vpcs.vpcs
|
21
|
+
end
|
22
|
+
|
23
|
+
def pretty_vpc_security_group(db)
|
24
|
+
groups = vpc_security_groups(db)
|
25
|
+
groups.map { |g| "#{g.group_id} (#{g.group_name})" }
|
26
|
+
end
|
27
|
+
|
28
|
+
# pretty name of the vpc security groups
|
29
|
+
def vpc_security_groups(db)
|
30
|
+
group_ids = db.vpc_security_groups.map(&:vpc_security_group_id)
|
31
|
+
group_ids.map do |db_security_group_id|
|
32
|
+
security_groups.find {|sg| sg.group_id == db_security_group_id }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def db_instances
|
37
|
+
@db_instances ||= rds.describe_db_instances.db_instances
|
38
|
+
end
|
39
|
+
|
40
|
+
def security_group_names(instance)
|
41
|
+
instance.security_groups.map {|sg| sg.group_name}.join(', ')
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Inventory::Rds
|
2
|
+
class Summary < Inventory::Base
|
3
|
+
include Shared
|
4
|
+
|
5
|
+
def header
|
6
|
+
["Name", "Engine", "Instance Class", "Publicly Accessible", "VPC", "Security Groups"]
|
7
|
+
#
|
8
|
+
end
|
9
|
+
|
10
|
+
def data
|
11
|
+
db_instances.map do |db|
|
12
|
+
[
|
13
|
+
db.db_name,
|
14
|
+
db.engine,
|
15
|
+
db.db_instance_class,
|
16
|
+
db.publicly_accessible ? "yes" : "no",
|
17
|
+
vpc_name(db),
|
18
|
+
pretty_vpc_security_group(db),
|
19
|
+
]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Inventory::Route53 < Inventory::Base
|
2
|
+
def header
|
3
|
+
["Domain", "Record Set Count"]
|
4
|
+
end
|
5
|
+
|
6
|
+
def data
|
7
|
+
zones.map do |zone|
|
8
|
+
record_sets = resource_record_sets(zone)
|
9
|
+
[zone.name, record_sets.count]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def records
|
14
|
+
zones.inject([]) do |array, zone|
|
15
|
+
array << resource_record_sets(zone)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
@@resource_record_sets = {}
|
20
|
+
def resource_record_sets(zone)
|
21
|
+
@@resource_record_sets[zone.id] ||= route53
|
22
|
+
.list_resource_record_sets(hosted_zone_id: zone.id)
|
23
|
+
.resource_record_sets
|
24
|
+
end
|
25
|
+
|
26
|
+
def zones
|
27
|
+
@zones ||= route53.list_hosted_zones.hosted_zones
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class Inventory::SecurityGroup < Inventory::Base
|
2
|
+
autoload :Shared, "inventory/security_group/shared"
|
3
|
+
autoload :Summary, "inventory/security_group/summary"
|
4
|
+
autoload :Open, "inventory/security_group/open"
|
5
|
+
|
6
|
+
# Default is the open report because it seems like the most useful report
|
7
|
+
def report
|
8
|
+
Summary.new(@options).report if show(:summary)
|
9
|
+
Open.new(@options).report if show(:open)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'facets/array/arrange'
|
2
|
+
|
3
|
+
class Inventory::SecurityGroup
|
4
|
+
class Open < Inventory::Base
|
5
|
+
include Shared
|
6
|
+
|
7
|
+
def header
|
8
|
+
["Security Group", "Open to World"]
|
9
|
+
end
|
10
|
+
|
11
|
+
def data
|
12
|
+
opened_security_groups_in_use = opened_security_groups.select do |sg|
|
13
|
+
group_ids_in_use = used_security_groups.map(&:group_id)
|
14
|
+
group_ids_in_use.include?(sg.group_id)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Only display used security groups that have opened ports for review.
|
18
|
+
# will delete the unused security groups anyway.
|
19
|
+
opened_security_groups_in_use.map do |sg|
|
20
|
+
ports = ports_open_to_world(sg)
|
21
|
+
[
|
22
|
+
sg.group_name,
|
23
|
+
ports
|
24
|
+
]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def opened_security_groups
|
29
|
+
security_groups.select do |sg|
|
30
|
+
ports = ports_open_to_world(sg)
|
31
|
+
!ports.empty?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns an Array of ports with a cidr of 0.0.0.0/0
|
36
|
+
def ports_open_to_world(sg)
|
37
|
+
ip_permissions = sg.ip_permissions.select do |permission|
|
38
|
+
permission.ip_ranges.detect do |ip_range|
|
39
|
+
ip_range.include?('0.0.0.0/0')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
ports = ip_permissions.map do |p|
|
44
|
+
if p.from_port == p.to_port
|
45
|
+
p.from_port
|
46
|
+
else
|
47
|
+
(p.from_port..p.to_port)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
ports = combine_ports(ports)
|
52
|
+
# convert to string for printing
|
53
|
+
ports.map(&:to_s).join(', ')
|
54
|
+
end
|
55
|
+
|
56
|
+
# Examples
|
57
|
+
#
|
58
|
+
# Input:
|
59
|
+
# ports: [80, 443]
|
60
|
+
# Output:
|
61
|
+
# ports: [80, 443
|
62
|
+
#
|
63
|
+
# Input:
|
64
|
+
# ports: [8001, 8000..8002]
|
65
|
+
# Output:
|
66
|
+
# ports: [8000..8002]
|
67
|
+
def combine_ports(port_objects)
|
68
|
+
ports = port_objects.inject([]) do |array, port|
|
69
|
+
ports = port.is_a?(Range) ? port.to_a : [port]
|
70
|
+
array += ports
|
71
|
+
array
|
72
|
+
end.uniq
|
73
|
+
ports.arrange
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Inventory::SecurityGroup::Shared
|
2
|
+
def used_security_groups
|
3
|
+
groups = instances.inject([]) do |results, i|
|
4
|
+
results += i.security_groups
|
5
|
+
results
|
6
|
+
end
|
7
|
+
groups.uniq(&:group_id)
|
8
|
+
end
|
9
|
+
|
10
|
+
def unused_security_groups
|
11
|
+
used_group_ids = used_security_groups.map(&:group_id)
|
12
|
+
security_groups.reject {|sg| used_group_ids.include?(sg.group_id) }
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Inventory::SecurityGroup
|
2
|
+
class Summary < Inventory::Base
|
3
|
+
include Shared
|
4
|
+
|
5
|
+
def header
|
6
|
+
["Security Group", "Count"]
|
7
|
+
end
|
8
|
+
|
9
|
+
def data
|
10
|
+
[
|
11
|
+
["Total", security_groups.size],
|
12
|
+
["Used", used_security_groups.size],
|
13
|
+
["Unused", unused_security_groups.size],
|
14
|
+
]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Inventory::Shared
|
2
|
+
def instances
|
3
|
+
return @instances if @instances
|
4
|
+
|
5
|
+
@instances = []
|
6
|
+
resp = ec2.describe_instances
|
7
|
+
resp.reservations.each do |res|
|
8
|
+
@instances += res.instances
|
9
|
+
end
|
10
|
+
@instances
|
11
|
+
end
|
12
|
+
|
13
|
+
def security_groups
|
14
|
+
@security_groups ||= ec2.describe_security_groups.security_groups
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class Inventory::Vpc < Inventory::Base
|
2
|
+
def header
|
3
|
+
["Name", "Vpc ID", "CIDR", "Subnets", "Instances"]
|
4
|
+
end
|
5
|
+
|
6
|
+
def data
|
7
|
+
vpcs.map do |vpc|
|
8
|
+
subnets = subnets_for(vpc)
|
9
|
+
instances = instances_in(subnets)
|
10
|
+
|
11
|
+
[
|
12
|
+
vpc_name(vpc.vpc_id),
|
13
|
+
vpc.vpc_id,
|
14
|
+
vpc.cidr_block,
|
15
|
+
subnets.count,
|
16
|
+
instances.count
|
17
|
+
]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Pretty vpc name
|
22
|
+
# Use vpc_id as argument so other classes can use this method also
|
23
|
+
def vpc_name(vpc_id)
|
24
|
+
vpc = vpcs.find { |vpc| vpc.vpc_id == vpc_id }
|
25
|
+
tag = vpc.tags.find {|t| t.key == "Name"}
|
26
|
+
name = tag ? tag.value : "(unnamed)"
|
27
|
+
end
|
28
|
+
|
29
|
+
def vpcs
|
30
|
+
@vpcs ||= ec2.describe_vpcs.vpcs
|
31
|
+
end
|
32
|
+
|
33
|
+
def subnets_for(vpc)
|
34
|
+
ec2.describe_subnets(
|
35
|
+
filters: [
|
36
|
+
{
|
37
|
+
name: "vpc-id",
|
38
|
+
values: [
|
39
|
+
vpc.vpc_id,
|
40
|
+
],
|
41
|
+
},
|
42
|
+
]).subnets
|
43
|
+
end
|
44
|
+
|
45
|
+
def instances_in(subnets)
|
46
|
+
subnet_ids = subnets.map(&:subnet_id)
|
47
|
+
instances.select do |i|
|
48
|
+
subnet_ids.include?(i.subnet_id)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def subnets
|
53
|
+
@subnets ||= ec2.describe_subnets.subnets
|
54
|
+
end
|
55
|
+
end
|