mirror of
https://github.com/inspec/inspec
synced 2024-11-30 08:30:39 +00:00
7451917223
For larger processes, Busybox's ps displays the vsz and rss columns in megabytes or gigabytes, with no option I've found to override the behavior. This change updates the process regex to account for that and converts the values to kilobytes so they can still be cast as integers. Signed-off-by: Jonathan Hartman <j@hartman.io>
218 lines
6.7 KiB
Ruby
218 lines
6.7 KiB
Ruby
# encoding: utf-8
|
|
# copyright: 2015, Vulcano Security GmbH
|
|
|
|
require 'utils/filter'
|
|
require 'ostruct'
|
|
|
|
module Inspec::Resources
|
|
class Processes < Inspec.resource(1)
|
|
name 'processes'
|
|
supports platform: 'unix'
|
|
supports platform: 'windows'
|
|
desc 'Use the processes InSpec audit resource to test properties for programs that are running on the system.'
|
|
example "
|
|
describe processes('mysqld') do
|
|
its('entries.length') { should eq 1 }
|
|
its('users') { should eq ['mysql'] }
|
|
its('states') { should include 'S' }
|
|
end
|
|
|
|
describe processes(/.+/).where { label != 'unconfined' && pid < 1000 } do
|
|
its('users') { should cmp [] }
|
|
end
|
|
|
|
# work with all processes
|
|
describe processes do
|
|
its('entries.length') { should be <= 100 }
|
|
end
|
|
"
|
|
|
|
def initialize(grep = /.*/)
|
|
@grep = grep
|
|
# turn into a regexp if it isn't one yet
|
|
if grep.class == String
|
|
# if windows ignore case as we can't make up our minds
|
|
if inspec.os.windows?
|
|
grep = '(?i)' + grep
|
|
else
|
|
grep = '(/[^/]*)*' + grep unless grep[0] == '/'
|
|
grep = '^' + grep + '(\s|$)'
|
|
end
|
|
grep = Regexp.new(grep)
|
|
end
|
|
|
|
all_cmds = ps_axo
|
|
@list = all_cmds.find_all do |hm|
|
|
hm[:command] =~ grep
|
|
end
|
|
end
|
|
|
|
def exists?
|
|
!@list.empty?
|
|
end
|
|
|
|
def to_s
|
|
"Processes #{@grep.class == String ? @grep : @grep.inspect}"
|
|
end
|
|
|
|
def list
|
|
warn '[DEPRECATION] `processes.list` is deprecated. Please use `processes.entries` instead. It will be removed in version 2.0.0.'
|
|
@list
|
|
end
|
|
|
|
filter = FilterTable.create
|
|
filter.register_column(:labels, field: 'label')
|
|
.register_column(:pids, field: 'pid')
|
|
.register_column(:cpus, field: 'cpu')
|
|
.register_column(:mem, field: 'mem')
|
|
.register_column(:vsz, field: 'vsz')
|
|
.register_column(:rss, field: 'rss')
|
|
.register_column(:tty, field: 'tty')
|
|
.register_column(:states, field: 'stat')
|
|
.register_column(:start, field: 'start')
|
|
.register_column(:time, field: 'time')
|
|
.register_column(:users, field: 'user')
|
|
.register_column(:commands, field: 'command')
|
|
.install_filter_methods_on_resource(self, :filtered_processes)
|
|
|
|
private
|
|
|
|
def filtered_processes
|
|
@list
|
|
end
|
|
|
|
def ps_axo
|
|
os = inspec.os
|
|
|
|
if os.linux?
|
|
command, regex, field_map = ps_configuration_for_linux
|
|
elsif os.windows?
|
|
command = '$Proc = Get-Process -IncludeUserName | Where-Object {$_.Path -ne $null } | Select-Object PriorityClass,Id,CPU,PM,VirtualMemorySize,NPM,SessionId,Responding,StartTime,TotalProcessorTime,UserName,Path | ConvertTo-Csv -NoTypeInformation;$Proc.Replace("""","").Replace("`r`n","`n")'
|
|
# Wanted to use /(?:^|,)([^,]*)/; works on rubular.com not sure why here?
|
|
regex = /^(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+)$/
|
|
field_map = {
|
|
pid: 2,
|
|
cpu: 3,
|
|
mem: 4,
|
|
vsz: 5,
|
|
rss: 6,
|
|
tty: 7,
|
|
stat: 8,
|
|
start: 9,
|
|
time: 10,
|
|
user: 11,
|
|
command: 12,
|
|
}
|
|
else
|
|
command = 'ps axo pid,pcpu,pmem,vsz,rss,tty,stat,start,time,user,command'
|
|
regex = /^\s*([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+(.*)$/
|
|
field_map = {
|
|
pid: 1,
|
|
cpu: 2,
|
|
mem: 3,
|
|
vsz: 4,
|
|
rss: 5,
|
|
tty: 6,
|
|
stat: 7,
|
|
start: 8,
|
|
time: 9,
|
|
user: 10,
|
|
command: 11,
|
|
}
|
|
end
|
|
build_process_list(command, regex, field_map)
|
|
end
|
|
|
|
def ps_configuration_for_linux
|
|
if busybox_ps?
|
|
command = 'ps -o pid,vsz,rss,tty,stat,time,ruser,args'
|
|
regex = /^\s*(\d+)\s+(\d+(?:\.\d+)?[gm]?)\s+(\d+(?:\.\d+)?[gm]?)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/
|
|
field_map = {
|
|
pid: 1,
|
|
vsz: 2,
|
|
rss: 3,
|
|
tty: 4,
|
|
stat: 5,
|
|
time: 6,
|
|
user: 7,
|
|
command: 8,
|
|
}
|
|
else
|
|
command = 'ps axo label,pid,pcpu,pmem,vsz,rss,tty,stat,start,time,user:32,command'
|
|
regex = /^(.+?)\s+(\d+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+(\w{3} \d{2}|\d{2}:\d{2}:\d{2})\s+([^ ]+)\s+([^ ]+)\s+(.*)$/
|
|
field_map = {
|
|
label: 1,
|
|
pid: 2,
|
|
cpu: 3,
|
|
mem: 4,
|
|
vsz: 5,
|
|
rss: 6,
|
|
tty: 7,
|
|
stat: 8,
|
|
start: 9,
|
|
time: 10,
|
|
user: 11,
|
|
command: 12,
|
|
}
|
|
end
|
|
|
|
[command, regex, field_map]
|
|
end
|
|
|
|
def busybox_ps?
|
|
@busybox_ps ||= inspec.command('ps --help').stderr.include?('BusyBox')
|
|
end
|
|
|
|
def convert_to_kilobytes(param)
|
|
return param.to_i unless param.is_a?(String)
|
|
|
|
if param.end_with?('g')
|
|
(param[0..-2].to_f * 1024 * 1024).to_i
|
|
elsif param.end_with?('m')
|
|
(param[0..-2].to_f * 1024).to_i
|
|
else
|
|
param.to_i
|
|
end
|
|
end
|
|
|
|
def build_process_list(command, regex, field_map)
|
|
cmd = inspec.command(command)
|
|
all = cmd.stdout.split("\n")[1..-1]
|
|
return [] if all.nil?
|
|
|
|
# map all the process lines into match objects, fetch the available fields,
|
|
# and then build an OpenStruct of the process data for each process
|
|
all.map do |line|
|
|
line = line.match(regex)
|
|
|
|
# skip this line if we couldn't match the regular expression
|
|
next if line.nil?
|
|
|
|
# skip this entry if there's no command for this line
|
|
next if line[field_map[:command]].nil?
|
|
|
|
# build a hash of process data that we'll turn into a struct for FilterTable
|
|
process_data = {}
|
|
[:label, :pid, :cpu, :mem, :vsz, :rss, :tty, :stat, :start, :time, :user, :command].each do |param|
|
|
# not all operating systems support all fields, so skip the field if we don't have it
|
|
process_data[param] = line[field_map[param]] if field_map.key?(param)
|
|
end
|
|
|
|
# ensure pid, vsz, and rss are integers for backward compatibility
|
|
process_data[:pid] = process_data[:pid].to_i if process_data.key?(:pid)
|
|
|
|
# some ps variants (*cough* busybox) display vsz and rss as human readable MB or GB
|
|
[:vsz, :rss].each do |param|
|
|
next unless process_data.key?(param)
|
|
process_data[param] = convert_to_kilobytes(process_data[param])
|
|
end
|
|
|
|
# strip any newlines off the command
|
|
process_data[:command].strip!
|
|
|
|
# return an OpenStruct of the process for future use by FilterTable
|
|
OpenStruct.new(process_data)
|
|
end.compact
|
|
end
|
|
end
|
|
end
|