inspec 2.1.84 → 2.2.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -8
  3. data/README.md +1 -0
  4. data/docs/dev/filtertable-internals.md +353 -0
  5. data/docs/dev/filtertable-usage.md +533 -0
  6. data/docs/matchers.md +36 -36
  7. data/docs/profiles.md +2 -2
  8. data/docs/resources/apache.md.erb +1 -1
  9. data/docs/resources/aws_elb.md.erb +144 -0
  10. data/docs/resources/aws_elbs.md.erb +242 -0
  11. data/docs/resources/aws_flow_log.md.erb +118 -0
  12. data/docs/resources/aws_iam_groups.md.erb +34 -1
  13. data/docs/resources/crontab.md.erb +10 -6
  14. data/docs/resources/dh_params.md.erb +71 -65
  15. data/docs/resources/docker_service.md.erb +1 -1
  16. data/docs/resources/etc_fstab.md.erb +1 -1
  17. data/docs/resources/firewalld.md.erb +1 -1
  18. data/docs/resources/http.md.erb +1 -1
  19. data/docs/resources/iis_app.md.erb +1 -1
  20. data/docs/resources/inetd_conf.md.erb +1 -1
  21. data/docs/resources/nginx.md.erb +1 -1
  22. data/docs/resources/npm.md.erb +9 -1
  23. data/docs/resources/os.md.erb +21 -19
  24. data/docs/resources/shadow.md.erb +37 -31
  25. data/docs/resources/x509_certificate.md.erb +2 -2
  26. data/examples/custom-resource/README.md +3 -0
  27. data/examples/custom-resource/controls/example.rb +7 -0
  28. data/examples/custom-resource/inspec.yml +8 -0
  29. data/examples/custom-resource/libraries/batsignal.rb +20 -0
  30. data/examples/custom-resource/libraries/gordon.rb +21 -0
  31. data/lib/inspec/reporters/junit.rb +1 -0
  32. data/lib/inspec/resource.rb +8 -0
  33. data/lib/inspec/version.rb +1 -1
  34. data/lib/resource_support/aws.rb +3 -0
  35. data/lib/resources/aws/aws_elb.rb +81 -0
  36. data/lib/resources/aws/aws_elbs.rb +78 -0
  37. data/lib/resources/aws/aws_flow_log.rb +102 -0
  38. data/lib/resources/aws/aws_iam_groups.rb +1 -2
  39. data/lib/resources/aws/aws_iam_users.rb +65 -47
  40. data/lib/resources/npm.rb +15 -2
  41. data/lib/resources/package.rb +1 -1
  42. data/lib/utils/filter.rb +243 -85
  43. metadata +15 -2
@@ -120,22 +120,24 @@ Use `os.family` to enable more granular testing of platforms, platform names, ar
120
120
 
121
121
  For example, both of the following tests should have the same result:
122
122
 
123
- if os.family == 'debian'
124
- describe port(69) do
125
- its('processes') { should include 'in.tftpd' }
126
- end
127
- elsif os.family == 'redhat'
128
- describe port(69) do
129
- its('processes') { should include 'xinetd' }
130
- end
131
- end
132
-
133
- if os.debian?
134
- describe port(69) do
135
- its('processes') { should include 'in.tftpd' }
136
- end
137
- elsif os.redhat?
138
- describe port(69) do
139
- its('processes') { should include 'xinetd' }
140
- end
141
- end
123
+ ```ruby
124
+ if os.family == 'debian'
125
+ describe port(69) do
126
+ its('processes') { should include 'in.tftpd' }
127
+ end
128
+ elsif os.family == 'redhat'
129
+ describe port(69) do
130
+ its('processes') { should include 'xinetd' }
131
+ end
132
+ end
133
+
134
+ if os.debian?
135
+ describe port(69) do
136
+ its('processes') { should include 'in.tftpd' }
137
+ end
138
+ elsif os.redhat?
139
+ describe port(69) do
140
+ its('processes') { should include 'xinetd' }
141
+ end
142
+ end
143
+ ```
@@ -8,7 +8,7 @@ platform: linux
8
8
  Use the `shadow` InSpec audit resource to test the contents of `/etc/shadow`, which contains password details that are only readable by the `root` user. The format for `/etc/shadow` includes:
9
9
 
10
10
  * A username
11
- * The password for that user (on newer systems passwords should be stored in `/etc/shadow` )
11
+ * The hashed password for that user
12
12
  * The last time a password was changed
13
13
  * The minimum number of days a password must exist, before it may be changed
14
14
  * The maximum number of days after which a password must be changed
@@ -24,22 +24,26 @@ These entries are defined as a colon-delimited row in the file, one row per user
24
24
 
25
25
  ## Syntax
26
26
 
27
- A `shadow` resource block declares one (or more) users and associated user information to be tested:
27
+ A `shadow` resource block declares user properties to be tested:
28
28
 
29
29
  describe shadow do
30
30
  its('user') { should_not include 'forbidden_user' }
31
31
  end
32
32
 
33
- or with a single query:
33
+ Properties can be used as a single query:
34
34
 
35
35
  describe shadow.user('root') do
36
36
  its('count') { should eq 1 }
37
37
  end
38
38
 
39
- or with a filter:
39
+ Use the `.where` method to find properties that match a value:
40
40
 
41
- describe shadow.filter(min_days: '0', max_days: '99999') do
42
- its('count') { should eq 1 }
41
+ describe shadow.where { min_days == '0' } do
42
+ its ('user') { should include 'nfs' }
43
+ end
44
+
45
+ describe shadow.where { password =~ /[x|!|*]/ } do
46
+ its('count') { should eq 0 }
43
47
  end
44
48
 
45
49
  The following properties are available:
@@ -54,8 +58,6 @@ The following properties are available:
54
58
  * `expiry_date`
55
59
  * `reserved`
56
60
 
57
- Properties can be used as a single query or can be joined together with the `.filter` method.
58
-
59
61
  <br>
60
62
 
61
63
  ## Examples
@@ -77,31 +79,17 @@ The following examples show how to use this InSpec audit resource.
77
79
 
78
80
  <br>
79
81
 
80
- ## Matchers
81
-
82
- For a full list of available matchers, please visit our [matchers page](https://www.inspec.io/docs/reference/matchers/).
83
-
84
- ### count
85
-
86
- The `count` matcher tests the number of times the named user appears in `/etc/shadow`:
87
-
88
- its('count') { should eq 1 }
89
-
90
- This matcher is best used in conjunction with filters. For example:
91
-
92
- describe shadow.user('dannos') do
93
- its('count') { should eq 1 }
94
- end
82
+ ## Properties
95
83
 
96
84
  ### user
97
85
 
98
- The `user` matcher tests if the username exists `/etc/shadow`:
86
+ The `user` property tests if the username exists `/etc/shadow`:
99
87
 
100
88
  its('user') { should eq 'root' }
101
89
 
102
90
  ### password
103
91
 
104
- The `password` matcher returns the encrypted password string from the shadow file. The returned string may not be an encrypted password, but rather a `*` or similar which indicates that direct logins are not allowed.
92
+ The `password` property returns the encrypted password string from the shadow file. The returned string may not be an encrypted password, but rather a `*` or similar which indicates that direct logins are not allowed.
105
93
 
106
94
  For example:
107
95
 
@@ -109,38 +97,56 @@ For example:
109
97
 
110
98
  ### last_change
111
99
 
112
- The `last_change` matcher tests the last time a password was changed:
100
+ The `last_change` property tests the last time a password was changed:
113
101
 
114
102
  its('last_change') { should be_empty }
115
103
 
116
104
  ### min_days
117
105
 
118
- The `min_days` matcher tests the minimum number of days a password must exist, before it may be changed:
106
+ The `min_days` property tests the minimum number of days a password must exist, before it may be changed:
119
107
 
120
108
  its('min_days') { should eq 0 }
121
109
 
122
110
  ### max_days
123
111
 
124
- The `max_days` matcher tests the maximum number of days after which a password must be changed:
112
+ The `max_days` property tests the maximum number of days after which a password must be changed:
125
113
 
126
114
  its('max_days') { should eq 90 }
127
115
 
128
116
  ### warn_days
129
117
 
130
- The `warn_days` matcher tests the number of days a user is warned about an expiring password:
118
+ The `warn_days` property tests the number of days a user is warned about an expiring password:
131
119
 
132
120
  its('warn_days') { should eq 7 }
133
121
 
134
122
  ### inactive_days
135
123
 
136
- The `inactive_days` matcher tests the number of days a user must be inactive before the user account is disabled:
124
+ The `inactive_days` property tests the number of days a user must be inactive before the user account is disabled:
137
125
 
138
126
  its('inactive_days') { should be_empty }
139
127
 
140
128
  ### expiry_date
141
129
 
142
- The `expiry_date` matcher tests the number of days a user account has been disabled:
130
+ The `expiry_date` property tests the number of days a user account has been disabled:
143
131
 
144
132
  its('expiry_date') { should be_empty }
145
133
 
134
+ ### count
135
+
136
+ The `count` property tests the number of times the named property appears:
137
+
138
+ describe shadow.user('root') do
139
+ its('count') { should eq 1 }
140
+ end
141
+
142
+ This property is best used in conjunction with filters. For example:
146
143
 
144
+ describe shadow.where { password =~ /[x|!|*]/ } do
145
+ its('count') { should eq 0 }
146
+ end
147
+
148
+ <br>
149
+
150
+ ## Matchers
151
+
152
+ For a full list of available matchers, please visit our [matchers page](https://www.inspec.io/docs/reference/matchers/).
@@ -92,7 +92,7 @@ sign the certificate.
92
92
  end
93
93
 
94
94
 
95
- ### validity_in_days (Float)
95
+ ### validity\_in\_days (Float)
96
96
 
97
97
  The `validity_in_days` property can be used to check that certificates are not in
98
98
  danger of expiring soon.
@@ -101,7 +101,7 @@ danger of expiring soon.
101
101
  its('validity_in_days') { should be > 30 }
102
102
  end
103
103
 
104
- ### not_before and not_after (Time)
104
+ ### not\_before and not\_after (Time)
105
105
 
106
106
  The `not_before` and `not_after` properties expose the start and end dates of certificate
107
107
  validity. They are exposed as ruby Time class so that date arithmetic can be easily performed.
@@ -0,0 +1,3 @@
1
+ # Example Custom Resource Profile
2
+
3
+ This example shows the implementation of an InSpec profile with custom resources.
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ describe gordon do
4
+ its('crime_rate') { should be < 5 }
5
+ it { should have_a_fabulous_mustache }
6
+ end
7
+
@@ -0,0 +1,8 @@
1
+ name: custom_resource_example
2
+ title: InSpec Profile
3
+ maintainer: The Authors
4
+ copyright: The Authors
5
+ copyright_email: you@example.com
6
+ license: Apache-2.0
7
+ summary: An InSpec Compliance Profile
8
+ version: 0.1.0
@@ -0,0 +1,20 @@
1
+ class Batsignal < Inspec.resource(1)
2
+ name 'batsignal'
3
+
4
+ example "
5
+ describe batsignal do
6
+ its('number_of_sightings)') { should eq '4' }
7
+ end
8
+ "
9
+
10
+ def number_of_sightings
11
+ local_command_call
12
+ end
13
+
14
+ private
15
+
16
+ def local_command_call
17
+ # call out to a core resource
18
+ inspec.command('echo 4').stdout.to_i
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ class Gordon < Inspec.resource(1)
2
+ name 'gordon'
3
+
4
+ example "
5
+ describe gordon do
6
+ its('crime_rate') { should be < 2 }
7
+ it { should have_a_fabulous_mustache }
8
+ end
9
+ "
10
+
11
+ def crime_rate
12
+ # call out ot another custom resource
13
+ inspec.batsignal.number_of_sightings
14
+ end
15
+
16
+ def has_a_fabulous_mustache?
17
+ # always true
18
+ true
19
+ end
20
+ end
21
+
@@ -27,6 +27,7 @@ module Inspec::Reporters
27
27
  profile_xml.add_attribute('name', profile[:name])
28
28
  profile_xml.add_attribute('tests', count_profile_tests(profile))
29
29
  profile_xml.add_attribute('failed', count_profile_failed_tests(profile))
30
+ profile_xml.add_attribute('failures', count_profile_failed_tests(profile))
30
31
 
31
32
  profile[:controls].each do |control|
32
33
  next if control[:results].nil?
@@ -50,8 +50,16 @@ module Inspec
50
50
  define_method id.to_sym do |*args|
51
51
  r.new(backend, id.to_s, *args)
52
52
  end
53
+
54
+ # confirm backend custom resources have access to other custom resources
55
+ next if backend.respond_to?(id)
56
+ backend.class.send(:define_method, id.to_sym) do |*args|
57
+ r.new(backend, id.to_s, *args)
58
+ end
53
59
  end
54
60
 
61
+ # attach backend so we have access to all resources and
62
+ # the train connection object
55
63
  define_method :inspec do
56
64
  backend
57
65
  end
@@ -4,5 +4,5 @@
4
4
  # author: Christoph Hartmann
5
5
 
6
6
  module Inspec
7
- VERSION = '2.1.84'
7
+ VERSION = '2.2.10'
8
8
  end
@@ -19,7 +19,10 @@ require 'resources/aws/aws_cloudwatch_log_metric_filter'
19
19
  require 'resources/aws/aws_config_delivery_channel'
20
20
  require 'resources/aws/aws_config_recorder'
21
21
  require 'resources/aws/aws_ec2_instance'
22
+ require 'resources/aws/aws_flow_log'
22
23
  require 'resources/aws/aws_ec2_instances'
24
+ require 'resources/aws/aws_elb'
25
+ require 'resources/aws/aws_elbs'
23
26
  require 'resources/aws/aws_iam_access_key'
24
27
  require 'resources/aws/aws_iam_access_keys'
25
28
  require 'resources/aws/aws_iam_group'
@@ -0,0 +1,81 @@
1
+ class AwsElb < Inspec.resource(1)
2
+ name 'aws_elb'
3
+ desc 'Verifies settings for AWS Elastic Load Balancer'
4
+ example "
5
+ describe aws_elb('myelb') do
6
+ it { should exist }
7
+ end
8
+ "
9
+ supports platform: 'aws'
10
+
11
+ include AwsSingularResourceMixin
12
+ attr_reader :availability_zones, :dns_name, :elb_name, :external_ports,
13
+ :instance_ids, :internal_ports, :security_group_ids,
14
+ :subnet_ids, :vpc_id
15
+
16
+ def to_s
17
+ "AWS ELB #{elb_name}"
18
+ end
19
+
20
+ private
21
+
22
+ def validate_params(raw_params)
23
+ validated_params = check_resource_param_names(
24
+ raw_params: raw_params,
25
+ allowed_params: [:elb_name],
26
+ allowed_scalar_name: :elb_name,
27
+ allowed_scalar_type: String,
28
+ )
29
+
30
+ if validated_params.empty?
31
+ raise ArgumentError, 'You must provide a elb_name to aws_elb.'
32
+ end
33
+
34
+ validated_params
35
+ end
36
+
37
+ def fetch_from_api
38
+ backend = BackendFactory.create(inspec_runner)
39
+ begin
40
+ lbs = backend.describe_load_balancers(load_balancer_names: [elb_name]).load_balancer_descriptions
41
+ @exists = true
42
+ # Load balancer names are uniq; we will either have 0 or 1 result
43
+ unpack_describe_elbs_response(lbs.first)
44
+ rescue Aws::ElasticLoadBalancing::Errors::LoadBalancerNotFound
45
+ @exists = false
46
+ populate_as_missing
47
+ end
48
+ end
49
+
50
+ def unpack_describe_elbs_response(lb_struct)
51
+ @availability_zones = lb_struct.availability_zones
52
+ @dns_name = lb_struct.dns_name
53
+ @external_ports = lb_struct.listener_descriptions.map { |ld| ld.listener.load_balancer_port }
54
+ @instance_ids = lb_struct.instances.map(&:instance_id)
55
+ @internal_ports = lb_struct.listener_descriptions.map { |ld| ld.listener.instance_port }
56
+ @elb_name = lb_struct.load_balancer_name
57
+ @security_group_ids = lb_struct.security_groups
58
+ @subnet_ids = lb_struct.subnets
59
+ @vpc_id = lb_struct.vpc_id
60
+ end
61
+
62
+ def populate_as_missing
63
+ @availability_zones = []
64
+ @external_ports = []
65
+ @instance_ids = []
66
+ @internal_ports = []
67
+ @security_group_ids = []
68
+ @subnet_ids = []
69
+ end
70
+
71
+ class Backend
72
+ class AwsClientApi < AwsBackendBase
73
+ BackendFactory.set_default_backend(self)
74
+ self.aws_client_class = Aws::ElasticLoadBalancing::Client
75
+
76
+ def describe_load_balancers(query = {})
77
+ aws_service_client.describe_load_balancers(query)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,78 @@
1
+ class AwsElbs < Inspec.resource(1)
2
+ name 'aws_elbs'
3
+ desc 'Verifies settings for AWS ELBs (classic Elastic Load Balancers) in bulk'
4
+ example '
5
+ describe aws_elbs do
6
+ it { should exist }
7
+ end
8
+ '
9
+ supports platform: 'aws'
10
+
11
+ include AwsPluralResourceMixin
12
+ def validate_params(resource_params)
13
+ unless resource_params.empty?
14
+ raise ArgumentError, 'aws_elbs does not accept resource parameters.'
15
+ end
16
+ resource_params
17
+ end
18
+
19
+ # Underlying FilterTable implementation.
20
+ filter = FilterTable.create
21
+ filter.add_accessor(:entries)
22
+ .add_accessor(:where)
23
+ .add(:exists?) { |table| !table.params.empty? }
24
+ .add(:count) { |table| table.params.count }
25
+ .add(:availability_zones, field: :availability_zones, style: :simple)
26
+ .add(:dns_names, field: :dns_name)
27
+ .add(:external_ports, field: :external_ports, style: :simple)
28
+ .add(:instance_ids, field: :instance_ids, style: :simple)
29
+ .add(:internal_ports, field: :internal_ports, style: :simple)
30
+ .add(:elb_names, field: :elb_name)
31
+ .add(:security_group_ids, field: :security_group_ids, style: :simple)
32
+ .add(:subnet_ids, field: :subnet_ids, style: :simple)
33
+ .add(:vpc_ids, field: :vpc_id, style: :simple)
34
+ filter.connect(self, :table)
35
+
36
+ def to_s
37
+ 'AWS ELBs'
38
+ end
39
+
40
+ def fetch_from_api
41
+ backend = BackendFactory.create(inspec_runner)
42
+ @table = []
43
+ pagination_opts = {}
44
+ loop do
45
+ api_result = backend.describe_load_balancers(pagination_opts)
46
+ @table += unpack_describe_elbs_response(api_result.load_balancer_descriptions)
47
+ break unless api_result.next_marker
48
+ pagination_opts = { marker: api_result.next_marker }
49
+ end
50
+ end
51
+
52
+ def unpack_describe_elbs_response(load_balancers)
53
+ load_balancers.map do |lb_struct|
54
+ {
55
+ availability_zones: lb_struct.availability_zones,
56
+ dns_name: lb_struct.dns_name,
57
+ external_ports: lb_struct.listener_descriptions.map { |ld| ld.listener.load_balancer_port },
58
+ instance_ids: lb_struct.instances.map(&:instance_id),
59
+ internal_ports: lb_struct.listener_descriptions.map { |ld| ld.listener.instance_port },
60
+ elb_name: lb_struct.load_balancer_name,
61
+ security_group_ids: lb_struct.security_groups,
62
+ subnet_ids: lb_struct.subnets,
63
+ vpc_id: lb_struct.vpc_id,
64
+ }
65
+ end
66
+ end
67
+
68
+ class Backend
69
+ class AwsClientApi < AwsBackendBase
70
+ BackendFactory.set_default_backend(self)
71
+ self.aws_client_class = Aws::ElasticLoadBalancing::Client
72
+
73
+ def describe_load_balancers(query = {})
74
+ aws_service_client.describe_load_balancers(query)
75
+ end
76
+ end
77
+ end
78
+ end