aws-inventory 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|