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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +31 -8
- data/README.md +1 -0
- data/docs/dev/filtertable-internals.md +353 -0
- data/docs/dev/filtertable-usage.md +533 -0
- data/docs/matchers.md +36 -36
- data/docs/profiles.md +2 -2
- data/docs/resources/apache.md.erb +1 -1
- data/docs/resources/aws_elb.md.erb +144 -0
- data/docs/resources/aws_elbs.md.erb +242 -0
- data/docs/resources/aws_flow_log.md.erb +118 -0
- data/docs/resources/aws_iam_groups.md.erb +34 -1
- data/docs/resources/crontab.md.erb +10 -6
- data/docs/resources/dh_params.md.erb +71 -65
- data/docs/resources/docker_service.md.erb +1 -1
- data/docs/resources/etc_fstab.md.erb +1 -1
- data/docs/resources/firewalld.md.erb +1 -1
- data/docs/resources/http.md.erb +1 -1
- data/docs/resources/iis_app.md.erb +1 -1
- data/docs/resources/inetd_conf.md.erb +1 -1
- data/docs/resources/nginx.md.erb +1 -1
- data/docs/resources/npm.md.erb +9 -1
- data/docs/resources/os.md.erb +21 -19
- data/docs/resources/shadow.md.erb +37 -31
- data/docs/resources/x509_certificate.md.erb +2 -2
- data/examples/custom-resource/README.md +3 -0
- data/examples/custom-resource/controls/example.rb +7 -0
- data/examples/custom-resource/inspec.yml +8 -0
- data/examples/custom-resource/libraries/batsignal.rb +20 -0
- data/examples/custom-resource/libraries/gordon.rb +21 -0
- data/lib/inspec/reporters/junit.rb +1 -0
- data/lib/inspec/resource.rb +8 -0
- data/lib/inspec/version.rb +1 -1
- data/lib/resource_support/aws.rb +3 -0
- data/lib/resources/aws/aws_elb.rb +81 -0
- data/lib/resources/aws/aws_elbs.rb +78 -0
- data/lib/resources/aws/aws_flow_log.rb +102 -0
- data/lib/resources/aws/aws_iam_groups.rb +1 -2
- data/lib/resources/aws/aws_iam_users.rb +65 -47
- data/lib/resources/npm.rb +15 -2
- data/lib/resources/package.rb +1 -1
- data/lib/utils/filter.rb +243 -85
- metadata +15 -2
data/docs/resources/os.md.erb
CHANGED
@@ -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
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
39
|
+
Use the `.where` method to find properties that match a value:
|
40
40
|
|
41
|
-
describe shadow.
|
42
|
-
its('
|
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
|
-
##
|
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`
|
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`
|
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`
|
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`
|
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`
|
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`
|
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`
|
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`
|
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
|
-
###
|
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
|
-
###
|
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,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?
|
data/lib/inspec/resource.rb
CHANGED
@@ -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
|
data/lib/inspec/version.rb
CHANGED
data/lib/resource_support/aws.rb
CHANGED
@@ -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
|