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