diff --git a/docs/resources/processes.md.erb b/docs/resources/processes.md.erb index c3f59f97c..69b48880f 100644 --- a/docs/resources/processes.md.erb +++ b/docs/resources/processes.md.erb @@ -60,18 +60,33 @@ The following examples show how to use this InSpec audit resource. its('list.length') { should eq 1 } end -### Test if the init process is owned by the root user +### Test if the process is owned by a specifc user describe processes('init') do its('users') { should eq ['root'] } end + describe processes('winlogon') do + its('users') { should cmp "NT AUTHORITY\\SYSTEM" } + end + + ### Test if a high-priority process is running - describe processes('some_process') do + describe processes('linux_process') do its('states') { should eq ['R<'] } end + describe processes('windows_process') do + its('labels') { should cmp "High" } + end + +### Test if a process exists on the system + + describe processes('some_process') do + it { should exist } + end + ### Test for a process using a specific Regexp If the process name is too common for a string to uniquely find it, @@ -81,3 +96,28 @@ needed. describe processes(Regexp.new("/usr/local/bin/swap -d")) do its('list.length') { should eq 1 } end + +### Notes for auditing Windows systems + +Sometimes with system properties there isn't a direct comparison between different operating systems. +Most of the `property_name`'s do align between the different OS's. + +There are however some exception's, for example, within linux `states` offers multiple properties. +Windows doesn't have direct comparison that is a single property so instead `states` is mapped to the property of `Responding`, This is a boolean true/false flag to help determine if the process is hung. + +Below is a mapping table to help you understand what property the unix field maps to the windows `Get-Process` Property + +| *unix ps field* | *windows PowerShell Property* | +|:---------------:|:-----------------------------:| +|labels |PriorityClass| +|pids |Id| +|cpus |CPU| +|mem |PM| +|vsz |VirtualMemorySize| +|rss |NPM| +|tty |SessionId| +|states |Responding| +|start |StartTime| +|time |TotalProcessorTime| +|users |UserName| +|commands |Path| diff --git a/lib/resources/processes.rb b/lib/resources/processes.rb index 8de7c0da6..ad9fe6a71 100644 --- a/lib/resources/processes.rb +++ b/lib/resources/processes.rb @@ -25,15 +25,24 @@ module Inspec::Resources @grep = grep # turn into a regexp if it isn't one yet if grep.class == String - grep = '(/[^/]*)*' + grep if grep[0] != '/' - grep = Regexp.new('^' + grep + '(\s|$)') + # 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 - return skip_resource 'The `processes` resource is not supported on your OS yet.' if inspec.os.windows? + def exists? + !@list.empty? end def to_s @@ -74,6 +83,10 @@ module Inspec::Resources if os.linux? command = 'ps axo label,pid,pcpu,pmem,vsz,rss,tty,stat,start,time,user:32,command' regex = /^([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+(\w{3} \d{2}|\d{2}:\d{2}:\d{2})\s+([^ ]+)\s+([^ ]+)\s+(.*)$/ + 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 = /^(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+)$/ 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+(.*)$/ @@ -95,7 +108,7 @@ module Inspec::Resources end.compact lines.map do |m| a = m.to_a[1..-1] # grab all matching groups - a.unshift(nil) unless os.linux? + a.unshift(nil) unless os.linux? || os.windows? a[1] = a[1].to_i a[4] = a[4].to_i a[5] = a[5].to_i diff --git a/test/helper.rb b/test/helper.rb index 145b2999a..a24d9f617 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -303,10 +303,10 @@ class MockLoader 'crontab -l' => cmd.call('crontab-root'), # crontab display for non-current user 'crontab -l -u foouser' => cmd.call('crontab-foouser'), - # zfs output for dataset tank/tmp - '/sbin/zfs get -Hp all tank/tmp' => cmd.call('zfs-get-all-tank-tmp'), - # zfs output for pool tank - '/sbin/zpool get -Hp all tank' => cmd.call('zpool-get-all-tank'), + # zfs output for dataset tank/tmp + '/sbin/zfs get -Hp all tank/tmp' => cmd.call('zfs-get-all-tank-tmp'), + # zfs output for pool tank + '/sbin/zpool get -Hp all tank' => cmd.call('zpool-get-all-tank'), # docker "docker ps -a --no-trunc --format '{{ json . }}'" => cmd.call('docker-ps-a'), "docker version --format '{{ json . }}'" => cmd.call('docker-version'), @@ -314,8 +314,9 @@ class MockLoader "docker inspect 71b5df59442b" => cmd.call('docker-inspec'), # docker images "83c36bfade9375ae1feb91023cd1f7409b786fd992ad4013bf0f2259d33d6406" => cmd.call('docker-images'), - } - + # get-process cmdlet for processes resource + '$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")' => cmd.call('get-process_processes') + } @backend end diff --git a/test/unit/mock/cmd/get-process_processes b/test/unit/mock/cmd/get-process_processes new file mode 100644 index 000000000..ef7bb9963 --- /dev/null +++ b/test/unit/mock/cmd/get-process_processes @@ -0,0 +1,3 @@ +PriorityClass,Id,CPU,PM,VirtualMemorySize,NPM,SessionId,Responding,StartTime,TotalProcessorTime,UserName,Path +Normal,2456,0.296875,4808704,118202368,14576,1,True,5/31/2017 9:13:17 AM,00:00:00.2968750,WINVAGR-QQQNHPN\Administrator,C:\Windows\system32\mmc.exe +High,396,0.15625,1323008,53710848,7776,1,True,5/31/2017 9:12:56 AM,00:00:00.1562500,NT AUTHORITY\SYSTEM,C:\Windows\system32\winlogon.exe diff --git a/test/unit/resources/processes_test.rb b/test/unit/resources/processes_test.rb index 2011874bf..a92fbf87f 100644 --- a/test/unit/resources/processes_test.rb +++ b/test/unit/resources/processes_test.rb @@ -109,6 +109,7 @@ describe 'Inspec::Resources::Processes' do resource = MockLoader.new(:centos6).load_resource('processes', 'postgres: bifrost bifrost') _(resource.users.sort).must_equal ['opscode-pgsql'] _(resource.states.sort).must_equal ['Ss'] + _(resource.exists?).must_equal true end it 'command name matches with output (string)' do @@ -120,4 +121,24 @@ describe 'Inspec::Resources::Processes' do resource = MockLoader.new(:centos6).load_resource('processes', /mysqld/) _(resource.to_s).must_equal 'Processes /mysqld/' end + + it 'command name matches with output (string)' do + resource = MockLoader.new(:windows).load_resource('processes', 'winlogon.exe') + _(resource.to_s).must_equal 'Processes winlogon.exe' + end + + it 'retrieves the users and states as arrays on windows os' do + resource = MockLoader.new(:windows).load_resource('processes', 'winlogon.exe') + _(resource.users.sort).must_equal ['NT AUTHORITY\\SYSTEM'] + end + + it 'process should exist' do + resource = MockLoader.new(:windows).load_resource('processes', 'winlogon.exe') + _(resource.exists?).must_equal true + end + + it 'process should_not exist' do + resource = MockLoader.new(:windows).load_resource('processes', 'unicorn.exe') + _(resource.exists?).must_equal false + end end