inspec 0.14.8 → 0.15.0
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 -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
|