inspec 0.9.0
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 +7 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +65 -0
- data/.travis.yml +23 -0
- data/CHANGELOG.md +38 -0
- data/Gemfile +33 -0
- data/LICENSE +201 -0
- data/MAINTAINERS.md +28 -0
- data/MAINTAINERS.toml +42 -0
- data/README.md +257 -0
- data/Rakefile +47 -0
- data/bin/inspec +109 -0
- data/docs/ctl_inspec.rst +195 -0
- data/docs/dsl_inspec.rst +182 -0
- data/docs/readme.rst +100 -0
- data/docs/resources.rst +4319 -0
- data/docs/template.rst +51 -0
- data/examples/test-kitchen/.kitchen.yml +20 -0
- data/examples/test-kitchen/Berksfile +3 -0
- data/examples/test-kitchen/Gemfile +21 -0
- data/examples/test-kitchen/README.md +27 -0
- data/examples/test-kitchen/metadata.rb +7 -0
- data/examples/test-kitchen/recipes/default.rb +6 -0
- data/examples/test-kitchen/recipes/nginx.rb +30 -0
- data/examples/test-kitchen/test/integration/default/web_spec.rb +28 -0
- data/inspec.gemspec +30 -0
- data/lib/inspec.rb +20 -0
- data/lib/inspec/backend.rb +42 -0
- data/lib/inspec/dsl.rb +151 -0
- data/lib/inspec/log.rb +34 -0
- data/lib/inspec/metadata.rb +79 -0
- data/lib/inspec/plugins.rb +9 -0
- data/lib/inspec/plugins/resource.rb +62 -0
- data/lib/inspec/profile.rb +138 -0
- data/lib/inspec/profile_context.rb +170 -0
- data/lib/inspec/resource.rb +76 -0
- data/lib/inspec/rspec_json_formatter.rb +27 -0
- data/lib/inspec/rule.rb +170 -0
- data/lib/inspec/runner.rb +154 -0
- data/lib/inspec/shell.rb +66 -0
- data/lib/inspec/targets.rb +9 -0
- data/lib/inspec/targets/core.rb +27 -0
- data/lib/inspec/targets/dir.rb +67 -0
- data/lib/inspec/targets/file.rb +29 -0
- data/lib/inspec/targets/folder.rb +43 -0
- data/lib/inspec/targets/tar.rb +34 -0
- data/lib/inspec/targets/url.rb +39 -0
- data/lib/inspec/targets/zip.rb +47 -0
- data/lib/inspec/version.rb +7 -0
- data/lib/matchers/matchers.rb +221 -0
- data/lib/resources/apache.rb +29 -0
- data/lib/resources/apache_conf.rb +113 -0
- data/lib/resources/apt.rb +140 -0
- data/lib/resources/audit_policy.rb +63 -0
- data/lib/resources/auditd_conf.rb +56 -0
- data/lib/resources/auditd_rules.rb +53 -0
- data/lib/resources/bond.rb +65 -0
- data/lib/resources/bridge.rb +114 -0
- data/lib/resources/command.rb +57 -0
- data/lib/resources/csv.rb +32 -0
- data/lib/resources/directory.rb +15 -0
- data/lib/resources/etc_group.rb +150 -0
- data/lib/resources/file.rb +110 -0
- data/lib/resources/gem.rb +46 -0
- data/lib/resources/group.rb +132 -0
- data/lib/resources/host.rb +143 -0
- data/lib/resources/inetd_conf.rb +56 -0
- data/lib/resources/interface.rb +127 -0
- data/lib/resources/iptables.rb +65 -0
- data/lib/resources/json.rb +64 -0
- data/lib/resources/kernel_module.rb +40 -0
- data/lib/resources/kernel_parameter.rb +55 -0
- data/lib/resources/limits_conf.rb +55 -0
- data/lib/resources/login_def.rb +60 -0
- data/lib/resources/mysql.rb +81 -0
- data/lib/resources/mysql_conf.rb +116 -0
- data/lib/resources/mysql_session.rb +52 -0
- data/lib/resources/npm.rb +44 -0
- data/lib/resources/ntp_conf.rb +58 -0
- data/lib/resources/oneget.rb +63 -0
- data/lib/resources/os.rb +22 -0
- data/lib/resources/os_env.rb +34 -0
- data/lib/resources/package.rb +169 -0
- data/lib/resources/parse_config.rb +75 -0
- data/lib/resources/passwd.rb +93 -0
- data/lib/resources/pip.rb +75 -0
- data/lib/resources/port.rb +296 -0
- data/lib/resources/postgres.rb +37 -0
- data/lib/resources/postgres_conf.rb +87 -0
- data/lib/resources/postgres_session.rb +59 -0
- data/lib/resources/processes.rb +57 -0
- data/lib/resources/registry_key.rb +54 -0
- data/lib/resources/script.rb +34 -0
- data/lib/resources/security_policy.rb +73 -0
- data/lib/resources/service.rb +379 -0
- data/lib/resources/ssh_conf.rb +75 -0
- data/lib/resources/user.rb +374 -0
- data/lib/resources/windows_feature.rb +77 -0
- data/lib/resources/yaml.rb +23 -0
- data/lib/resources/yum.rb +154 -0
- data/lib/utils/convert.rb +12 -0
- data/lib/utils/detect.rb +15 -0
- data/lib/utils/find_files.rb +36 -0
- data/lib/utils/hash.rb +13 -0
- data/lib/utils/modulator.rb +12 -0
- data/lib/utils/parser.rb +61 -0
- data/lib/utils/simpleconfig.rb +115 -0
- data/tasks/maintainers.rb +213 -0
- data/test/docker_run.rb +156 -0
- data/test/docker_test.rb +51 -0
- data/test/helper.rb +200 -0
- data/test/integration/.kitchen.yml +42 -0
- data/test/integration/Berksfile +4 -0
- data/test/integration/cookbooks/os_prepare/metadata.rb +8 -0
- data/test/integration/cookbooks/os_prepare/recipes/apt.rb +20 -0
- data/test/integration/cookbooks/os_prepare/recipes/default.rb +9 -0
- data/test/integration/cookbooks/os_prepare/recipes/file.rb +21 -0
- data/test/integration/cookbooks/os_prepare/recipes/package.rb +26 -0
- data/test/integration/default/_debug_spec.rb +1 -0
- data/test/integration/default/apt_spec.rb +42 -0
- data/test/integration/default/file_spec.rb +109 -0
- data/test/integration/default/group_spec.rb +32 -0
- data/test/integration/default/kernel_module_spec.rb +17 -0
- data/test/integration/default/kernel_parameter_spec.rb +56 -0
- data/test/integration/default/package_spec.rb +11 -0
- data/test/integration/default/service_spec.rb +28 -0
- data/test/integration/default/user_spec.rb +44 -0
- data/test/resource/command_test.rb +33 -0
- data/test/resource/dsl_test.rb +45 -0
- data/test/resource/file_test.rb +130 -0
- data/test/resource/ssh_config.rb +9 -0
- data/test/resource/sshd_config.rb +9 -0
- data/test/test-extra.yaml +11 -0
- data/test/test.yaml +11 -0
- data/test/unit/mock/cmd/Get-NetAdapter +24 -0
- data/test/unit/mock/cmd/GetUserAccount +33 -0
- data/test/unit/mock/cmd/GetWin32Group +23 -0
- data/test/unit/mock/cmd/PATH +1 -0
- data/test/unit/mock/cmd/Resolve-DnsName +26 -0
- data/test/unit/mock/cmd/Test-NetConnection +4 -0
- data/test/unit/mock/cmd/auditctl +7 -0
- data/test/unit/mock/cmd/auditpol +2 -0
- data/test/unit/mock/cmd/brew-info-jq +1 -0
- data/test/unit/mock/cmd/chage-l-root +7 -0
- data/test/unit/mock/cmd/dpkg-s-curl +21 -0
- data/test/unit/mock/cmd/dscl +5 -0
- data/test/unit/mock/cmd/etc-apt +7 -0
- data/test/unit/mock/cmd/find-etc-rc-d-name-S +12 -0
- data/test/unit/mock/cmd/find-net-interface +9 -0
- data/test/unit/mock/cmd/gem-list-local-a-q-rubocop +1 -0
- data/test/unit/mock/cmd/get-net-tcpconnection +24 -0
- data/test/unit/mock/cmd/get-netadapter-binding-bridge +4 -0
- data/test/unit/mock/cmd/get-package-firefox +30 -0
- data/test/unit/mock/cmd/get-package-ruby +18 -0
- data/test/unit/mock/cmd/get-service-dhcp +10 -0
- data/test/unit/mock/cmd/get-windows-feature +7 -0
- data/test/unit/mock/cmd/getent-hosts-example.com +1 -0
- data/test/unit/mock/cmd/getent-passwd-root +1 -0
- data/test/unit/mock/cmd/id-chartmann +1 -0
- data/test/unit/mock/cmd/id-root +1 -0
- data/test/unit/mock/cmd/initctl-show-config-ssh +3 -0
- data/test/unit/mock/cmd/initctl-status-ssh +1 -0
- data/test/unit/mock/cmd/iptables-s +6 -0
- data/test/unit/mock/cmd/launchctl-list +3 -0
- data/test/unit/mock/cmd/ls-1-etc-init.d +2 -0
- data/test/unit/mock/cmd/ls-sys-class-net-br +2 -0
- data/test/unit/mock/cmd/lsmod +2 -0
- data/test/unit/mock/cmd/lsof-np-itcp +4 -0
- data/test/unit/mock/cmd/netstat-tulpen +5 -0
- data/test/unit/mock/cmd/npm-ls-g--json-bower +9 -0
- data/test/unit/mock/cmd/pacman-qi-curl +21 -0
- data/test/unit/mock/cmd/ping-example.com +6 -0
- data/test/unit/mock/cmd/pip-show-jinja2 +11 -0
- data/test/unit/mock/cmd/ps-aux +3 -0
- data/test/unit/mock/cmd/pw-usershow-root-7 +1 -0
- data/test/unit/mock/cmd/reg_schedule +1 -0
- data/test/unit/mock/cmd/rpm-qia-curl +24 -0
- data/test/unit/mock/cmd/sbin_sysctl +1 -0
- data/test/unit/mock/cmd/secedit-export +7 -0
- data/test/unit/mock/cmd/service-e +2 -0
- data/test/unit/mock/cmd/service-sendmail-onestatus +3 -0
- data/test/unit/mock/cmd/service-sshd-status +1 -0
- data/test/unit/mock/cmd/sockstat +5 -0
- data/test/unit/mock/cmd/success +0 -0
- data/test/unit/mock/cmd/systemctl-show-all-sshd +6 -0
- data/test/unit/mock/cmd/win32_product +8 -0
- data/test/unit/mock/cmd/yum-repolist-all +52 -0
- data/test/unit/mock/files/auditd.conf +4 -0
- data/test/unit/mock/files/bond0 +37 -0
- data/test/unit/mock/files/etcgroup +3 -0
- data/test/unit/mock/files/example.csv +6 -0
- data/test/unit/mock/files/inetd.conf +2 -0
- data/test/unit/mock/files/kitchen.yml +7 -0
- data/test/unit/mock/files/limits.conf +5 -0
- data/test/unit/mock/files/login.defs +5 -0
- data/test/unit/mock/files/mysql.conf +8 -0
- data/test/unit/mock/files/mysql2.conf +2 -0
- data/test/unit/mock/files/ntp.conf +5 -0
- data/test/unit/mock/files/passwd +2 -0
- data/test/unit/mock/files/policyfile.lock.json +12 -0
- data/test/unit/mock/files/ssh_config +5 -0
- data/test/unit/mock/files/sshd_config +7 -0
- data/test/unit/mock/profiles/empty/metadata.rb +0 -0
- data/test/unit/mock/profiles/metadata/metadata.rb +1 -0
- data/test/unit/profile_context_test.rb +140 -0
- data/test/unit/profile_test.rb +49 -0
- data/test/unit/resources/apt_test.rb +46 -0
- data/test/unit/resources/audit_policy_test.rb +13 -0
- data/test/unit/resources/auditd_conf_test.rb +15 -0
- data/test/unit/resources/auditd_rules_test.rb +21 -0
- data/test/unit/resources/bond_test.rb +24 -0
- data/test/unit/resources/bridge_test.rb +56 -0
- data/test/unit/resources/csv_test.rb +35 -0
- data/test/unit/resources/etc_group_test.rb +37 -0
- data/test/unit/resources/gem_test.rb +20 -0
- data/test/unit/resources/group_test.rb +96 -0
- data/test/unit/resources/host_test.rb +38 -0
- data/test/unit/resources/inetd_conf_test.rb +15 -0
- data/test/unit/resources/interface_test.rb +54 -0
- data/test/unit/resources/iptables_test.rb +30 -0
- data/test/unit/resources/json_test.rb +36 -0
- data/test/unit/resources/kernel_module_test.rb +23 -0
- data/test/unit/resources/kernel_parameter_test.rb +13 -0
- data/test/unit/resources/limits_conf_test.rb +14 -0
- data/test/unit/resources/login_def_test.rb +16 -0
- data/test/unit/resources/mysql_conf_test.rb +14 -0
- data/test/unit/resources/npm_test.rb +20 -0
- data/test/unit/resources/ntp_conf_test.rb +16 -0
- data/test/unit/resources/oneget_test.rb +45 -0
- data/test/unit/resources/os_env_test.rb +13 -0
- data/test/unit/resources/package_test.rb +51 -0
- data/test/unit/resources/passwd_test.rb +24 -0
- data/test/unit/resources/pip_test.rb +15 -0
- data/test/unit/resources/port_test.rb +46 -0
- data/test/unit/resources/processes_test.rb +32 -0
- data/test/unit/resources/registry_key_test.rb +19 -0
- data/test/unit/resources/script_test.rb +19 -0
- data/test/unit/resources/security_policy_test.rb +16 -0
- data/test/unit/resources/service_test.rb +116 -0
- data/test/unit/resources/ssh_conf_test.rb +33 -0
- data/test/unit/resources/user_test.rb +93 -0
- data/test/unit/resources/windows_feature.rb +17 -0
- data/test/unit/resources/yaml_test.rb +34 -0
- data/test/unit/resources/yum_test.rb +68 -0
- data/test/unit/simpleconfig_test.rb +80 -0
- data/test/unit/utils/content_parser_test.rb +30 -0
- metadata +555 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# copyright: 2015, Vulcano Security GmbH
|
|
3
|
+
# author: Dominik Richter
|
|
4
|
+
# author: Christoph Hartmann
|
|
5
|
+
# license: All rights reserved
|
|
6
|
+
|
|
7
|
+
# Usage example:
|
|
8
|
+
#
|
|
9
|
+
# audit = command('/sbin/auditctl -l').stdout
|
|
10
|
+
# options = {
|
|
11
|
+
# assignment_re: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
|
|
12
|
+
# multiple_values: true
|
|
13
|
+
# }
|
|
14
|
+
# describe parse_config(audit, options ) do
|
|
15
|
+
|
|
16
|
+
class PConfig < Inspec.resource(1)
|
|
17
|
+
name 'parse_config'
|
|
18
|
+
|
|
19
|
+
def initialize(content = nil, useropts = nil)
|
|
20
|
+
@opts = {}
|
|
21
|
+
@opts = useropts.dup unless useropts.nil?
|
|
22
|
+
@files_contents = {}
|
|
23
|
+
@params = nil
|
|
24
|
+
|
|
25
|
+
@content = content
|
|
26
|
+
read_content if @content.nil?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def method_missing(name)
|
|
30
|
+
@params || read_content
|
|
31
|
+
@params[name.to_s]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def parse_file(conf_path)
|
|
35
|
+
@conf_path = conf_path
|
|
36
|
+
|
|
37
|
+
# read the file
|
|
38
|
+
if !inspec.file(conf_path).file?
|
|
39
|
+
return skip_resource "Can't find file \"#{conf_path}\""
|
|
40
|
+
end
|
|
41
|
+
@content = read_file(conf_path)
|
|
42
|
+
if @content.empty? && inspec.file(conf_path).size > 0
|
|
43
|
+
return skip_resource "Can't read file \"#{conf_path}\""
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
read_content
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def read_file(path)
|
|
50
|
+
@files_contents[path] ||= inspec.file(path).content
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def read_content
|
|
54
|
+
# parse the file
|
|
55
|
+
@params = SimpleConfig.new(@content, @opts).params
|
|
56
|
+
@content
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def to_s
|
|
60
|
+
"Parse Config #{@conf_path}"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class PConfigFile < PConfig
|
|
65
|
+
name 'parse_config_file'
|
|
66
|
+
|
|
67
|
+
def initialize(path, opts = nil)
|
|
68
|
+
super(nil, opts)
|
|
69
|
+
parse_file(path)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def to_s
|
|
73
|
+
"Parse Config File #{@conf_path}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# copyright: 2015, Vulcano Security GmbH
|
|
3
|
+
# author: Christoph Hartmann
|
|
4
|
+
# author: Dominik Richter
|
|
5
|
+
# license: All rights reserved
|
|
6
|
+
|
|
7
|
+
# The file format consists of
|
|
8
|
+
# - username
|
|
9
|
+
# - password
|
|
10
|
+
# - userid
|
|
11
|
+
# - groupid
|
|
12
|
+
# - user id info
|
|
13
|
+
# - home directory
|
|
14
|
+
# - command
|
|
15
|
+
|
|
16
|
+
# usage:
|
|
17
|
+
#
|
|
18
|
+
# describe passwd do
|
|
19
|
+
# its(:usernames) { should eq ['root'] }
|
|
20
|
+
# its(:uids) { should eq [0] }
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# describe passwd.uid(0) do
|
|
24
|
+
# its(:username) { should eq 'root' }
|
|
25
|
+
# its(:count) { should eq 1 }
|
|
26
|
+
# end
|
|
27
|
+
|
|
28
|
+
require 'utils/parser'
|
|
29
|
+
|
|
30
|
+
class Passwd < Inspec.resource(1)
|
|
31
|
+
name 'passwd'
|
|
32
|
+
|
|
33
|
+
include ContentParser
|
|
34
|
+
|
|
35
|
+
attr_reader :uid
|
|
36
|
+
attr_reader :parsed
|
|
37
|
+
|
|
38
|
+
def initialize(path = nil)
|
|
39
|
+
@path = path || '/etc/passwd'
|
|
40
|
+
@content = inspec.file(@path).content
|
|
41
|
+
@parsed = parse_passwd(@content)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# call passwd().uid(0)
|
|
45
|
+
# returns a seperate object with reference to this object
|
|
46
|
+
def uid(uid)
|
|
47
|
+
PasswdUid.new(self, uid)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def usernames
|
|
51
|
+
map_data('name')
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def passwords
|
|
55
|
+
map_data('password')
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def uids
|
|
59
|
+
map_data('uid')
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def gids
|
|
63
|
+
map_data('gid')
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def to_s
|
|
67
|
+
'/etc/passwd'
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def map_data(id)
|
|
73
|
+
@parsed.map {|x|
|
|
74
|
+
x[id]
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# object that hold a specifc uid view on passwd
|
|
80
|
+
class PasswdUid
|
|
81
|
+
def initialize(passwd, uid)
|
|
82
|
+
@passwd = passwd
|
|
83
|
+
@users = @passwd.parsed.select { |x| x['uid'] == "#{uid}" }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def username
|
|
87
|
+
@users.at(0)['name']
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def count
|
|
91
|
+
@users.size
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# author: Christoph Hartmann
|
|
3
|
+
# author: Dominik Richter
|
|
4
|
+
|
|
5
|
+
# Usage:
|
|
6
|
+
# describe pip('Jinja2') do
|
|
7
|
+
# it { should be_installed }
|
|
8
|
+
# end
|
|
9
|
+
#
|
|
10
|
+
class PipPackage < Inspec.resource(1)
|
|
11
|
+
name 'pip'
|
|
12
|
+
|
|
13
|
+
def initialize(package_name)
|
|
14
|
+
@package_name = package_name
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def info
|
|
18
|
+
return @info if defined?(@info)
|
|
19
|
+
|
|
20
|
+
@info = {}
|
|
21
|
+
@info[:type] = 'pip'
|
|
22
|
+
cmd = inspec.command("#{pip_cmd} show #{@package_name}")
|
|
23
|
+
return @info if cmd.exit_status != 0
|
|
24
|
+
|
|
25
|
+
params = SimpleConfig.new(
|
|
26
|
+
cmd.stdout,
|
|
27
|
+
assignment_re: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
|
|
28
|
+
multiple_values: false,
|
|
29
|
+
).params
|
|
30
|
+
@info[:name] = params['Name']
|
|
31
|
+
@info[:version] = params['Version']
|
|
32
|
+
@info[:installed] = true
|
|
33
|
+
@info
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def installed?
|
|
37
|
+
info[:installed] == true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def version
|
|
41
|
+
info[:version]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def to_s
|
|
45
|
+
"Pip Package #{@package_name}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def pip_cmd
|
|
51
|
+
# Pip is not on the default path for Windows, therefore we do some logic
|
|
52
|
+
# to find the binary on Windows
|
|
53
|
+
family = inspec.os[:family]
|
|
54
|
+
case family
|
|
55
|
+
when 'windows'
|
|
56
|
+
# we need to detect the pip command on Windows
|
|
57
|
+
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')
|
|
58
|
+
begin
|
|
59
|
+
paths = JSON.parse(cmd.stdout)
|
|
60
|
+
# use pip if it on system path
|
|
61
|
+
pipcmd = paths['Pip']
|
|
62
|
+
# calculate path on windows
|
|
63
|
+
if defined?(paths['Python']) && pipcmd.nil?
|
|
64
|
+
pipdir = paths['Python'].split('\\')
|
|
65
|
+
# remove python.exe
|
|
66
|
+
pipdir.pop
|
|
67
|
+
pipcmd = pipdir.push('Scripts').push('pip.exe').join('/')
|
|
68
|
+
end
|
|
69
|
+
rescue JSON::ParserError => _e
|
|
70
|
+
return nil
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
pipcmd || 'pip'
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# author: Christoph Hartmann
|
|
3
|
+
# author: Dominik Richter
|
|
4
|
+
|
|
5
|
+
# Usage:
|
|
6
|
+
# describe port(80) do
|
|
7
|
+
# it { should be_listening }
|
|
8
|
+
# its('protocol') {should eq 'tcp'}
|
|
9
|
+
# end
|
|
10
|
+
#
|
|
11
|
+
# not supported serverspec syntax
|
|
12
|
+
# describe port(80) do
|
|
13
|
+
# it { should be_listening.with('tcp') }
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# TODO: currently we return local ip only
|
|
17
|
+
# TODO: improve handling of same port on multiple interfaces
|
|
18
|
+
class Port < Inspec.resource(1)
|
|
19
|
+
name 'port'
|
|
20
|
+
|
|
21
|
+
def initialize(port)
|
|
22
|
+
@port = port
|
|
23
|
+
@port_manager = nil
|
|
24
|
+
@cache = nil
|
|
25
|
+
|
|
26
|
+
case inspec.os[:family]
|
|
27
|
+
when 'ubuntu', 'debian', 'redhat', 'fedora', 'arch'
|
|
28
|
+
@port_manager = LinuxPorts.new(inspec)
|
|
29
|
+
when 'darwin'
|
|
30
|
+
@port_manager = DarwinPorts.new(inspec)
|
|
31
|
+
when 'windows'
|
|
32
|
+
@port_manager = WindowsPorts.new(inspec)
|
|
33
|
+
when 'freebsd'
|
|
34
|
+
@port_manager = FreeBsdPorts.new(inspec)
|
|
35
|
+
else
|
|
36
|
+
return skip_resource 'The `port` resource is not supported on your OS yet.'
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def listening?(_protocol = nil, _local_address = nil)
|
|
41
|
+
info.size > 0
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def protocol
|
|
45
|
+
res = info.map { |x| x[:protocol] }.uniq.compact
|
|
46
|
+
res.size > 0 ? res : nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def process
|
|
50
|
+
res = info.map { |x| x[:process] }.uniq.compact
|
|
51
|
+
res.size > 0 ? res : nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def pid
|
|
55
|
+
res = info.map { |x| x[:pid] }.uniq.compact
|
|
56
|
+
res.size > 0 ? res : nil
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def to_s
|
|
60
|
+
"Port #{@port}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def info
|
|
66
|
+
return @cache if !@cache.nil?
|
|
67
|
+
# abort if os detection has not worked
|
|
68
|
+
return @cache = [] if @port_manager.nil?
|
|
69
|
+
# query ports
|
|
70
|
+
ports = @port_manager.info || []
|
|
71
|
+
@cache = ports.select { |p| p[:port] == @port }
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# implements an info method and returns all ip adresses and protocols for
|
|
76
|
+
# each port
|
|
77
|
+
# [{
|
|
78
|
+
# port: 80,
|
|
79
|
+
# address: [{
|
|
80
|
+
# ip: '0.0.0.0'
|
|
81
|
+
# protocol: 'tcp'
|
|
82
|
+
# }],
|
|
83
|
+
# }]
|
|
84
|
+
class PortsInfo
|
|
85
|
+
attr_reader :inspec
|
|
86
|
+
def initialize(inspec)
|
|
87
|
+
@inspec = inspec
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# TODO: Add UDP infromation Get-NetUDPEndpoint
|
|
92
|
+
# TODO: currently Windows only supports tcp ports
|
|
93
|
+
# TODO: Get-NetTCPConnection does not return PIDs
|
|
94
|
+
# TODO: double-check output with 'netstat -ano'
|
|
95
|
+
# @see https://connect.microsoft.com/PowerShell/feedback/details/1349420/get-nettcpconnection-does-not-show-processid
|
|
96
|
+
class WindowsPorts < PortsInfo
|
|
97
|
+
def info
|
|
98
|
+
# get all port information
|
|
99
|
+
cmd = inspec.command('Get-NetTCPConnection | Select-Object -Property State, Caption, Description, LocalAddress, LocalPort, RemoteAddress, RemotePort, DisplayName, Status | ConvertTo-Json')
|
|
100
|
+
|
|
101
|
+
begin
|
|
102
|
+
ports = JSON.parse(cmd.stdout)
|
|
103
|
+
rescue JSON::ParserError => _e
|
|
104
|
+
return nil
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
return nil if ports.nil?
|
|
108
|
+
|
|
109
|
+
ports.map { |x|
|
|
110
|
+
{
|
|
111
|
+
port: x['LocalPort'],
|
|
112
|
+
address: x['LocalAddress'],
|
|
113
|
+
protocol: 'tcp',
|
|
114
|
+
process: nil,
|
|
115
|
+
pid: nil,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# extracts udp and tcp ports from macos
|
|
122
|
+
class DarwinPorts < PortsInfo
|
|
123
|
+
def info
|
|
124
|
+
# collects UDP and TCP information
|
|
125
|
+
cmd = inspec.command('lsof -nP -iTCP -iUDP -sTCP:LISTEN')
|
|
126
|
+
return nil if cmd.exit_status.to_i != 0
|
|
127
|
+
|
|
128
|
+
ports = []
|
|
129
|
+
# split on each newline
|
|
130
|
+
cmd.stdout.each_line do |line|
|
|
131
|
+
# parse each line
|
|
132
|
+
# 1 - COMMAND, 2 - PID, 3 - USER, 4 - FD, 5 - TYPE, 6 - DEVICE, 7 - SIZE/OFF, 8 - NODE, 9 - NAME
|
|
133
|
+
parsed = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+).*$/.match(line)
|
|
134
|
+
# extract network info
|
|
135
|
+
net_addr = parsed[9].split(':')
|
|
136
|
+
# convert to number if possible
|
|
137
|
+
net_port = net_addr[1]
|
|
138
|
+
net_port = net_port.to_i if /^\d+$/.match(net_port)
|
|
139
|
+
protocol = parsed[8].downcase
|
|
140
|
+
|
|
141
|
+
# add version to protocol
|
|
142
|
+
type = parsed[5].downcase
|
|
143
|
+
protocol += '6' if type == 'IPv6'
|
|
144
|
+
|
|
145
|
+
# map data
|
|
146
|
+
port_info = {
|
|
147
|
+
port: net_port,
|
|
148
|
+
address: net_addr[0],
|
|
149
|
+
protocol: protocol,
|
|
150
|
+
process: parsed[1],
|
|
151
|
+
pid: parsed[2].to_i,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# push data, if not headerfile
|
|
155
|
+
ports.push(port_info) if %w{tcp tcp6 udp udp6}.include?(protocol)
|
|
156
|
+
end
|
|
157
|
+
ports
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# extract port information from netstat
|
|
162
|
+
class LinuxPorts < PortsInfo
|
|
163
|
+
def info
|
|
164
|
+
cmd = inspec.command('netstat -tulpen')
|
|
165
|
+
return nil if cmd.exit_status.to_i != 0
|
|
166
|
+
|
|
167
|
+
ports = []
|
|
168
|
+
# parse all lines
|
|
169
|
+
cmd.stdout.each_line do |line|
|
|
170
|
+
port_info = parse_netstat_line(line)
|
|
171
|
+
|
|
172
|
+
# only push protocols we are interested in
|
|
173
|
+
next unless %w{tcp tcp6 udp udp6}.include?(port_info[:protocol])
|
|
174
|
+
ports.push(port_info)
|
|
175
|
+
end
|
|
176
|
+
ports
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def parse_net_address(net_addr, protocol)
|
|
180
|
+
if protocol.eql?('tcp6') || protocol.eql?('udp6')
|
|
181
|
+
# prep for URI parsing, parse ip6 port
|
|
182
|
+
ip6 = /^(\S+:)(\d+)$/.match(net_addr)
|
|
183
|
+
ip6addr = ip6[1]
|
|
184
|
+
ip6addr = '::' if /^:::$/.match(ip6addr)
|
|
185
|
+
# build uri
|
|
186
|
+
ip_addr = URI("addr://[#{ip6addr}]:#{ip6[2]}")
|
|
187
|
+
# replace []
|
|
188
|
+
host = ip_addr.host[1..ip_addr.host.size-2]
|
|
189
|
+
port = ip_addr.port
|
|
190
|
+
else
|
|
191
|
+
ip_addr = URI('addr://'+net_addr)
|
|
192
|
+
host = ip_addr.host
|
|
193
|
+
port = ip_addr.port
|
|
194
|
+
end
|
|
195
|
+
[host, port]
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def parse_netstat_line(line)
|
|
199
|
+
# parse each line
|
|
200
|
+
# 1 - Proto, 2 - Recv-Q, 3 - Send-Q, 4 - Local Address, 5 - Foreign Address, 6 - State, 7 - Inode, 8 - PID/Program name
|
|
201
|
+
parsed = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$/.match(line)
|
|
202
|
+
return {} if parsed.nil?
|
|
203
|
+
|
|
204
|
+
# parse ip4 and ip6 addresses
|
|
205
|
+
protocol = parsed[1].downcase
|
|
206
|
+
host, port = parse_net_address(parsed[4], protocol)
|
|
207
|
+
|
|
208
|
+
# extract PID
|
|
209
|
+
process = parsed[9].split('/')
|
|
210
|
+
pid = process[0]
|
|
211
|
+
pid = pid.to_i if /^\d+$/.match(pid)
|
|
212
|
+
process = process[1]
|
|
213
|
+
|
|
214
|
+
# map data
|
|
215
|
+
{
|
|
216
|
+
port: port,
|
|
217
|
+
address: host,
|
|
218
|
+
protocol: protocol,
|
|
219
|
+
process: process,
|
|
220
|
+
pid: pid,
|
|
221
|
+
}
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# extracts information from sockstat
|
|
226
|
+
class FreeBsdPorts < PortsInfo
|
|
227
|
+
def info
|
|
228
|
+
cmd = inspec.command('sockstat -46l')
|
|
229
|
+
return nil if cmd.exit_status.to_i != 0
|
|
230
|
+
|
|
231
|
+
ports = []
|
|
232
|
+
# split on each newline
|
|
233
|
+
cmd.stdout.each_line do |line|
|
|
234
|
+
port_info = parse_sockstat_line(line)
|
|
235
|
+
|
|
236
|
+
# push data, if not headerfile
|
|
237
|
+
next unless %w{tcp tcp6 udp udp6}.include?(port_info[:protocol])
|
|
238
|
+
ports.push(port_info)
|
|
239
|
+
end
|
|
240
|
+
ports
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def parse_net_address(net_addr, protocol)
|
|
244
|
+
case protocol
|
|
245
|
+
when 'tcp4', 'udp4'
|
|
246
|
+
# replace * with 0.0.0.0
|
|
247
|
+
net_addr = net_addr.gsub(/^\*:/, '0.0.0.0:') if /^*:(\d+)$/.match(net_addr)
|
|
248
|
+
ip_addr = URI('addr://'+net_addr)
|
|
249
|
+
host = ip_addr.host
|
|
250
|
+
port = ip_addr.port
|
|
251
|
+
when 'tcp6', 'udp6'
|
|
252
|
+
return [] if net_addr == '*:*' # abort for now
|
|
253
|
+
# replace * with 0:0:0:0:0:0:0:0
|
|
254
|
+
net_addr = net_addr.gsub(/^\*:/, '0:0:0:0:0:0:0:0:') if /^*:(\d+)$/.match(net_addr)
|
|
255
|
+
# extract port
|
|
256
|
+
ip6 = /^(\S+):(\d+)$/.match(net_addr)
|
|
257
|
+
ip6addr = ip6[1]
|
|
258
|
+
ip_addr = URI("addr://[#{ip6addr}]:#{ip6[2]}")
|
|
259
|
+
# replace []
|
|
260
|
+
host = ip_addr.host[1..ip_addr.host.size-2]
|
|
261
|
+
port = ip_addr.port
|
|
262
|
+
end
|
|
263
|
+
[host, port]
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def parse_sockstat_line(line)
|
|
267
|
+
# 1 - USER, 2 - COMMAND, 3 - PID, 4 - FD 5 - PROTO, 6 - LOCAL ADDRESS, 7 - FOREIGN ADDRESS
|
|
268
|
+
parsed = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$/.match(line)
|
|
269
|
+
return {} if parsed.nil?
|
|
270
|
+
|
|
271
|
+
# extract ip information
|
|
272
|
+
protocol = parsed[5].downcase
|
|
273
|
+
host, port = parse_net_address(parsed[6], protocol)
|
|
274
|
+
return {} if host.nil? or port.nil?
|
|
275
|
+
|
|
276
|
+
# extract process
|
|
277
|
+
process = parsed[2]
|
|
278
|
+
|
|
279
|
+
# extract PID
|
|
280
|
+
pid = parsed[3]
|
|
281
|
+
pid = pid.to_i if /^\d+$/.match(pid)
|
|
282
|
+
|
|
283
|
+
# map tcp4 and udp4
|
|
284
|
+
protocol = 'tcp' if protocol.eql?('tcp4')
|
|
285
|
+
protocol = 'udp' if protocol.eql?('udp4')
|
|
286
|
+
|
|
287
|
+
# map data
|
|
288
|
+
{
|
|
289
|
+
port: port,
|
|
290
|
+
address: host,
|
|
291
|
+
protocol: protocol,
|
|
292
|
+
process: process,
|
|
293
|
+
pid: pid,
|
|
294
|
+
}
|
|
295
|
+
end
|
|
296
|
+
end
|