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.
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