inspec 2.1.84 → 2.2.10

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