inspec 2.1.84 → 2.2.10
Sign up to get free protection for your applications and to get access to all the features.
- 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
|