Adding new crontab resource

The crontab resource parses a particular user's crontab file into
individual entries and allows the user to assert information about
each entry as needed.

Signed-off-by: Adam Leff <adam@leff.co>
This commit is contained in:
Adam Leff 2017-02-09 14:28:06 -05:00
parent 4daa4d06c0
commit 417b791baa
7 changed files with 244 additions and 1 deletions

View file

@ -0,0 +1,62 @@
---
title: About the crontab Resource
---
# crontab
Use the `crontab` InSpec audit resource to test the crontab entries for a particular user on the system.
## Syntax
A `crontab` resource block declares a user (which defaults to the current user, if not specified), and then the details to be tested, such as the schedule elements for each crontab entry or the commands itself:
describe crontab do
its('commands') { should include '/some/scheduled/task.sh' }
end
## Matchers
This InSpec audit resource has the following matchers:
### be
<%= partial "/shared/matcher_be" %>
### cmp
<%= partial "/shared/matcher_cmp" %>
### eq
<%= partial "/shared/matcher_eq" %>
### include
<%= partial "/shared/matcher_include" %>
### match
<%= partial "/shared/matcher_match" %>
## Examples
The following examples show how to use this InSpec audit resource.
### Test that root's crontab has a particular command
describe crontab('root') do
its('commands') { should include '/path/to/some/script' }
end
### Test that myuser's crontab entry for command '/home/myuser/build.sh' runs every minute
describe crontab('myuser').commands('/home/myuser/build.sh') do
its('hours') { should cmp '*' }
its('minutes') { should cmp '*' }
end
### Test that the logged-in user's crontab has no tasks set to run on every hour and every minute
describe crontab.where({'hour' => '*', 'minute' => '*'}) do
its('entries.length') { should cmp '0' }
end

View file

@ -79,6 +79,7 @@ require 'resources/bash'
require 'resources/bond'
require 'resources/bridge'
require 'resources/command'
require 'resources/crontab'
require 'resources/directory'
require 'resources/etc_group'
require 'resources/file'

83
lib/resources/crontab.rb Normal file
View file

@ -0,0 +1,83 @@
# encoding: utf-8
# author: Adam Leff
require 'utils/parser'
require 'utils/filter'
module Inspec::Resources
class Crontab < Inspec.resource(1)
name 'crontab'
desc 'Use the crontab InSpec audit resource to test the contents of the crontab for a given user which contains information about scheduled tasks owned by that user.'
example "
describe crontab('root') do
its('commands') { should include '/path/to/some/script' }
end
describe crontab('myuser').commands('/home/myuser/build.sh') do
its('hours') { should cmp '*' }
its('minutes') { should cmp '*' }
end
describe crontab.where({'hour' => '*', 'minute' => '*'}) do
its('entries.length') { should cmp '0' }
end
"
attr_reader :params
include CommentParser
def initialize(user = nil)
@user = user
@params = read_crontab
return skip_resource 'The `crontab` resource is not supported on your OS.' unless inspec.os.unix?
end
def read_crontab
inspec.command(crontab_cmd).stdout.lines.map { |l| parse_crontab_line(l) }.compact
end
def parse_crontab_line(l)
data, = parse_comment_line(l, comment_char: '#', standalone_comments: false)
return nil if data.nil? || data.empty?
elements = data.split(/\s+/, 6)
{
'minute' => elements.at(0),
'hour' => elements.at(1),
'day' => elements.at(2),
'month' => elements.at(3),
'weekday' => elements.at(4),
'command' => elements.at(5),
}
end
def crontab_cmd
@user.nil? ? 'crontab -l' : "crontab -l -u #{@user}"
end
filter = FilterTable.create
filter.add_accessor(:where)
.add_accessor(:entries)
.add(:minutes, field: 'minute')
.add(:hours, field: 'hour')
.add(:days, field: 'day')
.add(:months, field: 'month')
.add(:weekdays, field: 'weekday')
.add(:commands, field: 'command')
# rebuild the crontab line from raw content
filter.add(:content) { |t, _|
t.entries.map do |e|
[e.minute, e.hour, e.day, e.month, e.weekday, e.command].join(' ')
end.join("\n")
}
filter.connect(self, :params)
def to_s
@user.nil? ? 'crontab for current user' : "crontab for user #{@user}"
end
end
end

View file

@ -274,7 +274,11 @@ class MockLoader
"schtasks /query /v /fo csv /tn 'does-not-exist' | ConvertFrom-Csv | Select @{N='URI';E={$_.TaskName}},@{N='State';E={$_.Status.ToString()}},'Logon Mode','Last Result','Task To Run','Run As User','Scheduled Task State' | ConvertTo-Json -Compress" => cmd.call('schtasks-error'),
# windows_task exist
"schtasks /query /v /fo csv /tn 'WeLovePizza' | ConvertFrom-Csv | Select @{N='URI';E={$_.TaskName}},@{N='State';E={$_.Status.ToString()}},'Logon Mode','Last Result','Task To Run','Run As User','Scheduled Task State' | ConvertTo-Json -Compress" => cmd.call('schtasks-success'),
'modinfo -F version dhcp' => cmd.call('modinfo-f-version-dhcp')
'modinfo -F version dhcp' => cmd.call('modinfo-f-version-dhcp'),
# crontab display for root / current user
'crontab -l' => cmd.call('crontab-root'),
# crontab display for non-current user
'crontab -l -u foouser' => cmd.call('crontab-foouser')
}
@backend

View file

@ -0,0 +1,5 @@
#
# This is a sample crontab file for unit testing, for user 'foouser'
#
* * * * * /path/to/script3

View file

@ -0,0 +1,10 @@
#
# This is a sample crontab file for unit testing
#
# entry number 1
0 2 11 9 4 /path/to/script1
# entry number 2
1 3 12 10 5 /path/to/script2 arg1 arg2

View file

@ -0,0 +1,78 @@
# encoding: utf-8
# author: Adam Leff
require 'helper'
require 'inspec/resource'
describe 'Inspec::Resources::Crontab' do
let(:crontab) { load_resource('crontab') }
it 'retrieve minutes via field' do
_(crontab.minutes).must_equal [ '0', '1' ]
end
it 'retrieve hours via field' do
_(crontab.hours).must_equal [ '2', '3' ]
end
it 'retrieve days via field' do
_(crontab.days).must_equal [ '11', '12' ]
end
it 'retrieve months via field' do
_(crontab.months).must_equal [ '9', '10' ]
end
it 'retrieve weekdays via field' do
_(crontab.weekdays).must_equal [ '4', '5' ]
end
it 'retrieve commands via field' do
_(crontab.commands).must_equal [ '/path/to/script1', '/path/to/script2 arg1 arg2' ]
end
it 'returns all params of the file' do
_(crontab.params).must_equal([
{
'minute' => '0',
'hour' => '2',
'day' => '11',
'month' => '9',
'weekday' => '4',
'command' => '/path/to/script1',
},
{
'minute' => '1',
'hour' => '3',
'day' => '12',
'month' => '10',
'weekday' => '5',
'command' => '/path/to/script2 arg1 arg2'
},
])
end
it 'prints a nice to_s string' do
_(crontab.to_s).must_equal "crontab for current user"
end
describe 'filter by command' do
let(:entry) { crontab.commands(/script2/) }
it 'returns the correct content' do
_(entry.content).must_equal '1 3 12 10 5 /path/to/script2 arg1 arg2'
end
it 'prints a nice to_s string' do
_(entry.to_s).must_equal 'crontab for current user with command == /script2/'
end
end
describe 'query by user' do
let(:crontab) { load_resource('crontab', 'foouser') }
it 'prints a user-specific to_s string' do
_(crontab.to_s).must_equal 'crontab for user foouser'
end
end
end