outliers 0.0.0 → 0.0.1

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