inspec 1.34.1 → 1.35.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -10
- data/docs/dsl_inspec.md +10 -0
- data/docs/resources/aide_conf.md.erb +81 -0
- data/docs/resources/etc_hosts.md.erb +62 -0
- data/docs/resources/xml.md.erb +75 -0
- data/examples/profile-sensitive/README.md +29 -0
- data/examples/profile-sensitive/controls/sensitive-failures.rb +9 -0
- data/examples/profile-sensitive/controls/sensitive.rb +9 -0
- data/examples/profile-sensitive/inspec.yml +8 -0
- data/lib/inspec/resource.rb +3 -0
- data/lib/inspec/rspec_json_formatter.rb +7 -2
- data/lib/inspec/version.rb +1 -1
- data/lib/resources/aide_conf.rb +162 -0
- data/lib/resources/auditd_rules.rb +1 -1
- data/lib/resources/etc_hosts.rb +81 -0
- data/lib/resources/groups.rb +0 -1
- data/lib/resources/http.rb +1 -0
- data/lib/resources/pip.rb +28 -20
- data/lib/resources/port.rb +86 -2
- data/lib/resources/xml.rb +27 -0
- metadata +12 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 75f7ef6bcd93aae1ea6bda13e5d50675fa234afa
|
4
|
+
data.tar.gz: a43bc69d1420af04b82860164873a42f5fc0ba4b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f7bffcdfa35005e4805026dd0028031713f1ecb30b7fae10d811c79bde5840599df4e35dc9631ffea18d637c768abed92dbe05392d1a7c77efaf1332629e445
|
7
|
+
data.tar.gz: b24fc841af39b05e38ee9537e76da3d1b68bcdd07349e59af1c6b140a5298cec578fef54e1167d7b48b348829d02a59054c785a4fb265fd7cce9767868d3f1b5
|
data/CHANGELOG.md
CHANGED
@@ -1,24 +1,40 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
-
<!-- latest_release 1.
|
4
|
-
## [v1.
|
3
|
+
<!-- latest_release 1.34.10 -->
|
4
|
+
## [v1.34.10](https://github.com/chef/inspec/tree/v1.34.10) (2017-08-31)
|
5
5
|
|
6
|
-
####
|
7
|
-
-
|
6
|
+
#### New Resources
|
7
|
+
- etc_hosts resource: test the contents of the /etc/hosts file [#2065](https://github.com/chef/inspec/pull/2065) ([dromazmj](https://github.com/dromazmj))
|
8
8
|
<!-- latest_release -->
|
9
9
|
|
10
|
-
<!-- release_rollup since=1.
|
11
|
-
### Changes since 1.
|
10
|
+
<!-- release_rollup since=1.34.1 -->
|
11
|
+
### Changes since 1.34.1 release
|
12
12
|
|
13
13
|
#### Enhancements
|
14
|
-
-
|
14
|
+
- port resource: support ss instead of netstat [#2110](https://github.com/chef/inspec/pull/2110) ([adamleff](https://github.com/adamleff)) <!-- 1.34.8 -->
|
15
|
+
- pip resource: support non-default pip locations, such as virtualenvs [#2097](https://github.com/chef/inspec/pull/2097) ([tonybaloney](https://github.com/tonybaloney)) <!-- 1.34.7 -->
|
16
|
+
|
17
|
+
#### Bug Fixes
|
18
|
+
- Support mixed-case group entries [#2101](https://github.com/chef/inspec/pull/2101) ([adamleff](https://github.com/adamleff)) <!-- 1.34.6 -->
|
19
|
+
- http resource: prevent repeat calls during a control with multiple tests [#2108](https://github.com/chef/inspec/pull/2108) ([mivok](https://github.com/mivok)) <!-- 1.34.5 -->
|
20
|
+
- auditd_rules resource: fix get_keys error on lines that have no keys [#2103](https://github.com/chef/inspec/pull/2103) ([jburns12](https://github.com/jburns12)) <!-- 1.34.4 -->
|
15
21
|
|
16
22
|
#### Merged Pull Requests
|
17
|
-
- Add
|
18
|
-
|
23
|
+
- Add sensitive flag to resources to restrict logging output [#2017](https://github.com/chef/inspec/pull/2017) ([arothian](https://github.com/arothian)) <!-- 1.34.3 -->
|
24
|
+
|
25
|
+
#### New Resources
|
26
|
+
- etc_hosts resource: test the contents of the /etc/hosts file [#2065](https://github.com/chef/inspec/pull/2065) ([dromazmj](https://github.com/dromazmj)) <!-- 1.34.10 -->
|
27
|
+
- Add support for XML files [#2107](https://github.com/chef/inspec/pull/2107) ([jonathanmorley](https://github.com/jonathanmorley)) <!-- 1.34.9 -->
|
28
|
+
- aide_conf resource: test configuration of the AIDE file integrity tool [#2063](https://github.com/chef/inspec/pull/2063) ([jburns12](https://github.com/jburns12)) <!-- 1.34.2 -->
|
19
29
|
<!-- release_rollup -->
|
20
30
|
|
21
31
|
<!-- latest_stable_release -->
|
32
|
+
## [v1.34.1](https://github.com/chef/inspec/tree/v1.34.1) (2017-08-24)
|
33
|
+
|
34
|
+
#### Enhancements
|
35
|
+
- Refine the profile/test summary output of the CLI formatter [#2094](https://github.com/chef/inspec/pull/2094) ([adamleff](https://github.com/adamleff))
|
36
|
+
<!-- latest_stable_release -->
|
37
|
+
|
22
38
|
## [v1.33.12](https://github.com/chef/inspec/tree/v1.33.12) (2017-08-18)
|
23
39
|
|
24
40
|
#### Bug Fixes
|
@@ -34,7 +50,6 @@
|
|
34
50
|
#### Merged Pull Requests
|
35
51
|
- add functional tests for inspec check [#2077](https://github.com/chef/inspec/pull/2077) ([chris-rock](https://github.com/chris-rock))
|
36
52
|
- Move bug fixes in CHANGELOG to correct header [#2089](https://github.com/chef/inspec/pull/2089) ([adamleff](https://github.com/adamleff))
|
37
|
-
<!-- latest_stable_release -->
|
38
53
|
|
39
54
|
## [v1.33.1](https://github.com/chef/inspec/tree/v1.33.1) (2017-08-10)
|
40
55
|
|
data/docs/dsl_inspec.md
CHANGED
@@ -71,6 +71,16 @@ describe.one do
|
|
71
71
|
end
|
72
72
|
```
|
73
73
|
|
74
|
+
#### Sensitive resources
|
75
|
+
|
76
|
+
In some scenarios, you may be writing checks involving resources with sensitive content (e.g. a file resource). In the case of failures, it may be desired to suppress output. This can be done by adding the `:sensitive` flag to the resource definition
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
describe file('/tmp/mysecretfile'), :sensitive do
|
80
|
+
its('content') { should contain 'secret_info' }
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
74
84
|
## Examples
|
75
85
|
|
76
86
|
The following examples show simple compliance tests using a single `control` block.
|
@@ -0,0 +1,81 @@
|
|
1
|
+
---
|
2
|
+
title: About the aide_conf Resource
|
3
|
+
---
|
4
|
+
|
5
|
+
# aide_conf
|
6
|
+
|
7
|
+
Use the `aide_conf` InSpec audit resource to test the rules established for the file integrity tool AIDE. Controlled by the aide.conf file typically at /etc/aide.conf.
|
8
|
+
|
9
|
+
## Syntax
|
10
|
+
|
11
|
+
An `aide_conf` resource block can be used to determine if the selection lines contain one (or more) directories whose files should be added to the aide database:
|
12
|
+
|
13
|
+
describe aide_conf('path') do
|
14
|
+
its('selection_lines') { should include '/sbin' }
|
15
|
+
end
|
16
|
+
|
17
|
+
where
|
18
|
+
|
19
|
+
* `'selection_lines'` refers to all selection lines found in the aide.conf file
|
20
|
+
* `('path')` is the non-default path to the `aide.conf` file (optional)
|
21
|
+
* `should include 'value'` is the value that is expected
|
22
|
+
|
23
|
+
Use the where clause to match a selection_line to one rule or a particular set of rules found in the aide.conf file:
|
24
|
+
|
25
|
+
describe aide_conf.where { selection_line == '/bin' } do
|
26
|
+
its('rules.flatten') { should include 'r' }
|
27
|
+
end
|
28
|
+
|
29
|
+
describe aide_conf.where { selection_line == '/sbin' } do
|
30
|
+
its('rules') { should include ['p', 'i', 'l', 'n', 'u', 'g', 'sha512'] }
|
31
|
+
end
|
32
|
+
|
33
|
+
## Matchers
|
34
|
+
|
35
|
+
This InSpec audit resource has the following matchers:
|
36
|
+
|
37
|
+
### be
|
38
|
+
|
39
|
+
<%= partial "/shared/matcher_be" %>
|
40
|
+
|
41
|
+
### cmp
|
42
|
+
|
43
|
+
<%= partial "/shared/matcher_cmp" %>
|
44
|
+
|
45
|
+
### eq
|
46
|
+
|
47
|
+
<%= partial "/shared/matcher_eq" %>
|
48
|
+
|
49
|
+
### include
|
50
|
+
|
51
|
+
<%= partial "/shared/matcher_include" %>
|
52
|
+
|
53
|
+
### all_have_rule
|
54
|
+
|
55
|
+
The usage of all_have_rule will return whether or not all selection lines in audit.conf contain a particular rule:
|
56
|
+
|
57
|
+
describe aide_conf.all_have_rule('sha512') do
|
58
|
+
it { should eq true }
|
59
|
+
end
|
60
|
+
|
61
|
+
## Examples
|
62
|
+
|
63
|
+
The following examples show how to use this InSpec audit resource.
|
64
|
+
|
65
|
+
### Test if all selection lines contain the xattr rule
|
66
|
+
|
67
|
+
describe aide_conf.all_have_rule('xattr') do
|
68
|
+
it { should eq true }
|
69
|
+
end
|
70
|
+
|
71
|
+
### Test whether selection line for /bin contains a particular rule
|
72
|
+
|
73
|
+
describe aide_conf.where { selection_line == '/bin' } do
|
74
|
+
its('rules.flatten') { should include 'r' }
|
75
|
+
end
|
76
|
+
|
77
|
+
### Test whether selection line for /sbin consists of a particular set of rules
|
78
|
+
|
79
|
+
describe aide_conf.where { selection_line == '/sbin' } do
|
80
|
+
its('rules') { should include ['r', 'sha512'] }
|
81
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
---
|
2
|
+
title: About the etc_hosts Resource
|
3
|
+
---
|
4
|
+
|
5
|
+
# etc_hosts
|
6
|
+
|
7
|
+
Use the `etc_hosts` InSpec audit resource to test rules set to match IP addresses with hostnames.
|
8
|
+
## Syntax
|
9
|
+
|
10
|
+
An etc/hosts rule specifies an IP address and what its hostname is along with optional aliases it can have.
|
11
|
+
|
12
|
+
## Syntax
|
13
|
+
|
14
|
+
Use the where clause to match a property to one or more rules in the hosts file.
|
15
|
+
|
16
|
+
describe etc_hosts.where { ip_address == 'value' } do
|
17
|
+
its('primary_name') { should cmp 'hostname' }
|
18
|
+
its('all_host_names') { should cmp 'list' }
|
19
|
+
end
|
20
|
+
|
21
|
+
Use the optional constructor parameter to give an alternative path to hosts file
|
22
|
+
|
23
|
+
describe etc_hosts('path/to/hosts').where { ip_address == 'value' } do
|
24
|
+
its('primary_name') { should cmp 'hostname' }
|
25
|
+
its('all_host_names') { should cmp 'list' }
|
26
|
+
end
|
27
|
+
|
28
|
+
where
|
29
|
+
|
30
|
+
* `ip_address` is the ip address of the hostname in either ipv4 or ipv6 format.
|
31
|
+
* `primary_name` is the name associated with the ip address.
|
32
|
+
* `all_host_names` is a list including the primary_name as the first entry followed by any aliase names the host has.
|
33
|
+
|
34
|
+
## Supported Properties
|
35
|
+
|
36
|
+
'ip_address', 'primary_name', 'all_host_names'
|
37
|
+
|
38
|
+
## Property Examples and Return Types
|
39
|
+
|
40
|
+
### ip_address
|
41
|
+
|
42
|
+
`ip_address` returns a string array of ip addresses specified in the etc/hosts file.
|
43
|
+
|
44
|
+
describe etc_hosts.where { primary_name == 'localhost' } do
|
45
|
+
its('ip_address') { should cmp '127.0.1.154' }
|
46
|
+
end
|
47
|
+
|
48
|
+
### primary_name
|
49
|
+
|
50
|
+
`primary_name` returns a string array of primary_names specified in the etc/hosts file.
|
51
|
+
|
52
|
+
describe etc_hosts.where { ip_address == '::1' } do
|
53
|
+
its('primary_name') { should cmp 'localhost' }
|
54
|
+
end
|
55
|
+
|
56
|
+
### all_host_names
|
57
|
+
|
58
|
+
`all_host_names` returns a two dimensional string array where each entry has the primary_name first followed by any aliases.
|
59
|
+
|
60
|
+
describe etc_hosts.where { ip_address == '127.0.1.154' } do
|
61
|
+
its('all_host_names') { should eq [['localhost', 'localhost.localdomain', 'localhost4', 'localhost4.localdomain4'], ['localhost', 'localhost.localdomain', 'localhost6', 'localhost6.localdomain6']] }
|
62
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
---
|
2
|
+
title: About the xml Resource
|
3
|
+
---
|
4
|
+
|
5
|
+
# xml
|
6
|
+
|
7
|
+
Use the `xml` InSpec audit resource to test data in an XML file.
|
8
|
+
|
9
|
+
## Syntax
|
10
|
+
|
11
|
+
An `xml` resource block declares the data to be tested. Assume the following XML file:
|
12
|
+
|
13
|
+
<root>
|
14
|
+
<name>hello</name>
|
15
|
+
<meta>
|
16
|
+
<creator>John Doe</creator>
|
17
|
+
</meta>
|
18
|
+
<array>
|
19
|
+
<element>one</element>
|
20
|
+
<element>two</element>
|
21
|
+
</array>
|
22
|
+
</root>
|
23
|
+
|
24
|
+
This file can be queried using:
|
25
|
+
|
26
|
+
describe xml('/path/to/name.xml') do
|
27
|
+
its('root/name') { should eq ['hello'] }
|
28
|
+
its('root/meta/creator') { should eq ['John Doe'] }
|
29
|
+
its('root/array[2]/element]) { should eq ['two'] }
|
30
|
+
end
|
31
|
+
|
32
|
+
where
|
33
|
+
|
34
|
+
* `root/name` is an XPath expression
|
35
|
+
* `should eq ['foo']` tests a value of `root/name` as read from an XML file versus the value declared in the test
|
36
|
+
|
37
|
+
## Matchers
|
38
|
+
|
39
|
+
This InSpec audit resource has the following matchers:
|
40
|
+
|
41
|
+
### be
|
42
|
+
|
43
|
+
<%= partial "/shared/matcher_be" %>
|
44
|
+
|
45
|
+
### cmp
|
46
|
+
|
47
|
+
<%= partial "/shared/matcher_cmp" %>
|
48
|
+
|
49
|
+
### eq
|
50
|
+
|
51
|
+
<%= partial "/shared/matcher_eq" %>
|
52
|
+
|
53
|
+
### include
|
54
|
+
|
55
|
+
<%= partial "/shared/matcher_include" %>
|
56
|
+
|
57
|
+
### match
|
58
|
+
|
59
|
+
<%= partial "/shared/matcher_match" %>
|
60
|
+
|
61
|
+
### name
|
62
|
+
|
63
|
+
The `name` matcher tests the value of `name` as read from a JSON file versus the value declared in the test:
|
64
|
+
|
65
|
+
its('name') { should eq 'foo' }
|
66
|
+
|
67
|
+
## Examples
|
68
|
+
|
69
|
+
The following examples show how to use this InSpec audit resource.
|
70
|
+
|
71
|
+
### Test an AppPool's presence in an applicationHost.config file
|
72
|
+
|
73
|
+
describe xml('applicationHost.config') do
|
74
|
+
its('configuration/system.applicationHost/applicationPools/add@name') { should contain('my_pool') }
|
75
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Example InSpec Profile with Sensitive failures
|
2
|
+
|
3
|
+
This profile demostrates resources flagged as sensitive
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
```
|
8
|
+
$ inspec exec examples/profile-sensitive
|
9
|
+
....
|
10
|
+
|
11
|
+
bob should
|
12
|
+
∅ eq "billy"
|
13
|
+
|
14
|
+
expected: "billy"
|
15
|
+
got: "bob"
|
16
|
+
|
17
|
+
(compared using ==)
|
18
|
+
|
19
|
+
sensitivepassword should
|
20
|
+
∅ eq "secret"
|
21
|
+
*** sensitive output suppressed ***
|
22
|
+
bob should
|
23
|
+
✔ eq "bob"
|
24
|
+
sensitivepassword should
|
25
|
+
✔ eq "sensitivepassword"
|
26
|
+
|
27
|
+
Test Summary: 2 successful, 2 failures, 0 skipped
|
28
|
+
|
29
|
+
```
|
data/lib/inspec/resource.rb
CHANGED
@@ -72,6 +72,7 @@ module Inspec
|
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
|
+
require 'resources/aide_conf'
|
75
76
|
require 'resources/apache'
|
76
77
|
require 'resources/apache_conf'
|
77
78
|
require 'resources/apt'
|
@@ -89,6 +90,7 @@ require 'resources/docker'
|
|
89
90
|
require 'resources/docker_image'
|
90
91
|
require 'resources/docker_container'
|
91
92
|
require 'resources/etc_group'
|
93
|
+
require 'resources/etc_hosts'
|
92
94
|
require 'resources/file'
|
93
95
|
require 'resources/gem'
|
94
96
|
require 'resources/groups'
|
@@ -157,3 +159,4 @@ require 'resources/json'
|
|
157
159
|
require 'resources/yaml'
|
158
160
|
require 'resources/csv'
|
159
161
|
require 'resources/ini'
|
162
|
+
require 'resources/xml'
|
@@ -52,7 +52,12 @@ class InspecRspecMiniJson < RSpec::Core::Formatters::JsonFormatter
|
|
52
52
|
format_example(example).tap do |hash|
|
53
53
|
e = example.exception
|
54
54
|
next unless e
|
55
|
-
|
55
|
+
|
56
|
+
if example.metadata[:sensitive]
|
57
|
+
hash[:message] = '*** sensitive output suppressed ***'
|
58
|
+
else
|
59
|
+
hash[:message] = exception_message(e)
|
60
|
+
end
|
56
61
|
|
57
62
|
next if e.is_a? RSpec::Expectations::ExpectationNotMetError
|
58
63
|
hash[:exception] = e.class.name
|
@@ -341,7 +346,7 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
|
|
341
346
|
# This method is called through the RSpec Formatter interface for every
|
342
347
|
# example found in the test suite.
|
343
348
|
#
|
344
|
-
# Within #format_example we are getting
|
349
|
+
# Within #format_example we are getting an example and:
|
345
350
|
# * if this is an example, within a control, within a profile then we want
|
346
351
|
# to display the profile header, display the control, and then display
|
347
352
|
# the example.
|
data/lib/inspec/version.rb
CHANGED
@@ -0,0 +1,162 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# author: Jen Burns, burnsjennifere@gmail.com
|
3
|
+
|
4
|
+
require 'utils/filter'
|
5
|
+
require 'utils/parser'
|
6
|
+
|
7
|
+
# rubocop:disable Metrics/ClassLength
|
8
|
+
module Inspec::Resources
|
9
|
+
class AideConf < Inspec.resource(1)
|
10
|
+
name 'aide_conf'
|
11
|
+
desc 'Use the aide_conf InSpec audit resource to test the rules established for
|
12
|
+
the file integrity tool AIDE. Controlled by the aide.conf file typically at /etc/aide.conf.'
|
13
|
+
example "
|
14
|
+
describe aide_conf do
|
15
|
+
its('selection_lines') { should include '/sbin' }
|
16
|
+
end
|
17
|
+
|
18
|
+
describe aide_conf.where { selection_line == '/bin' } do
|
19
|
+
its('rules.flatten') { should include 'r' }
|
20
|
+
end
|
21
|
+
|
22
|
+
describe aide_conf.all_have_rule('sha512') do
|
23
|
+
it { should eq true }
|
24
|
+
end
|
25
|
+
"
|
26
|
+
|
27
|
+
attr_reader :params
|
28
|
+
|
29
|
+
include CommentParser
|
30
|
+
|
31
|
+
def initialize(aide_conf_path = nil)
|
32
|
+
return skip_resource 'The `aide_conf` resource is not supported on your OS.' unless inspec.os.linux?
|
33
|
+
@conf_path = aide_conf_path || '/etc/aide.conf'
|
34
|
+
@content = nil
|
35
|
+
@rules = nil
|
36
|
+
read_content
|
37
|
+
end
|
38
|
+
|
39
|
+
def all_have_rule(rule)
|
40
|
+
# Case when file didn't exist or perms didn't allow an open
|
41
|
+
return false if @content.nil?
|
42
|
+
|
43
|
+
lines = @params.reject { |line| line['rules'].include? rule }
|
44
|
+
lines.empty?
|
45
|
+
end
|
46
|
+
|
47
|
+
filter = FilterTable.create
|
48
|
+
filter.add_accessor(:where)
|
49
|
+
.add_accessor(:entries)
|
50
|
+
.add(:selection_lines, field: 'selection_line')
|
51
|
+
.add(:rules, field: 'rules')
|
52
|
+
|
53
|
+
filter.connect(self, :params)
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def read_content
|
58
|
+
return @content unless @content.nil?
|
59
|
+
@rules = {}
|
60
|
+
|
61
|
+
file = inspec.file(@conf_path)
|
62
|
+
if !file.file?
|
63
|
+
return skip_resource "Can't find file \"#{@conf_path}\""
|
64
|
+
end
|
65
|
+
raw_conf = file.content
|
66
|
+
if raw_conf.nil?
|
67
|
+
return skip_resource "File can't be opened or is empty \"#{@conf_path}\""
|
68
|
+
end
|
69
|
+
if raw_conf.empty? && !file.empty?
|
70
|
+
return skip_resource "Can't read file \"#{@conf_path}\""
|
71
|
+
end
|
72
|
+
|
73
|
+
# If there is a file and it contains content, continue
|
74
|
+
@content = filter_comments(inspec.file(@conf_path).content.lines)
|
75
|
+
@params = parse_conf(@content)
|
76
|
+
end
|
77
|
+
|
78
|
+
def filter_comments(data)
|
79
|
+
content = []
|
80
|
+
data.each do |line|
|
81
|
+
content_line, = parse_comment_line(line, comment_char: '#', standalone_comments: false)
|
82
|
+
content.push(content_line)
|
83
|
+
end
|
84
|
+
content
|
85
|
+
end
|
86
|
+
|
87
|
+
def parse_conf(content)
|
88
|
+
params = []
|
89
|
+
content.each do |line|
|
90
|
+
param = parse_line(line)
|
91
|
+
if !param['selection_line'].nil?
|
92
|
+
params.push(param)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
params
|
96
|
+
end
|
97
|
+
|
98
|
+
def parse_line(line)
|
99
|
+
line_and_rules = {}
|
100
|
+
# Case when line is a rule line
|
101
|
+
if line.include?(' = ')
|
102
|
+
parse_rule_line(line)
|
103
|
+
# Case when line is a selection line
|
104
|
+
elsif line.start_with?('/', '!', '=')
|
105
|
+
line_and_rules = parse_selection_line(line)
|
106
|
+
end
|
107
|
+
line_and_rules
|
108
|
+
end
|
109
|
+
|
110
|
+
def parse_rule_line(line)
|
111
|
+
line.gsub!(/\s+/, '')
|
112
|
+
rule_line_arr = line.split('=')
|
113
|
+
rules_list = rule_line_arr.last.split('+')
|
114
|
+
rule_name = rule_line_arr.first
|
115
|
+
rules_list.each_index do |i|
|
116
|
+
# Cases where rule respresents one or more other rules
|
117
|
+
if @rules.key?(rules_list[i])
|
118
|
+
rules_list[i] = @rules[rules_list[i]]
|
119
|
+
end
|
120
|
+
rules_list[i] = handle_multi_rule(rules_list, i)
|
121
|
+
end
|
122
|
+
@rules[rule_name] = rules_list.flatten
|
123
|
+
end
|
124
|
+
|
125
|
+
def parse_selection_line(line)
|
126
|
+
selec_line_arr = line.split(' ')
|
127
|
+
selection_line = selec_line_arr.first
|
128
|
+
selection_line.chop! if selection_line.end_with?('/')
|
129
|
+
rule_list = selec_line_arr.last.split('+')
|
130
|
+
rule_list.each_index do |i|
|
131
|
+
hash_list = @rules[rule_list[i]]
|
132
|
+
# Cases where rule respresents one or more other rules
|
133
|
+
if !hash_list.nil?
|
134
|
+
rule_list[i] = hash_list
|
135
|
+
end
|
136
|
+
rule_list[i] = handle_multi_rule(rule_list, i)
|
137
|
+
end
|
138
|
+
rule_list.flatten!
|
139
|
+
{
|
140
|
+
'selection_line' => selection_line,
|
141
|
+
'rules' => rule_list,
|
142
|
+
}
|
143
|
+
end
|
144
|
+
|
145
|
+
def handle_multi_rule(rule_list, i)
|
146
|
+
# Rules that represent multiple rules (R,L,>)
|
147
|
+
r_rules = %w{p i l n u g s m c md5}
|
148
|
+
l_rules = %w{p i l n u g}
|
149
|
+
grow_log_rules = %w{p l u g i n S}
|
150
|
+
|
151
|
+
case rule_list[i]
|
152
|
+
when 'R'
|
153
|
+
return r_rules
|
154
|
+
when 'L'
|
155
|
+
return l_rules
|
156
|
+
when '>'
|
157
|
+
return grow_log_rules
|
158
|
+
end
|
159
|
+
rule_list[i]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -177,7 +177,7 @@ module Inspec::Resources
|
|
177
177
|
|
178
178
|
# NB only in file lines
|
179
179
|
def get_key(line)
|
180
|
-
line.match(/-k ([^ ]+)/)[1]
|
180
|
+
line.match(/-k ([^ ]+)/)[1] if line.include?('-k ')
|
181
181
|
end
|
182
182
|
|
183
183
|
# NOTE there are NO precautions wrt. filenames containing spaces in auditctl
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# author: Matthew Dromazos
|
3
|
+
|
4
|
+
require 'utils/parser'
|
5
|
+
|
6
|
+
class EtcHosts < Inspec.resource(1)
|
7
|
+
name 'etc_hosts'
|
8
|
+
desc 'Use the etc_hosts InSpec audit resource to find an
|
9
|
+
ip_address and its associated hosts'
|
10
|
+
example "
|
11
|
+
describe etc_hosts.where { ip_address == '127.0.0.1' } do
|
12
|
+
its('ip_address') { should cmp '127.0.0.1' }
|
13
|
+
its('primary_name') { should cmp 'localhost' }
|
14
|
+
its('all_host_names') { should eq [['localhost', 'localhost.localdomain', 'localhost4', 'localhost4.localdomain4']] }
|
15
|
+
end
|
16
|
+
"
|
17
|
+
|
18
|
+
attr_reader :params
|
19
|
+
|
20
|
+
include CommentParser
|
21
|
+
|
22
|
+
def initialize(hosts_path = nil)
|
23
|
+
return skip_resource 'The `etc_hosts` resource is not supported on your OS.' unless inspec.os.linux? || inspec.os.windows?
|
24
|
+
@conf_path = hosts_path || default_hosts_file_path
|
25
|
+
@content = nil
|
26
|
+
@params = nil
|
27
|
+
read_content
|
28
|
+
end
|
29
|
+
|
30
|
+
filter = FilterTable.create
|
31
|
+
filter.add_accessor(:where)
|
32
|
+
.add_accessor(:entries)
|
33
|
+
.add(:ip_address, field: 'ip_address')
|
34
|
+
.add(:primary_name, field: 'primary_name')
|
35
|
+
.add(:all_host_names, field: 'all_host_names')
|
36
|
+
|
37
|
+
filter.connect(self, :params)
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def default_hosts_file_path
|
42
|
+
inspec.os.windows? ? 'C:\windows\system32\drivers\etc\hosts' : '/etc/hosts'
|
43
|
+
end
|
44
|
+
|
45
|
+
def read_content
|
46
|
+
@content = ''
|
47
|
+
@params = {}
|
48
|
+
@content = read_file(@conf_path)
|
49
|
+
@params = parse_conf(@content)
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse_conf(content)
|
53
|
+
content.map do |line|
|
54
|
+
data, = parse_comment_line(line, comment_char: '#', standalone_comments: false)
|
55
|
+
parse_line(data) unless data == ''
|
56
|
+
end.compact
|
57
|
+
end
|
58
|
+
|
59
|
+
def parse_line(line)
|
60
|
+
line_parts = line.split
|
61
|
+
return nil unless line_parts.length >= 2
|
62
|
+
{
|
63
|
+
'ip_address' => line_parts[0],
|
64
|
+
'primary_name' => line_parts[1],
|
65
|
+
'all_host_names' => line_parts[1..-1],
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def read_file(conf_path = @conf_path)
|
70
|
+
file = inspec.file(conf_path)
|
71
|
+
if !file.file?
|
72
|
+
return skip_resource "Can't find file. \"#{@conf_path}\""
|
73
|
+
end
|
74
|
+
|
75
|
+
raw_conf = file.content
|
76
|
+
if raw_conf.empty? && !file.empty?
|
77
|
+
return skip_resource("Could not read file contents\" #{@conf_path}\"")
|
78
|
+
end
|
79
|
+
raw_conf.lines
|
80
|
+
end
|
81
|
+
end
|
data/lib/resources/groups.rb
CHANGED
data/lib/resources/http.rb
CHANGED
data/lib/resources/pip.rb
CHANGED
@@ -7,6 +7,7 @@
|
|
7
7
|
# it { should be_installed }
|
8
8
|
# end
|
9
9
|
#
|
10
|
+
|
10
11
|
module Inspec::Resources
|
11
12
|
class PipPackage < Inspec.resource(1)
|
12
13
|
name 'pip'
|
@@ -15,10 +16,17 @@ module Inspec::Resources
|
|
15
16
|
describe pip('Jinja2') do
|
16
17
|
it { should be_installed }
|
17
18
|
end
|
19
|
+
|
20
|
+
describe pip('django', '/path/to/virtualenv/bin/pip') do
|
21
|
+
it { should be_installed }
|
22
|
+
its('version') { should eq('1.11.4')}
|
23
|
+
end
|
18
24
|
"
|
19
25
|
|
20
|
-
def initialize(package_name)
|
26
|
+
def initialize(package_name, pip_path = nil)
|
21
27
|
@package_name = package_name
|
28
|
+
@pip_cmd = pip_path || default_pip_path
|
29
|
+
return skip_resource 'pip not found' unless inspec.command(@pip_cmd).exist?
|
22
30
|
end
|
23
31
|
|
24
32
|
def info
|
@@ -26,7 +34,7 @@ module Inspec::Resources
|
|
26
34
|
|
27
35
|
@info = {}
|
28
36
|
@info[:type] = 'pip'
|
29
|
-
cmd = inspec.command("#{pip_cmd} show #{@package_name}")
|
37
|
+
cmd = inspec.command("#{@pip_cmd} show #{@package_name}")
|
30
38
|
return @info if cmd.exit_status != 0
|
31
39
|
|
32
40
|
params = SimpleConfig.new(
|
@@ -54,28 +62,28 @@ module Inspec::Resources
|
|
54
62
|
|
55
63
|
private
|
56
64
|
|
57
|
-
def
|
65
|
+
def default_pip_path
|
66
|
+
return 'pip' unless inspec.os.windows?
|
67
|
+
|
58
68
|
# Pip is not on the default path for Windows, therefore we do some logic
|
59
69
|
# to find the binary on Windows
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
pipdir.pop
|
72
|
-
pipcmd = pipdir.push('Scripts').push('pip.exe').join('/')
|
73
|
-
end
|
74
|
-
rescue JSON::ParserError => _e
|
75
|
-
return nil
|
70
|
+
cmd = inspec.command('New-Object -Type PSObject | Add-Member -MemberType NoteProperty -Name Pip -Value (Invoke-Command -ScriptBlock {where.exe pip}) -PassThru | Add-Member -MemberType NoteProperty -Name Python -Value (Invoke-Command -ScriptBlock {where.exe python}) -PassThru | ConvertTo-Json')
|
71
|
+
begin
|
72
|
+
paths = JSON.parse(cmd.stdout)
|
73
|
+
# use pip if it on system path
|
74
|
+
pipcmd = paths['Pip']
|
75
|
+
# calculate path on windows
|
76
|
+
if defined?(paths['Python']) && pipcmd.nil?
|
77
|
+
pipdir = paths['Python'].split('\\')
|
78
|
+
# remove python.exe
|
79
|
+
pipdir.pop
|
80
|
+
pipcmd = pipdir.push('Scripts').push('pip.exe').join('/')
|
76
81
|
end
|
82
|
+
rescue JSON::ParserError => _e
|
83
|
+
return nil
|
77
84
|
end
|
78
|
-
|
85
|
+
|
86
|
+
pipcmd
|
79
87
|
end
|
80
88
|
end
|
81
89
|
end
|
data/lib/resources/port.rb
CHANGED
@@ -264,10 +264,34 @@ module Inspec::Resources
|
|
264
264
|
end
|
265
265
|
|
266
266
|
# extract port information from netstat
|
267
|
-
class LinuxPorts < PortsInfo
|
267
|
+
class LinuxPorts < PortsInfo # rubocop:disable Metrics/ClassLength
|
268
|
+
ALLOWED_PROTOCOLS = %w{tcp tcp6 udp udp6}.freeze
|
269
|
+
|
268
270
|
def info
|
271
|
+
ports_via_ss || ports_via_netstat
|
272
|
+
end
|
273
|
+
|
274
|
+
def ports_via_ss
|
275
|
+
return nil unless inspec.command('ss').exist?
|
276
|
+
|
277
|
+
cmd = inspec.command('ss -tulpen')
|
278
|
+
return nil unless cmd.exit_status.to_i.zero?
|
279
|
+
|
280
|
+
ports = []
|
281
|
+
|
282
|
+
cmd.stdout.each_line do |line|
|
283
|
+
parsed_line = parse_ss_line(line)
|
284
|
+
ports << parsed_line unless parsed_line.nil?
|
285
|
+
end
|
286
|
+
|
287
|
+
ports
|
288
|
+
end
|
289
|
+
|
290
|
+
def ports_via_netstat
|
291
|
+
return nil unless inspec.command('netstat').exist?
|
292
|
+
|
269
293
|
cmd = inspec.command('netstat -tulpen')
|
270
|
-
return nil
|
294
|
+
return nil unless cmd.exit_status.to_i.zero?
|
271
295
|
|
272
296
|
ports = []
|
273
297
|
# parse all lines
|
@@ -362,6 +386,66 @@ module Inspec::Resources
|
|
362
386
|
'pid' => pid,
|
363
387
|
}
|
364
388
|
end
|
389
|
+
|
390
|
+
def parse_ss_line(line)
|
391
|
+
parsed = line.split(/\s+/, 7)
|
392
|
+
|
393
|
+
# ss only returns "tcp" and "udp" as the protocol. However, netstat would return
|
394
|
+
# "tcp6" and "udp6" as necessary. In order to maintain backward compatibility, we
|
395
|
+
# will manually modify the protocol value if the line we're parsing is an IPv6
|
396
|
+
# entry.
|
397
|
+
process_info = parsed[6]
|
398
|
+
protocol = parsed[0]
|
399
|
+
protocol += '6' if process_info.include?('v6only:1')
|
400
|
+
return nil unless ALLOWED_PROTOCOLS.include?(protocol)
|
401
|
+
|
402
|
+
# parse the Local Address:Port
|
403
|
+
# examples:
|
404
|
+
# *:22
|
405
|
+
# :::22
|
406
|
+
# 10.0.2.15:1234
|
407
|
+
# ::ffff:10.0.2.15:9300
|
408
|
+
# fe80::a00:27ff:fe32:ed09%enp0s3:9200
|
409
|
+
parsed_net_address = parsed[4].match(/(\S+):(\*|\d+)$/)
|
410
|
+
return nil if parsed_net_address.nil?
|
411
|
+
host = parsed_net_address[1]
|
412
|
+
port = parsed_net_address[2]
|
413
|
+
return nil if host.nil? && port.nil?
|
414
|
+
|
415
|
+
# For backward compatibility with the netstat output, ensure the
|
416
|
+
# port is stored as an integer
|
417
|
+
port = port.to_i
|
418
|
+
|
419
|
+
# for those "v4-but-listed-in-v6" entries, strip off the
|
420
|
+
# leading IPv6 value at the beginning
|
421
|
+
# example: ::ffff:10.0.2.15:9200
|
422
|
+
host.delete!('::ffff:') if host.start_with?('::ffff:')
|
423
|
+
|
424
|
+
# if there's an interface name in the local address, which is common for
|
425
|
+
# IPv6 listeners, strip that out too.
|
426
|
+
# example: fe80::a00:27ff:fe32:ed09%enp0s3
|
427
|
+
host = host.split('%').first
|
428
|
+
|
429
|
+
# if host is "*", replace with "0.0.0.0" to maintain backward compatibility with
|
430
|
+
# the netstat-provided data
|
431
|
+
host = '0.0.0.0' if host == '*'
|
432
|
+
|
433
|
+
# parse the process name from the processes information
|
434
|
+
process_match = parsed[6].match(/users:\(\(\"(\S+)\"/)
|
435
|
+
process = process_match.nil? ? nil : process_match[1]
|
436
|
+
|
437
|
+
# parse the PID from the processes information
|
438
|
+
pid_match = parsed[6].match(/pid=(\d+)/)
|
439
|
+
pid = pid_match.nil? ? nil : pid_match[1].to_i
|
440
|
+
|
441
|
+
{
|
442
|
+
'port' => port,
|
443
|
+
'address' => host,
|
444
|
+
'protocol' => protocol,
|
445
|
+
'process' => process,
|
446
|
+
'pid' => pid,
|
447
|
+
}
|
448
|
+
end
|
365
449
|
end
|
366
450
|
|
367
451
|
# extracts information from sockstat
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# author: Jonathan Morley
|
3
|
+
|
4
|
+
module Inspec::Resources
|
5
|
+
class XmlConfig < JsonConfig
|
6
|
+
name 'xml'
|
7
|
+
desc 'Use the xml InSpec resource to test configuration data in an XML file'
|
8
|
+
example "
|
9
|
+
describe xml('default.xml') do
|
10
|
+
its('key/sub_key') { should eq(['value']) }
|
11
|
+
end
|
12
|
+
"
|
13
|
+
|
14
|
+
def parse(content)
|
15
|
+
require 'rexml/document'
|
16
|
+
REXML::Document.new(content)
|
17
|
+
end
|
18
|
+
|
19
|
+
def value(key)
|
20
|
+
REXML::XPath.each(@params, key.first.to_s).map(&:text)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
"XML #{@path}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: inspec
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.35.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dominik Richter
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-08-
|
11
|
+
date: 2017-08-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: train
|
@@ -312,6 +312,7 @@ files:
|
|
312
312
|
- docs/migration.md
|
313
313
|
- docs/plugin_kitchen_inspec.md
|
314
314
|
- docs/profiles.md
|
315
|
+
- docs/resources/aide_conf.md.erb
|
315
316
|
- docs/resources/apache_conf.md.erb
|
316
317
|
- docs/resources/apt.md.erb
|
317
318
|
- docs/resources/audit_policy.md.erb
|
@@ -330,6 +331,7 @@ files:
|
|
330
331
|
- docs/resources/docker_container.md.erb
|
331
332
|
- docs/resources/docker_image.md.erb
|
332
333
|
- docs/resources/etc_group.md.erb
|
334
|
+
- docs/resources/etc_hosts.md.erb
|
333
335
|
- docs/resources/file.md.erb
|
334
336
|
- docs/resources/gem.md.erb
|
335
337
|
- docs/resources/group.md.erb
|
@@ -393,6 +395,7 @@ files:
|
|
393
395
|
- docs/resources/wmi.md.erb
|
394
396
|
- docs/resources/x509_certificate.md.erb
|
395
397
|
- docs/resources/xinetd_conf.md.erb
|
398
|
+
- docs/resources/xml.md.erb
|
396
399
|
- docs/resources/yaml.md.erb
|
397
400
|
- docs/resources/yum.md.erb
|
398
401
|
- docs/resources/zfs_dataset.md.erb
|
@@ -437,6 +440,10 @@ files:
|
|
437
440
|
- examples/profile-attribute/README.md
|
438
441
|
- examples/profile-attribute/controls/example.rb
|
439
442
|
- examples/profile-attribute/inspec.yml
|
443
|
+
- examples/profile-sensitive/README.md
|
444
|
+
- examples/profile-sensitive/controls/sensitive-failures.rb
|
445
|
+
- examples/profile-sensitive/controls/sensitive.rb
|
446
|
+
- examples/profile-sensitive/inspec.yml
|
440
447
|
- examples/profile/README.md
|
441
448
|
- examples/profile/controls/example.rb
|
442
449
|
- examples/profile/controls/gordon.rb
|
@@ -545,6 +552,7 @@ files:
|
|
545
552
|
- lib/inspec/source_reader.rb
|
546
553
|
- lib/inspec/version.rb
|
547
554
|
- lib/matchers/matchers.rb
|
555
|
+
- lib/resources/aide_conf.rb
|
548
556
|
- lib/resources/apache.rb
|
549
557
|
- lib/resources/apache_conf.rb
|
550
558
|
- lib/resources/apt.rb
|
@@ -563,6 +571,7 @@ files:
|
|
563
571
|
- lib/resources/docker_container.rb
|
564
572
|
- lib/resources/docker_image.rb
|
565
573
|
- lib/resources/etc_group.rb
|
574
|
+
- lib/resources/etc_hosts.rb
|
566
575
|
- lib/resources/file.rb
|
567
576
|
- lib/resources/gem.rb
|
568
577
|
- lib/resources/groups.rb
|
@@ -623,6 +632,7 @@ files:
|
|
623
632
|
- lib/resources/wmi.rb
|
624
633
|
- lib/resources/x509_certificate.rb
|
625
634
|
- lib/resources/xinetd.rb
|
635
|
+
- lib/resources/xml.rb
|
626
636
|
- lib/resources/yaml.rb
|
627
637
|
- lib/resources/yum.rb
|
628
638
|
- lib/resources/zfs_dataset.rb
|