aws-inventory 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +661 -0
  6. data/Guardfile +19 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +65 -0
  9. data/Rakefile +6 -0
  10. data/exe/aws-inventory +14 -0
  11. data/inventory.gemspec +37 -0
  12. data/lib/inventory.rb +29 -0
  13. data/lib/inventory/acm.rb +15 -0
  14. data/lib/inventory/aws_services.rb +53 -0
  15. data/lib/inventory/base.rb +74 -0
  16. data/lib/inventory/cfn.rb +50 -0
  17. data/lib/inventory/cli.rb +86 -0
  18. data/lib/inventory/cloudwatch.rb +38 -0
  19. data/lib/inventory/command.rb +25 -0
  20. data/lib/inventory/eb.rb +19 -0
  21. data/lib/inventory/ec2.rb +86 -0
  22. data/lib/inventory/ecs.rb +10 -0
  23. data/lib/inventory/ecs/cluster.rb +20 -0
  24. data/lib/inventory/ecs/service.rb +33 -0
  25. data/lib/inventory/elb.rb +71 -0
  26. data/lib/inventory/help.rb +9 -0
  27. data/lib/inventory/help/acm.md +1 -0
  28. data/lib/inventory/help/cfn.md +1 -0
  29. data/lib/inventory/help/cw.md +1 -0
  30. data/lib/inventory/help/eb.md +1 -0
  31. data/lib/inventory/help/ec2.md +1 -0
  32. data/lib/inventory/help/ecs.md +1 -0
  33. data/lib/inventory/help/elb.md +1 -0
  34. data/lib/inventory/help/iam.md +15 -0
  35. data/lib/inventory/help/keypair.md +1 -0
  36. data/lib/inventory/help/rds.md +5 -0
  37. data/lib/inventory/help/sg.md +1 -0
  38. data/lib/inventory/help/vpc.md +1 -0
  39. data/lib/inventory/iam.rb +13 -0
  40. data/lib/inventory/iam/group.rb +22 -0
  41. data/lib/inventory/iam/shared.rb +62 -0
  42. data/lib/inventory/iam/summary.rb +16 -0
  43. data/lib/inventory/iam/user.rb +21 -0
  44. data/lib/inventory/keypair.rb +25 -0
  45. data/lib/inventory/presenter.rb +20 -0
  46. data/lib/inventory/presenters/base.rb +5 -0
  47. data/lib/inventory/presenters/tab.rb +7 -0
  48. data/lib/inventory/presenters/table.rb +10 -0
  49. data/lib/inventory/rds.rb +11 -0
  50. data/lib/inventory/rds/port.rb +53 -0
  51. data/lib/inventory/rds/shared.rb +43 -0
  52. data/lib/inventory/rds/summary.rb +23 -0
  53. data/lib/inventory/route53.rb +29 -0
  54. data/lib/inventory/security_group.rb +11 -0
  55. data/lib/inventory/security_group/open.rb +76 -0
  56. data/lib/inventory/security_group/shared.rb +14 -0
  57. data/lib/inventory/security_group/summary.rb +17 -0
  58. data/lib/inventory/shared.rb +16 -0
  59. data/lib/inventory/version.rb +3 -0
  60. data/lib/inventory/vpc.rb +55 -0
  61. data/spec/lib/cli_spec.rb +31 -0
  62. data/spec/lib/inventory/base_spec.rb +7 -0
  63. data/spec/lib/inventory/security_group/open_spec.rb +31 -0
  64. data/spec/spec_helper.rb +24 -0
  65. metadata +308 -0
@@ -0,0 +1,16 @@
1
+ class Inventory::Iam
2
+ class Summary < Inventory::Base
3
+ include Shared
4
+
5
+ def header
6
+ ["Summary", "Count"]
7
+ end
8
+
9
+ def data
10
+ [
11
+ ["Groups", groups.size],
12
+ ["Users", users.size]
13
+ ]
14
+ end
15
+ end
16
+ end
@@ -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,5 @@
1
+ class Inventory::Presenter::Base
2
+ def initialize(data)
3
+ @data = data
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ class Inventory::Presenter::Tab < Inventory::Presenter::Base
2
+ def display
3
+ @data.each do |row|
4
+ puts row.join("\t")
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ require 'text-table'
2
+
3
+ class Inventory::Presenter::Table < Inventory::Presenter::Base
4
+ def display
5
+ table = Text::Table.new
6
+ table.head = @data.shift
7
+ table.rows = @data
8
+ puts table
9
+ end
10
+ 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,3 @@
1
+ module Inventory
2
+ VERSION = "0.2.0"
3
+ 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