outliers 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-gemset +1 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +5 -0
  5. data/CHANGELOG.md +3 -0
  6. data/LICENSE.txt +10 -19
  7. data/README.md +175 -8
  8. data/Rakefile +7 -0
  9. data/bin/outliers +6 -0
  10. data/lib/outliers/cli/evaluate.rb +115 -0
  11. data/lib/outliers/cli/process.rb +56 -0
  12. data/lib/outliers/cli/providers.rb +29 -0
  13. data/lib/outliers/cli/resources.rb +57 -0
  14. data/lib/outliers/cli.rb +78 -0
  15. data/lib/outliers/collection.rb +124 -0
  16. data/lib/outliers/credentials.rb +28 -0
  17. data/lib/outliers/evaluation.rb +85 -0
  18. data/lib/outliers/exceptions.rb +40 -0
  19. data/lib/outliers/mixins.rb +27 -0
  20. data/lib/outliers/provider.rb +32 -0
  21. data/lib/outliers/providers/aws/base.rb +33 -0
  22. data/lib/outliers/providers/aws/cloud_formation.rb +20 -0
  23. data/lib/outliers/providers/aws/ec2.rb +20 -0
  24. data/lib/outliers/providers/aws/elb.rb +20 -0
  25. data/lib/outliers/providers/aws/iam.rb +20 -0
  26. data/lib/outliers/providers/aws/rds.rb +20 -0
  27. data/lib/outliers/providers/aws/s3.rb +20 -0
  28. data/lib/outliers/providers/aws/sqs.rb +20 -0
  29. data/lib/outliers/providers/aws.rb +9 -0
  30. data/lib/outliers/providers/github.rb +23 -0
  31. data/lib/outliers/providers.rb +19 -0
  32. data/lib/outliers/resource.rb +34 -0
  33. data/lib/outliers/resources/aws/cloud_formation/stack.rb +10 -0
  34. data/lib/outliers/resources/aws/cloud_formation/stack_collection.rb +15 -0
  35. data/lib/outliers/resources/aws/ec2/instance.rb +75 -0
  36. data/lib/outliers/resources/aws/ec2/instance_collection.rb +15 -0
  37. data/lib/outliers/resources/aws/ec2/security_group.rb +28 -0
  38. data/lib/outliers/resources/aws/ec2/security_group_collection.rb +15 -0
  39. data/lib/outliers/resources/aws/elb/load_balancer.rb +51 -0
  40. data/lib/outliers/resources/aws/elb/load_balancer_collection.rb +15 -0
  41. data/lib/outliers/resources/aws/iam/user.rb +40 -0
  42. data/lib/outliers/resources/aws/iam/user_collection.rb +15 -0
  43. data/lib/outliers/resources/aws/rds/db_instance.rb +35 -0
  44. data/lib/outliers/resources/aws/rds/db_instance_collection.rb +15 -0
  45. data/lib/outliers/resources/aws/rds/db_snapshot.rb +13 -0
  46. data/lib/outliers/resources/aws/rds/db_snapshot_collection.rb +15 -0
  47. data/lib/outliers/resources/aws/s3/bucket.rb +73 -0
  48. data/lib/outliers/resources/aws/s3/bucket_collection.rb +18 -0
  49. data/lib/outliers/resources/aws/sqs/queue.rb +13 -0
  50. data/lib/outliers/resources/aws/sqs/queue_collection.rb +15 -0
  51. data/lib/outliers/resources/aws.rb +18 -0
  52. data/lib/outliers/resources/github/repo.rb +24 -0
  53. data/lib/outliers/resources/github/repo_collection.rb +13 -0
  54. data/lib/outliers/resources/github.rb +2 -0
  55. data/lib/outliers/resources.rb +12 -0
  56. data/lib/outliers/result.rb +24 -0
  57. data/lib/outliers/run.rb +40 -0
  58. data/lib/outliers/verifications/shared.rb +31 -0
  59. data/lib/outliers/verifications.rb +1 -0
  60. data/lib/outliers/version.rb +1 -1
  61. data/lib/outliers.rb +24 -1
  62. data/outliers.gemspec +9 -5
  63. data/spec/collection_spec.rb +103 -0
  64. data/spec/credentials_spec.rb +42 -0
  65. data/spec/evaluation_spec.rb +96 -0
  66. data/spec/fixtures/credentials1.yml +5 -0
  67. data/spec/fixtures/credentials2.yml +5 -0
  68. data/spec/helpers/fixtures.rb +8 -0
  69. data/spec/mixins_spec.rb +33 -0
  70. data/spec/provider_spec.rb +35 -0
  71. data/spec/providers_spec.rb +18 -0
  72. data/spec/resource_spec.rb +19 -0
  73. data/spec/resources_spec.rb +15 -0
  74. data/spec/results_spec.rb +33 -0
  75. data/spec/run_spec.rb +56 -0
  76. data/spec/spec_helper.rb +21 -0
  77. data/spec/verifications/shared_spec.rb +35 -0
  78. metadata +145 -29
@@ -0,0 +1,75 @@
1
+ module Outliers
2
+ module Resources
3
+ module Aws
4
+ module Ec2
5
+ class Instance < Resource
6
+ def self.key
7
+ 'instance_id'
8
+ end
9
+
10
+ def self.verifications
11
+ [
12
+ { name: 'classic',
13
+ description: 'Instance is in AWS Classic (No VPC).' },
14
+ { name: 'source_dest_check',
15
+ description: 'Instance source dest check set to true.' },
16
+ { name: 'running',
17
+ description: 'Instance status is running.' },
18
+ { name: 'valid_image_id',
19
+ description: 'ami_ids=ami_id1,ami_id2 - Instances Image ID (AMI) is in given list.',
20
+ args: 'image_ids: [IMAGE_ID1, IMAGEID2]' },
21
+ { name: 'vpc',
22
+ description: 'Instance is in a VPC.' }
23
+ ]
24
+ end
25
+
26
+ def classic?
27
+ !vpc?
28
+ end
29
+
30
+ def running?
31
+ logger.debug "Verifying '#{status}' equals 'running'."
32
+ status == :running
33
+ end
34
+
35
+ def source_dest_check?
36
+ unless vpc?
37
+ logger.debug "Instance must be in a VPC to validate source_dest_check. Returning false."
38
+ return false
39
+ end
40
+ source_dest_check == true
41
+ end
42
+
43
+ def valid_image_id?(args)
44
+ image_ids = Array(args[:image_ids])
45
+
46
+ logger.debug "Verifying Image ID '#{image_id}' is one of '#{image_ids.join(', ')}'."
47
+ image_ids.include? image_id
48
+ end
49
+
50
+ def vpc?
51
+ !source.vpc_id.nil?
52
+ end
53
+
54
+ private
55
+
56
+ def tags
57
+ @tags ||= source.tags
58
+ end
59
+
60
+ def image_id
61
+ @image_id ||= source.image_id
62
+ end
63
+
64
+ def instance_type
65
+ @instance_type ||= source.instance_type
66
+ end
67
+
68
+ def source_dest_check
69
+ @source_dest_check ||= source.source_dest_check
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,15 @@
1
+ module Outliers
2
+ module Resources
3
+ module Aws
4
+ module Ec2
5
+ class InstanceCollection < Collection
6
+
7
+ def load_all
8
+ connect.instances.map {|r| resource_class.new r}
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,28 @@
1
+ module Outliers
2
+ module Resources
3
+ module Aws
4
+ module Ec2
5
+ class SecurityGroup < Resource
6
+ def self.verifications
7
+ [
8
+ { name: 'no_public_internet_ingress',
9
+ description: 'Security Group has no rules open to "0.0.0.0/0".' }
10
+ ]
11
+ end
12
+
13
+ def no_public_internet_ingress?
14
+ logger.debug "Verifying '#{id}'."
15
+ source.ip_permissions.select do |i|
16
+ if !i.egress? && (i.ip_ranges.include? "0.0.0.0/0")
17
+ logger.debug "Security Group '#{id}' is open to '#{i.ip_ranges.join(', ')}' via '#{i.protocol}'."
18
+ false
19
+ else
20
+ true
21
+ end
22
+ end.any?
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ module Outliers
2
+ module Resources
3
+ module Aws
4
+ module Ec2
5
+ class SecurityGroupCollection < Collection
6
+
7
+ def load_all
8
+ connect.security_groups.map {|r| resource_class.new r}
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,51 @@
1
+ module Outliers
2
+ module Resources
3
+ module Aws
4
+ module Elb
5
+ class LoadBalancer < Resource
6
+ def self.verifications
7
+ [
8
+ { name: 'ssl_certificates_valid',
9
+ description: 'Validates all SSL certificates associated with an ELB are valid for given number of days',
10
+ args: 'days: DAYS' }
11
+ ]
12
+ end
13
+
14
+ def ssl_certificates_valid?(args)
15
+ days = args[:days]
16
+ pass = true
17
+
18
+ logger.debug "Load Balancer '#{id}' has no certificates." unless certificates.any?
19
+
20
+ date = Time.now + (days.to_i * 86400)
21
+
22
+ logger.debug "Validating no certs expire before '#{date.to_s}'."
23
+
24
+ certificates.each do |c|
25
+ certificate = OpenSSL::X509::Certificate.new c.certificate_body
26
+ subject = certificate.subject
27
+ not_after = certificate.not_after
28
+
29
+ logger.debug "Certificate '#{subject}' expires '#{not_after}'."
30
+ result = not_after > date
31
+ logger.debug "Certificate #{result ? "valid" : "invalid"}."
32
+ pass = false unless result
33
+ end
34
+ pass
35
+ end
36
+
37
+ private
38
+
39
+ def certificates
40
+ listeners.map {|l| l.server_certificate}.reject {|s| s.nil?}
41
+ end
42
+
43
+ def listeners
44
+ @listeners ||= source.listeners
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,15 @@
1
+ module Outliers
2
+ module Resources
3
+ module Aws
4
+ module Elb
5
+ class LoadBalancerCollection < Collection
6
+
7
+ def load_all
8
+ connect.load_balancers.map {|r| resource_class.new r}
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,40 @@
1
+ module Outliers
2
+ module Resources
3
+ module Aws
4
+ module Iam
5
+ class User < Resource
6
+ def self.verifications
7
+ [
8
+ { name: 'mfa_enabled',
9
+ description: 'Verify MFA enabled for user.' },
10
+ { name: 'no_access_keys',
11
+ description: 'Verify user has no access keys.' },
12
+ { name: 'no_password_set',
13
+ description: 'Verify password not set for user.' }
14
+ ]
15
+ end
16
+
17
+ def no_access_keys?
18
+ logger.debug "#{id} has #{access_keys.count} access key(s)."
19
+ !access_keys.any?
20
+ end
21
+
22
+ def no_password_set?
23
+ !source.login_profile.exists?
24
+ end
25
+
26
+ def mfa_enabled?
27
+ source.mfa_devices.count > 0
28
+ end
29
+
30
+ private
31
+
32
+ def access_keys
33
+ @access_keys ||= source.access_keys
34
+ end
35
+
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,15 @@
1
+ module Outliers
2
+ module Resources
3
+ module Aws
4
+ module Iam
5
+ class UserCollection < Collection
6
+
7
+ def load_all
8
+ connect.users.map {|r| resource_class.new r}
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,35 @@
1
+ module Outliers
2
+ module Resources
3
+ module Aws
4
+ module Rds
5
+ class DbInstance < Resource
6
+ def self.key
7
+ 'db_instance_identifier'
8
+ end
9
+
10
+ def self.verifications
11
+ [
12
+ { name: 'backup_retention_period',
13
+ description: 'Validate the backup retention period equals given days for the db_instance.',
14
+ args: 'days: DAYS' },
15
+ { name: 'multi_az',
16
+ description: 'RDS Multi AZ set to yes.' }
17
+ ]
18
+ end
19
+
20
+ def backup_retention_period?(args)
21
+ days = args[:days]
22
+
23
+ current = source.backup_retention_period
24
+ logger.debug "Verifying '#{id}' retention period of '#{current}' equals '#{days}' days."
25
+ current.to_i == days.to_i
26
+ end
27
+
28
+ def multi_az?
29
+ source.multi_az?
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,15 @@
1
+ module Outliers
2
+ module Resources
3
+ module Aws
4
+ module Rds
5
+ class DbInstanceCollection < Collection
6
+
7
+ def load_all
8
+ connect.db_instances.map {|r| resource_class.new r}
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ module Outliers
2
+ module Resources
3
+ module Aws
4
+ module Rds
5
+ class DbSnapshot < Resource
6
+ def self.key
7
+ 'db_snapshot_identifier'
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module Outliers
2
+ module Resources
3
+ module Aws
4
+ module Rds
5
+ class DbSnapshotCollection < Collection
6
+
7
+ def load_all
8
+ connect.db_snapshots.map {|r| resource_class.new r}
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,73 @@
1
+ module Outliers
2
+ module Resources
3
+ module Aws
4
+ module S3
5
+ class Bucket < Resource
6
+
7
+ def self.verifications
8
+ [
9
+ { name: 'empty',
10
+ description: 'Bucket has no objects.' },
11
+ { name: 'no_public_objects',
12
+ description: 'Bucket has no public accessible objects.' },
13
+ { name: 'configured_as_website',
14
+ description: 'Bucket is configured as a website.' },
15
+ { name: 'not_configured_as_website',
16
+ description: 'Bucket is not configured as a website.' }
17
+ ]
18
+ end
19
+
20
+ def empty?
21
+ logger.debug "Bucket #{id} has #{count} objects."
22
+
23
+ count == 0
24
+ end
25
+
26
+ def no_public_objects?
27
+ passed = true
28
+
29
+ logger.info "Validating #{objects.count} objects in '#{id}' are private."
30
+
31
+ objects.each do |o|
32
+ logger.debug "Verifying '#{o.key}' is private."
33
+ o.acl.grants.select do |g|
34
+ grantee = Nokogiri::XML(g.grantee.to_s).children.children.children.to_s
35
+ if grantee == "http://acs.amazonaws.com/groups/global/AllUsers" || grantee == "http://acs.amazonaws.com/groups/global/AuthenticatedUsers"
36
+ logger.debug "Object '#{o.key}' in '#{id}' has public grant '#{grantee}'."
37
+ passed = false
38
+ end
39
+ end
40
+ end
41
+
42
+ logger.debug "Verification of '#{id}' #{passed ? 'passed' : 'failed'}."
43
+
44
+ passed
45
+ end
46
+
47
+ def not_configured_as_website?
48
+ !configured_as_website?
49
+ end
50
+
51
+ def configured_as_website?
52
+ !website_configuration.nil?
53
+ end
54
+
55
+ private
56
+
57
+ def website_configuration
58
+ source.website_configuration
59
+ end
60
+
61
+ def count
62
+ objects.count
63
+ end
64
+
65
+ def objects
66
+ @objects ||= source.objects
67
+ end
68
+
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,18 @@
1
+ module Outliers
2
+ module Resources
3
+ module Aws
4
+ module S3
5
+ class BucketCollection < Collection
6
+
7
+ def load_all
8
+ unless provider.credentials['region'] == 'us-east-1'
9
+ raise Exceptions::UnsupportedRegion.new "Bucket verifications must target region us-east-1."
10
+ end
11
+ connect.buckets.map {|r| resource_class.new r}
12
+ end
13
+
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ module Outliers
2
+ module Resources
3
+ module Aws
4
+ module Sqs
5
+ class Queue < Resource
6
+ def self.key
7
+ 'url'
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module Outliers
2
+ module Resources
3
+ module Aws
4
+ module Sqs
5
+ class QueueCollection < Collection
6
+
7
+ def load_all
8
+ connect.queues.map {|r| resource_class.new r}
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ require 'outliers/resources/aws/cloud_formation/stack'
2
+ require 'outliers/resources/aws/cloud_formation/stack_collection'
3
+ require 'outliers/resources/aws/ec2/security_group'
4
+ require 'outliers/resources/aws/ec2/security_group_collection'
5
+ require 'outliers/resources/aws/ec2/instance'
6
+ require 'outliers/resources/aws/ec2/instance_collection'
7
+ require 'outliers/resources/aws/elb/load_balancer'
8
+ require 'outliers/resources/aws/elb/load_balancer_collection'
9
+ require 'outliers/resources/aws/iam/user'
10
+ require 'outliers/resources/aws/iam/user_collection'
11
+ require 'outliers/resources/aws/rds/db_instance'
12
+ require 'outliers/resources/aws/rds/db_instance_collection'
13
+ require 'outliers/resources/aws/rds/db_snapshot'
14
+ require 'outliers/resources/aws/rds/db_snapshot_collection'
15
+ require 'outliers/resources/aws/s3/bucket'
16
+ require 'outliers/resources/aws/s3/bucket_collection'
17
+ require 'outliers/resources/aws/sqs/queue'
18
+ require 'outliers/resources/aws/sqs/queue_collection'
@@ -0,0 +1,24 @@
1
+ module Outliers
2
+ module Resources
3
+ module Github
4
+ class Repo < Resource
5
+ def self.verifications
6
+ [
7
+ { name: 'private',
8
+ description: 'Repo is private.' },
9
+ { name: 'public',
10
+ description: 'Repo is public.' }
11
+ ]
12
+ end
13
+
14
+ def private?
15
+ source.private
16
+ end
17
+
18
+ def public?
19
+ !source.private
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,13 @@
1
+ module Outliers
2
+ module Resources
3
+ module Github
4
+ class RepoCollection < Collection
5
+
6
+ def load_all
7
+ connect.repos.list(:type => 'all').map {|r| resource_class.new r}
8
+ end
9
+
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,2 @@
1
+ require 'outliers/resources/github/repo'
2
+ require 'outliers/resources/github/repo_collection'
@@ -0,0 +1,12 @@
1
+ require 'outliers/resources/aws'
2
+ require 'outliers/resources/github'
3
+
4
+ module Outliers
5
+ module Resources
6
+ module_function
7
+
8
+ def collections
9
+ all_the_modules.select {|m| (m.is_a? Class) && (m.to_s =~ /Collection$/)}
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,24 @@
1
+ module Outliers
2
+ class Result
3
+
4
+ attr_reader :description, :passed
5
+
6
+ def initialize(args)
7
+ @description = args[:description]
8
+ @passed = args[:passed]
9
+ end
10
+
11
+ def to_s
12
+ passed? ? 'passed' : 'failed'
13
+ end
14
+
15
+ def passed?
16
+ @passed == true
17
+ end
18
+
19
+ def failed?
20
+ !passed?
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,40 @@
1
+ module Outliers
2
+ class Run
3
+ attr_accessor :credentials, :results
4
+
5
+ def initialize
6
+ @results = []
7
+ end
8
+
9
+ def process_evaluations_in_config_folder
10
+ evaluations_path = File.join Outliers.config_path
11
+ entries = Dir.entries(evaluations_path) - ['.', '..']
12
+ entries.each do |e|
13
+ file = File.join(evaluations_path, e)
14
+ unless File.directory? file
15
+ logger.info "Processing '#{file}'."
16
+ self.instance_eval File.read(file)
17
+ end
18
+ end
19
+ end
20
+
21
+ def evaluate(name='unspecified')
22
+ yield Evaluation.new :name => name, :run => self
23
+ end
24
+
25
+ def passed
26
+ @results.select {|r| r.passed?}
27
+ end
28
+
29
+ def failed
30
+ @results.reject {|r| r.passed?}
31
+ end
32
+
33
+ private
34
+
35
+ def logger
36
+ @logger ||= Outliers.logger
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,31 @@
1
+ module Outliers
2
+ module Verifications
3
+ module Shared
4
+
5
+ def none_exist?
6
+ logger.debug 'Verifying no resources exist.'
7
+ logger.debug "Found #{all.empty? ? 'no resources' : all_by_key.join(',')}."
8
+ all.empty?
9
+ end
10
+
11
+ def equals?(args)
12
+ list = Array(args[:keys])
13
+ logger.debug "Verifying '#{list.join(',')}' equals #{all.empty? ? 'no resources' : all_by_key.join(',')}."
14
+ list == all_by_key
15
+ end
16
+
17
+ module_function
18
+
19
+ def verifications
20
+ [
21
+ { name: 'none_exist',
22
+ description: 'Verify no resources exist.' },
23
+ { name: 'equals',
24
+ description: 'Verify resources match the given list of keys.',
25
+ args: 'keys: [KEY1,KEY2]' }
26
+ ]
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1 @@
1
+ require 'outliers/verifications/shared'
@@ -1,3 +1,3 @@
1
1
  module Outliers
2
- VERSION = "0.0.0"
2
+ VERSION = "0.0.1"
3
3
  end
data/lib/outliers.rb CHANGED
@@ -1,5 +1,28 @@
1
+ require "outliers/mixins.rb"
2
+ require "outliers/verifications"
3
+
4
+ require "outliers/collection"
5
+ require "outliers/credentials"
6
+ require "outliers/exceptions"
7
+ require "outliers/evaluation"
8
+ require "outliers/provider"
9
+ require "outliers/providers"
10
+ require "outliers/resource"
11
+ require "outliers/resources"
12
+ require "outliers/result"
13
+ require "outliers/run"
14
+
1
15
  require "outliers/version"
2
16
 
3
17
  module Outliers
4
- # Your code goes here...
18
+ module_function
19
+
20
+ def logger(logger=nil)
21
+ @logger ||= logger ? logger : Logger.new(STDOUT)
22
+ end
23
+
24
+ def config_path(path=nil)
25
+ @config_path ||= path ? path : './'
26
+ end
27
+
5
28
  end