inspec 0.14.8 → 0.15.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 +4 -4
- data/CHANGELOG.md +25 -2
- data/bin/inspec +3 -4
- data/examples/inheritance/README.md +19 -0
- data/examples/inheritance/controls/example.rb +11 -0
- data/examples/inheritance/inspec.yml +10 -0
- data/lib/bundles/inspec-compliance/cli.rb +1 -4
- data/lib/bundles/inspec-supermarket/cli.rb +1 -4
- data/lib/inspec/dsl.rb +48 -55
- data/lib/inspec/profile.rb +6 -2
- data/lib/inspec/profile_context.rb +21 -8
- data/lib/inspec/runner.rb +17 -12
- data/lib/inspec/runner_rspec.rb +1 -0
- data/lib/inspec/version.rb +1 -1
- data/lib/resources/apache.rb +20 -18
- data/lib/resources/apache_conf.rb +92 -90
- data/lib/resources/apt.rb +92 -90
- data/lib/resources/audit_policy.rb +35 -33
- data/lib/resources/auditd_conf.rb +41 -39
- data/lib/resources/auditd_rules.rb +155 -153
- data/lib/resources/bond.rb +1 -1
- data/lib/resources/bridge.rb +97 -95
- data/lib/resources/command.rb +47 -45
- data/lib/resources/csv.rb +23 -21
- data/lib/resources/directory.rb +1 -1
- data/lib/resources/etc_group.rb +116 -114
- data/lib/resources/file.rb +1 -1
- data/lib/resources/gem.rb +39 -37
- data/lib/resources/group.rb +100 -98
- data/lib/resources/host.rb +103 -101
- data/lib/resources/inetd_conf.rb +42 -40
- data/lib/resources/ini.rb +15 -13
- data/lib/resources/interface.rb +106 -104
- data/lib/resources/iptables.rb +36 -34
- data/lib/resources/json.rb +64 -62
- data/lib/resources/kernel_module.rb +30 -28
- data/lib/resources/kernel_parameter.rb +44 -42
- data/lib/resources/limits_conf.rb +41 -39
- data/lib/resources/login_def.rb +38 -36
- data/lib/resources/mount.rb +43 -41
- data/lib/resources/mysql.rb +67 -65
- data/lib/resources/mysql_conf.rb +89 -87
- data/lib/resources/mysql_session.rb +46 -44
- data/lib/resources/npm.rb +35 -33
- data/lib/resources/ntp_conf.rb +44 -42
- data/lib/resources/oneget.rb +46 -44
- data/lib/resources/os.rb +22 -20
- data/lib/resources/os_env.rb +47 -45
- data/lib/resources/package.rb +213 -211
- data/lib/resources/parse_config.rb +59 -57
- data/lib/resources/passwd.rb +89 -87
- data/lib/resources/pip.rb +60 -58
- data/lib/resources/port.rb +352 -350
- data/lib/resources/postgres.rb +26 -24
- data/lib/resources/postgres_conf.rb +66 -64
- data/lib/resources/postgres_session.rb +47 -45
- data/lib/resources/processes.rb +56 -54
- data/lib/resources/registry_key.rb +150 -148
- data/lib/resources/script.rb +30 -28
- data/lib/resources/security_policy.rb +56 -54
- data/lib/resources/service.rb +638 -636
- data/lib/resources/shadow.rb +98 -96
- data/lib/resources/ssh_conf.rb +58 -56
- data/lib/resources/user.rb +363 -361
- data/lib/resources/windows_feature.rb +46 -44
- data/lib/resources/xinetd.rb +111 -109
- data/lib/resources/yaml.rb +16 -14
- data/lib/resources/yum.rb +107 -105
- data/lib/utils/base_cli.rb +18 -0
- data/test/helper.rb +2 -2
- data/test/unit/profile_context_test.rb +1 -1
- data/test/unit/resources/file_test.rb +1 -1
- data/test/unit/resources/mount_test.rb +1 -1
- metadata +5 -2
data/lib/resources/port.rb
CHANGED
@@ -17,413 +17,415 @@ require 'utils/parser'
|
|
17
17
|
#
|
18
18
|
# TODO: currently we return local ip only
|
19
19
|
# TODO: improve handling of same port on multiple interfaces
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
20
|
+
module Inspec::Resources
|
21
|
+
class Port < Inspec.resource(1)
|
22
|
+
name 'port'
|
23
|
+
desc "Use the port InSpec audit resource to test basic port properties, such as port, process, if it's listening."
|
24
|
+
example "
|
25
|
+
describe port(80) do
|
26
|
+
it { should be_listening }
|
27
|
+
its('protocols') {should eq ['tcp']}
|
28
|
+
end
|
29
|
+
"
|
30
|
+
|
31
|
+
def initialize(ip = nil, port) # rubocop:disable OptionalArguments
|
32
|
+
@ip = ip
|
33
|
+
@port = port
|
34
|
+
@port_manager = nil
|
35
|
+
@cache = nil
|
36
|
+
os = inspec.os
|
37
|
+
if os.linux?
|
38
|
+
@port_manager = LinuxPorts.new(inspec)
|
39
|
+
elsif %w{darwin aix}.include?(os[:family])
|
40
|
+
# AIX: see http://www.ibm.com/developerworks/aix/library/au-lsof.html#resources
|
41
|
+
# and https://www-01.ibm.com/marketing/iwm/iwm/web/reg/pick.do?source=aixbp
|
42
|
+
# Darwin: https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man8/lsof.8.html
|
43
|
+
@port_manager = LsofPorts.new(inspec)
|
44
|
+
elsif os.windows?
|
45
|
+
@port_manager = WindowsPorts.new(inspec)
|
46
|
+
elsif ['freebsd'].include?(os[:family])
|
47
|
+
@port_manager = FreeBsdPorts.new(inspec)
|
48
|
+
elsif os.solaris?
|
49
|
+
@port_manager = SolarisPorts.new(inspec)
|
50
|
+
else
|
51
|
+
return skip_resource 'The `port` resource is not supported on your OS yet.'
|
52
|
+
end
|
51
53
|
end
|
52
|
-
end
|
53
54
|
|
54
|
-
|
55
|
-
|
56
|
-
|
55
|
+
def listening?(_protocol = nil, _local_address = nil)
|
56
|
+
info.size > 0
|
57
|
+
end
|
57
58
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
59
|
+
def protocols
|
60
|
+
res = info.map { |x| x[:protocol] }.uniq.compact
|
61
|
+
res.size > 0 ? res : nil
|
62
|
+
end
|
62
63
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
64
|
+
def processes
|
65
|
+
res = info.map { |x| x[:process] }.uniq.compact
|
66
|
+
res.size > 0 ? res : nil
|
67
|
+
end
|
67
68
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
69
|
+
def pids
|
70
|
+
res = info.map { |x| x[:pid] }.uniq.compact
|
71
|
+
res.size > 0 ? res : nil
|
72
|
+
end
|
72
73
|
|
73
|
-
|
74
|
-
|
75
|
-
|
74
|
+
def to_s
|
75
|
+
"Port #{@port}"
|
76
|
+
end
|
76
77
|
|
77
|
-
|
78
|
+
private
|
78
79
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
80
|
+
def info
|
81
|
+
return @cache if !@cache.nil?
|
82
|
+
# abort if os detection has not worked
|
83
|
+
return @cache = [] if @port_manager.nil?
|
84
|
+
# query ports
|
85
|
+
ports = @port_manager.info || []
|
86
|
+
@cache = ports.select { |p| p[:port] == @port && (!@ip || p[:address] == @ip) }
|
87
|
+
end
|
86
88
|
end
|
87
|
-
end
|
88
89
|
|
89
|
-
# implements an info method and returns all ip adresses and protocols for
|
90
|
-
# each port
|
91
|
-
# [{
|
92
|
-
# port: 22,
|
93
|
-
# address: '0.0.0.0'
|
94
|
-
# protocol: 'tcp'
|
95
|
-
# },
|
96
|
-
# {
|
97
|
-
# port: 22,
|
98
|
-
# address: '::'
|
99
|
-
# protocol: 'tcp6'
|
100
|
-
# }]
|
101
|
-
class PortsInfo
|
102
|
-
|
103
|
-
|
104
|
-
|
90
|
+
# implements an info method and returns all ip adresses and protocols for
|
91
|
+
# each port
|
92
|
+
# [{
|
93
|
+
# port: 22,
|
94
|
+
# address: '0.0.0.0'
|
95
|
+
# protocol: 'tcp'
|
96
|
+
# },
|
97
|
+
# {
|
98
|
+
# port: 22,
|
99
|
+
# address: '::'
|
100
|
+
# protocol: 'tcp6'
|
101
|
+
# }]
|
102
|
+
class PortsInfo
|
103
|
+
attr_reader :inspec
|
104
|
+
def initialize(inspec)
|
105
|
+
@inspec = inspec
|
106
|
+
end
|
105
107
|
end
|
106
|
-
end
|
107
108
|
|
108
|
-
# TODO: Add UDP infromation Get-NetUDPEndpoint
|
109
|
-
# TODO: currently Windows only supports tcp ports
|
110
|
-
# TODO: Get-NetTCPConnection does not return PIDs
|
111
|
-
# TODO: double-check output with 'netstat -ano'
|
112
|
-
# @see https://connect.microsoft.com/PowerShell/feedback/details/1349420/get-nettcpconnection-does-not-show-processid
|
113
|
-
class WindowsPorts < PortsInfo
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
109
|
+
# TODO: Add UDP infromation Get-NetUDPEndpoint
|
110
|
+
# TODO: currently Windows only supports tcp ports
|
111
|
+
# TODO: Get-NetTCPConnection does not return PIDs
|
112
|
+
# TODO: double-check output with 'netstat -ano'
|
113
|
+
# @see https://connect.microsoft.com/PowerShell/feedback/details/1349420/get-nettcpconnection-does-not-show-processid
|
114
|
+
class WindowsPorts < PortsInfo
|
115
|
+
def info
|
116
|
+
# get all port information
|
117
|
+
cmd = inspec.command('Get-NetTCPConnection | Select-Object -Property State, Caption, Description, LocalAddress, LocalPort, RemoteAddress, RemotePort, DisplayName, Status | ConvertTo-Json')
|
118
|
+
|
119
|
+
begin
|
120
|
+
ports = JSON.parse(cmd.stdout)
|
121
|
+
rescue JSON::ParserError => _e
|
122
|
+
return nil
|
123
|
+
end
|
123
124
|
|
124
|
-
|
125
|
+
return nil if ports.nil?
|
125
126
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
127
|
+
ports.map { |x|
|
128
|
+
{
|
129
|
+
port: x['LocalPort'],
|
130
|
+
address: x['LocalAddress'],
|
131
|
+
protocol: 'tcp',
|
132
|
+
process: nil,
|
133
|
+
pid: nil,
|
134
|
+
}
|
133
135
|
}
|
134
|
-
|
136
|
+
end
|
135
137
|
end
|
136
|
-
end
|
137
|
-
|
138
|
-
# extracts udp and tcp ports from the lsof command
|
139
|
-
class LsofPorts < PortsInfo
|
140
|
-
attr_reader :lsof
|
141
138
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
end
|
139
|
+
# extracts udp and tcp ports from the lsof command
|
140
|
+
class LsofPorts < PortsInfo
|
141
|
+
attr_reader :lsof
|
146
142
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
# check that lsof is available, otherwise fail
|
151
|
-
fail 'Please ensure `lsof` is available on the machine.' if !inspec.command(@lsof.to_s).exist?
|
152
|
-
|
153
|
-
# -F p=pid, c=command, P=protocol name, t=type, n=internet addresses
|
154
|
-
# see 'OUTPUT FOR OTHER PROGRAMS' in LSOF(8)
|
155
|
-
lsof_cmd = inspec.command("#{@lsof} -nP -i -FpctPn")
|
156
|
-
return nil if lsof_cmd.exit_status.to_i != 0
|
157
|
-
|
158
|
-
# map to desired return struct
|
159
|
-
lsof_parser(lsof_cmd).each do |process, port_ids|
|
160
|
-
pid, cmd = process.split(':')
|
161
|
-
port_ids.each do |port_str|
|
162
|
-
# should not break on ipv6 addresses
|
163
|
-
ipv, proto, port, host = port_str.split(':', 4)
|
164
|
-
ports.push({ port: port.to_i,
|
165
|
-
address: host,
|
166
|
-
protocol: ipv == 'ipv6' ? proto + '6' : proto,
|
167
|
-
process: cmd,
|
168
|
-
pid: pid.to_i })
|
169
|
-
end
|
143
|
+
def initialize(inspec, lsofpath = nil)
|
144
|
+
@lsof = lsofpath || 'lsof'
|
145
|
+
super(inspec)
|
170
146
|
end
|
171
147
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
line.chomp!
|
195
|
-
key = line.slice!(0)
|
196
|
-
case key
|
197
|
-
when 'p'
|
198
|
-
proc_id = line
|
199
|
-
port_id = nil
|
200
|
-
when 'c'
|
201
|
-
proc_id += ':' + line
|
202
|
-
when 't'
|
203
|
-
port_id = line.downcase
|
204
|
-
when 'P'
|
205
|
-
port_id += ':' + line.downcase
|
206
|
-
when 'n'
|
207
|
-
src, dst = line.split('->')
|
208
|
-
|
209
|
-
# skip active comm streams
|
210
|
-
next if dst
|
211
|
-
|
212
|
-
host, port = /^(\S+):(\d+|\*)$/.match(src)[1, 2]
|
213
|
-
|
214
|
-
# skip channels from port 0 - what does this mean?
|
215
|
-
next if port == '*'
|
216
|
-
|
217
|
-
# create new array stub if !exist?
|
218
|
-
procs[proc_id] = [] unless procs.key?(proc_id)
|
219
|
-
|
220
|
-
# change address '*' to zero
|
221
|
-
host = (port_id =~ /^ipv6:/) ? '[::]' : '0.0.0.0' if host == '*'
|
222
|
-
# entrust URI to scrub the host and port
|
223
|
-
begin
|
224
|
-
uri = URI("addr://#{host}:#{port}")
|
225
|
-
uri.host && uri.port
|
226
|
-
rescue => e
|
227
|
-
warn "could not parse URI 'addr://#{host}:#{port}' - #{e}"
|
228
|
-
next
|
148
|
+
def info
|
149
|
+
ports = []
|
150
|
+
|
151
|
+
# check that lsof is available, otherwise fail
|
152
|
+
fail 'Please ensure `lsof` is available on the machine.' if !inspec.command(@lsof.to_s).exist?
|
153
|
+
|
154
|
+
# -F p=pid, c=command, P=protocol name, t=type, n=internet addresses
|
155
|
+
# see 'OUTPUT FOR OTHER PROGRAMS' in LSOF(8)
|
156
|
+
lsof_cmd = inspec.command("#{@lsof} -nP -i -FpctPn")
|
157
|
+
return nil if lsof_cmd.exit_status.to_i != 0
|
158
|
+
|
159
|
+
# map to desired return struct
|
160
|
+
lsof_parser(lsof_cmd).each do |process, port_ids|
|
161
|
+
pid, cmd = process.split(':')
|
162
|
+
port_ids.each do |port_str|
|
163
|
+
# should not break on ipv6 addresses
|
164
|
+
ipv, proto, port, host = port_str.split(':', 4)
|
165
|
+
ports.push({ port: port.to_i,
|
166
|
+
address: host,
|
167
|
+
protocol: ipv == 'ipv6' ? proto + '6' : proto,
|
168
|
+
process: cmd,
|
169
|
+
pid: pid.to_i })
|
229
170
|
end
|
171
|
+
end
|
230
172
|
|
231
|
-
|
232
|
-
|
233
|
-
port_id += ':' + port + ':' + host.gsub(/^\[|\]$/, '')
|
173
|
+
ports
|
174
|
+
end
|
234
175
|
|
235
|
-
|
236
|
-
|
176
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
177
|
+
# rubocop:disable Metrics/AbcSize
|
178
|
+
def lsof_parser(lsof_cmd)
|
179
|
+
procs = {}
|
180
|
+
# build this with formatted output (-F) from lsof
|
181
|
+
# procs = {
|
182
|
+
# '123:sshd' => [
|
183
|
+
# 'ipv4:tcp:22:127.0.0.1',
|
184
|
+
# 'ipv6:tcp:22:::1',
|
185
|
+
# 'ipv4:tcp:*',
|
186
|
+
# 'ipv6:tcp:*',
|
187
|
+
# ],
|
188
|
+
# '456:ntpd' => [
|
189
|
+
# 'ipv4:udp:123:*',
|
190
|
+
# 'ipv6:udp:123:*',
|
191
|
+
# ]
|
192
|
+
# }
|
193
|
+
proc_id = port_id = nil
|
194
|
+
lsof_cmd.stdout.each_line do |line|
|
195
|
+
line.chomp!
|
196
|
+
key = line.slice!(0)
|
197
|
+
case key
|
198
|
+
when 'p'
|
199
|
+
proc_id = line
|
200
|
+
port_id = nil
|
201
|
+
when 'c'
|
202
|
+
proc_id += ':' + line
|
203
|
+
when 't'
|
204
|
+
port_id = line.downcase
|
205
|
+
when 'P'
|
206
|
+
port_id += ':' + line.downcase
|
207
|
+
when 'n'
|
208
|
+
src, dst = line.split('->')
|
209
|
+
|
210
|
+
# skip active comm streams
|
211
|
+
next if dst
|
212
|
+
|
213
|
+
host, port = /^(\S+):(\d+|\*)$/.match(src)[1, 2]
|
214
|
+
|
215
|
+
# skip channels from port 0 - what does this mean?
|
216
|
+
next if port == '*'
|
217
|
+
|
218
|
+
# create new array stub if !exist?
|
219
|
+
procs[proc_id] = [] unless procs.key?(proc_id)
|
220
|
+
|
221
|
+
# change address '*' to zero
|
222
|
+
host = (port_id =~ /^ipv6:/) ? '[::]' : '0.0.0.0' if host == '*'
|
223
|
+
# entrust URI to scrub the host and port
|
224
|
+
begin
|
225
|
+
uri = URI("addr://#{host}:#{port}")
|
226
|
+
uri.host && uri.port
|
227
|
+
rescue => e
|
228
|
+
warn "could not parse URI 'addr://#{host}:#{port}' - #{e}"
|
229
|
+
next
|
230
|
+
end
|
231
|
+
|
232
|
+
# e.g. 'ipv4:tcp:22:127.0.0.1'
|
233
|
+
# strip ipv6 squares for inspec
|
234
|
+
port_id += ':' + port + ':' + host.gsub(/^\[|\]$/, '')
|
235
|
+
|
236
|
+
# lsof will give us another port unless it's done
|
237
|
+
procs[proc_id] << port_id
|
238
|
+
end
|
237
239
|
end
|
238
|
-
end
|
239
240
|
|
240
|
-
|
241
|
+
procs
|
242
|
+
end
|
241
243
|
end
|
242
|
-
end
|
243
244
|
|
244
|
-
# extract port information from netstat
|
245
|
-
class LinuxPorts < PortsInfo
|
246
|
-
|
247
|
-
|
248
|
-
|
245
|
+
# extract port information from netstat
|
246
|
+
class LinuxPorts < PortsInfo
|
247
|
+
def info
|
248
|
+
cmd = inspec.command('netstat -tulpen')
|
249
|
+
return nil if cmd.exit_status.to_i != 0
|
249
250
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
251
|
+
ports = []
|
252
|
+
# parse all lines
|
253
|
+
cmd.stdout.each_line do |line|
|
254
|
+
port_info = parse_netstat_line(line)
|
254
255
|
|
255
|
-
|
256
|
-
|
257
|
-
|
256
|
+
# only push protocols we are interested in
|
257
|
+
next unless %w{tcp tcp6 udp udp6}.include?(port_info[:protocol])
|
258
|
+
ports.push(port_info)
|
259
|
+
end
|
260
|
+
ports
|
258
261
|
end
|
259
|
-
ports
|
260
|
-
end
|
261
262
|
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
263
|
+
def parse_net_address(net_addr, protocol)
|
264
|
+
if protocol.eql?('tcp6') || protocol.eql?('udp6')
|
265
|
+
# prep for URI parsing, parse ip6 port
|
266
|
+
ip6 = /^(\S+):(\d+)$/.match(net_addr)
|
267
|
+
ip6addr = ip6[1]
|
268
|
+
ip6addr = '::' if ip6addr =~ /^:::$/
|
269
|
+
# build uri
|
270
|
+
ip_addr = URI("addr://[#{ip6addr}]:#{ip6[2]}")
|
271
|
+
# replace []
|
272
|
+
host = ip_addr.host[1..ip_addr.host.size-2]
|
273
|
+
else
|
274
|
+
ip_addr = URI('addr://'+net_addr)
|
275
|
+
host = ip_addr.host
|
276
|
+
end
|
276
277
|
|
277
|
-
|
278
|
+
port = ip_addr.port
|
278
279
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
280
|
+
[host, port]
|
281
|
+
rescue URI::InvalidURIError => e
|
282
|
+
warn "Could not parse #{net_addr}, #{e}"
|
283
|
+
nil
|
284
|
+
end
|
284
285
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
286
|
+
def parse_netstat_line(line)
|
287
|
+
# parse each line
|
288
|
+
# 1 - Proto, 2 - Recv-Q, 3 - Send-Q, 4 - Local Address, 5 - Foreign Address, 6 - State, 7 - Inode, 8 - PID/Program name
|
289
|
+
parsed = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)?\s+(\S+)\s+(\S+)\s+(\S+)/.match(line)
|
289
290
|
|
290
|
-
|
291
|
+
return {} if parsed.nil? || line.match(/^proto/i)
|
291
292
|
|
292
|
-
|
293
|
-
|
293
|
+
# parse ip4 and ip6 addresses
|
294
|
+
protocol = parsed[1].downcase
|
294
295
|
|
295
|
-
|
296
|
-
|
296
|
+
# detect protocol if not provided
|
297
|
+
protocol += '6' if parsed[4].count(':') > 1 && %w{tcp udp}.include?(protocol)
|
297
298
|
|
298
|
-
|
299
|
-
|
299
|
+
# extract host and port information
|
300
|
+
host, port = parse_net_address(parsed[4], protocol)
|
300
301
|
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
302
|
+
# extract PID
|
303
|
+
process = parsed[9].split('/')
|
304
|
+
pid = process[0]
|
305
|
+
pid = pid.to_i if pid =~ /^\d+$/
|
306
|
+
process = process[1]
|
306
307
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
308
|
+
# map data
|
309
|
+
{
|
310
|
+
port: port,
|
311
|
+
address: host,
|
312
|
+
protocol: protocol,
|
313
|
+
process: process,
|
314
|
+
pid: pid,
|
315
|
+
}
|
316
|
+
end
|
315
317
|
end
|
316
|
-
end
|
317
318
|
|
318
|
-
# extracts information from sockstat
|
319
|
-
class FreeBsdPorts < PortsInfo
|
320
|
-
|
321
|
-
|
322
|
-
|
319
|
+
# extracts information from sockstat
|
320
|
+
class FreeBsdPorts < PortsInfo
|
321
|
+
def info
|
322
|
+
cmd = inspec.command('sockstat -46l')
|
323
|
+
return nil if cmd.exit_status.to_i != 0
|
323
324
|
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
325
|
+
ports = []
|
326
|
+
# split on each newline
|
327
|
+
cmd.stdout.each_line do |line|
|
328
|
+
port_info = parse_sockstat_line(line)
|
328
329
|
|
329
|
-
|
330
|
-
|
331
|
-
|
330
|
+
# push data, if not headerfile
|
331
|
+
next unless %w{tcp tcp6 udp udp6}.include?(port_info[:protocol])
|
332
|
+
ports.push(port_info)
|
333
|
+
end
|
334
|
+
ports
|
332
335
|
end
|
333
|
-
ports
|
334
|
-
end
|
335
336
|
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
337
|
+
def parse_net_address(net_addr, protocol)
|
338
|
+
case protocol
|
339
|
+
when 'tcp4', 'udp4', 'tcp', 'udp'
|
340
|
+
# replace * with 0.0.0.0
|
341
|
+
net_addr = net_addr.gsub(/^\*:/, '0.0.0.0:') if net_addr =~ /^*:(\d+)$/
|
342
|
+
ip_addr = URI('addr://'+net_addr)
|
343
|
+
host = ip_addr.host
|
344
|
+
port = ip_addr.port
|
345
|
+
when 'tcp6', 'udp6'
|
346
|
+
return [] if net_addr == '*:*' # abort for now
|
347
|
+
# replace * with 0:0:0:0:0:0:0:0
|
348
|
+
net_addr = net_addr.gsub(/^\*:/, '0:0:0:0:0:0:0:0:') if net_addr =~ /^*:(\d+)$/
|
349
|
+
# extract port
|
350
|
+
ip6 = /^(\S+):(\d+)$/.match(net_addr)
|
351
|
+
ip6addr = ip6[1]
|
352
|
+
ip_addr = URI("addr://[#{ip6addr}]:#{ip6[2]}")
|
353
|
+
# replace []
|
354
|
+
host = ip_addr.host[1..ip_addr.host.size-2]
|
355
|
+
port = ip_addr.port
|
356
|
+
end
|
357
|
+
[host, port]
|
358
|
+
rescue URI::InvalidURIError => e
|
359
|
+
warn "Could not parse #{net_addr}, #{e}"
|
360
|
+
nil
|
355
361
|
end
|
356
|
-
[host, port]
|
357
|
-
rescue URI::InvalidURIError => e
|
358
|
-
warn "Could not parse #{net_addr}, #{e}"
|
359
|
-
nil
|
360
|
-
end
|
361
|
-
|
362
|
-
def parse_sockstat_line(line)
|
363
|
-
# 1 - USER, 2 - COMMAND, 3 - PID, 4 - FD 5 - PROTO, 6 - LOCAL ADDRESS, 7 - FOREIGN ADDRESS
|
364
|
-
parsed = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$/.match(line)
|
365
|
-
return {} if parsed.nil?
|
366
|
-
|
367
|
-
# extract ip information
|
368
|
-
protocol = parsed[5].downcase
|
369
|
-
host, port = parse_net_address(parsed[6], protocol)
|
370
|
-
return {} if host.nil? or port.nil?
|
371
|
-
|
372
|
-
# extract process
|
373
|
-
process = parsed[2]
|
374
|
-
|
375
|
-
# extract PID
|
376
|
-
pid = parsed[3]
|
377
|
-
pid = pid.to_i if pid =~ /^\d+$/
|
378
|
-
|
379
|
-
# map tcp4 and udp4
|
380
|
-
protocol = 'tcp' if protocol.eql?('tcp4')
|
381
|
-
protocol = 'udp' if protocol.eql?('udp4')
|
382
|
-
|
383
|
-
# map data
|
384
|
-
{
|
385
|
-
port: port,
|
386
|
-
address: host,
|
387
|
-
protocol: protocol,
|
388
|
-
process: process,
|
389
|
-
pid: pid,
|
390
|
-
}
|
391
|
-
end
|
392
|
-
end
|
393
362
|
|
394
|
-
|
395
|
-
|
363
|
+
def parse_sockstat_line(line)
|
364
|
+
# 1 - USER, 2 - COMMAND, 3 - PID, 4 - FD 5 - PROTO, 6 - LOCAL ADDRESS, 7 - FOREIGN ADDRESS
|
365
|
+
parsed = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$/.match(line)
|
366
|
+
return {} if parsed.nil?
|
396
367
|
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
368
|
+
# extract ip information
|
369
|
+
protocol = parsed[5].downcase
|
370
|
+
host, port = parse_net_address(parsed[6], protocol)
|
371
|
+
return {} if host.nil? or port.nil?
|
401
372
|
|
402
|
-
|
403
|
-
|
373
|
+
# extract process
|
374
|
+
process = parsed[2]
|
404
375
|
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
}
|
376
|
+
# extract PID
|
377
|
+
pid = parsed[3]
|
378
|
+
pid = pid.to_i if pid =~ /^\d+$/
|
409
379
|
|
410
|
-
|
411
|
-
|
412
|
-
protocol =
|
413
|
-
local_addr = val['local-address']
|
380
|
+
# map tcp4 and udp4
|
381
|
+
protocol = 'tcp' if protocol.eql?('tcp4')
|
382
|
+
protocol = 'udp' if protocol.eql?('udp4')
|
414
383
|
|
415
|
-
#
|
416
|
-
# the last . to :
|
417
|
-
local_addr[local_addr.rindex('.')] = ':'
|
418
|
-
host, port = parse_net_address(local_addr, protocol)
|
384
|
+
# map data
|
419
385
|
{
|
420
386
|
port: port,
|
421
387
|
address: host,
|
422
388
|
protocol: protocol,
|
423
|
-
process:
|
424
|
-
pid:
|
389
|
+
process: process,
|
390
|
+
pid: pid,
|
391
|
+
}
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
class SolarisPorts < FreeBsdPorts
|
396
|
+
include SolarisNetstatParser
|
397
|
+
|
398
|
+
def info
|
399
|
+
# extract all port info
|
400
|
+
cmd = inspec.command('netstat -an -f inet -f inet6')
|
401
|
+
return nil if cmd.exit_status.to_i != 0
|
402
|
+
|
403
|
+
# parse the content
|
404
|
+
netstat_ports = parse_netstat(cmd.stdout)
|
405
|
+
|
406
|
+
# filter all ports, where we listen
|
407
|
+
listen = netstat_ports.select { |val|
|
408
|
+
!val['state'].nil? && 'listen'.casecmp(val['state']) == 0
|
409
|
+
}
|
410
|
+
|
411
|
+
# map the data
|
412
|
+
ports = listen.map { |val|
|
413
|
+
protocol = val['protocol']
|
414
|
+
local_addr = val['local-address']
|
415
|
+
|
416
|
+
# solaris uses 127.0.0.1.57455 instead 127.0.0.1:57455, lets convert the
|
417
|
+
# the last . to :
|
418
|
+
local_addr[local_addr.rindex('.')] = ':'
|
419
|
+
host, port = parse_net_address(local_addr, protocol)
|
420
|
+
{
|
421
|
+
port: port,
|
422
|
+
address: host,
|
423
|
+
protocol: protocol,
|
424
|
+
process: nil, # we do not have pid on solaris
|
425
|
+
pid: nil, # we do not have pid on solaris
|
426
|
+
}
|
425
427
|
}
|
426
|
-
|
427
|
-
|
428
|
+
ports
|
429
|
+
end
|
428
430
|
end
|
429
431
|
end
|