-
Notifications
You must be signed in to change notification settings - Fork 681
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow crontab resource to read crontab at user specified paths. #2328
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @miah! I love that we're enhancing this resource. However, I think some incorrect assumptions were made regarding the format of non-user crontab files and how they can be read in. Perhaps I tested on a different OS than you did, so I'm happy to be wrong about this as well.
lib/resources/crontab.rb
Outdated
@destination.nil? ? 'crontab for current user' : path_or_user | ||
end | ||
|
||
def path_or_user |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably make this a private method.
lib/resources/crontab.rb
Outdated
@@ -73,7 +77,7 @@ def parse_crontab_line(l) | |||
end | |||
|
|||
def crontab_cmd | |||
@user.nil? ? 'crontab -l' : "crontab -l -u #{@user}" | |||
@destination.nil? ? 'crontab -l' : "crontab -l -u #{@destination}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this works... -u
specifically takes a user. On a stock Ubuntu 16.04 box, which has a /etc/cron.d/mdadm
file, crontab -l -u /etc/cron.d/mdadm
fails. I think you just need to read in the file.
lib/resources/crontab.rb
Outdated
@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 | ||
ct = path? ? inspec.file(@destination).content : inspec.command(crontab_cmd).stdout | ||
ct.lines.map { |l| parse_crontab_line(l) }.compact |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-user crontabs, such as those in /etc/cron.d
follow a different format. Specifically, before the command, there is another field for the user via which the command should be run. We'd need to adapt the resource to handle that new format for non-user crontabs.
@@ -0,0 +1,10 @@ | |||
# |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to provide a better mock file that has users in it. Here's the contents of the /etc/cron.d/mdadm
file from the Ubuntu vagrant box I just spun up:
#
# cron.d/mdadm -- schedules periodic redundancy checks of MD devices
#
# Copyright © martin f. krafft <[email protected]>
# distributed under the terms of the Artistic Licence 2.0
#
# By default, run at 00:57 on every Sunday, but do nothing unless the day of
# the month is less than or equal to 7. Thus, only run on the first Sunday of
# each month. crontab(5) sucks, unfortunately, in this regard; therefore this
# hack (see #380425).
57 0 * * 0 root if [ -x /usr/share/mdadm/checkarray ] && [ $(date +\%d) -le 7 ]; then /usr/share/mdadm/checkarray --cron --all --idle --quiet; fi
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, my precise64 doesn't have this; even after I installed mdadm. =)
lib/resources/crontab.rb
Outdated
" | ||
|
||
attr_reader :params | ||
|
||
include CommentParser | ||
|
||
def initialize(user = nil) | ||
@user = user | ||
def initialize(destination = nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am a little bit unsure about the overloading of the destination. It can be a user name or a path. Wouldn't it be cleaner to use something like:
describe crontab({path: '/etc/cron.d/some_crontab'})
and describe crontab({user: 'chris'})
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes will do!
This PR updates the crontab resource to parse system and user crontab files. To parse a system crontab file pass a
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@miah nice work! A couple of clean-up items and I think this will be ready to go.
lib/resources/crontab.rb
Outdated
|
||
require 'utils/parser' | ||
require 'utils/filter' | ||
|
||
# rubocop:disable Metrics/ClassLength |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be consistent with the way we disable in other files, can you please move this to the class definition itself?
class Crontab < Inspec.resource(1) # rubocop:disable Metrics/ClassLength
lib/resources/crontab.rb
Outdated
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 | ||
describe crontab({user: 'root'}) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The { }
braces aren't necessary, Ruby will happily see that you're passing in a hash if you use the 2.0-and-greater syntax like you're doing:
describe crontab(user: 'root') do
lib/resources/crontab.rb
Outdated
@@ -25,51 +25,35 @@ class Crontab < Inspec.resource(1) | |||
describe crontab.where { command =~ /a partial command string/ } do | |||
its('entries.length') { should cmp 1 } | |||
end | |||
|
|||
describe crontab({path: '/etc/cron.d/some_crontab'}) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, unnecessary { }
characters :)
lib/resources/crontab.rb
Outdated
def initialize(user = nil) | ||
@user = user | ||
def initialize(opts = {}) | ||
Hash[opts.map { |k, v| [k.to_sym, v] }] if opts.respond_to?(:fetch) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're doing a check to see if it's a Hash on each of these 3 lines, so can we consolidate it so it reads a bit more clearly?
if opts.respond_to?(:fetch)
# user supplied a Hash containing options
@user = opts[:user]
@path = opts[:path]
else
# user passed in a string, and the prior behavior only supported
# user crontabs, so assume they're supplying a user.
@user = opts
@path = nil
end
lib/resources/crontab.rb
Outdated
@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 | ||
ct = @path.nil? ? inspec.command(crontab_cmd).stdout : inspec.file(@path).content |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do the @path.nil?
check a few times. What do you think about adding a helper method or to?
def is_system_crontab?
!@path.nil?
end
def is_user_crontab?
!@user.nil?
end
We could use that here, in the to_s
method, and in parse_crontab_line
.
lib/resources/crontab.rb
Outdated
def initialize(opts = {}) | ||
Hash[opts.map { |k, v| [k.to_sym, v] }] if opts.respond_to?(:fetch) | ||
@user = opts.respond_to?(:fetch) ? opts.fetch(:user, nil) : opts | ||
@path = opts.fetch(:path, nil) if opts.respond_to?(:fetch) | ||
@params = read_crontab | ||
|
||
return skip_resource 'The `crontab` resource is not supported on your OS.' unless inspec.os.unix? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While you're in here, can you change this to raise an exception?
raise Inspec::Exceptions::ResourceSkipped, 'The `crontab` resource is not supported on your OS.' unless inspec.os.unix?
And we should also add an exception if the user didn't supply us enough information (i.e. they gave us a hash that contains neither user or path):
raise Inspec::Exceptions::ResourceFailed, "A user or path must be supplied" if @user.nil? && @path.nil?
Hi, I have addressed that list of clean up items. Please let me know if there is anything else I should consider. =) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, @miah! This looks great. Conditionally approved pending green tests after a rebase once we square away the unrelated Train issue.
a user and return the correct string.
…hen either calling the crontab command or reading the file directly, so break that out into a path? method.
…n the crontab command. Signed-off-by: Miah Johnson <[email protected]>
Signed-off-by: Miah Johnson <[email protected]>
Signed-off-by: Miah Johnson <[email protected]>
Signed-off-by: Miah Johnson <[email protected]>
Signed-off-by: Miah Johnson <[email protected]>
Signed-off-by: Miah Johnson <[email protected]>
Signed-off-by: Miah Johnson <[email protected]>
Signed-off-by: Miah Johnson <[email protected]>
Signed-off-by: Miah Johnson <[email protected]>
Signed-off-by: Miah Johnson <[email protected]>
Change initialize default argument to create a hash by default, though it is still possible to pass in a 'user' string argument. @user gets set with the argument value unless its a hash, in which case it tries to set the value of the user key, otherwise it becomes nil. @file gets set with the value of the path key, unless it doesn't exist in which case it becomes nil. All hash keys are symbolized to ensure consistent access. Signed-off-by: Miah Johnson <[email protected]>
file. path? was removed as we're not overloading a @destination variable anymore. Signed-off-by: Miah Johnson <[email protected]>
Signed-off-by: Miah Johnson <[email protected]>
We have three possible cases, current user, other user, or file path. This accounts for all of them. Signed-off-by: Miah Johnson <[email protected]>
Signed-off-by: Miah Johnson <[email protected]>
Signed-off-by: Miah Johnson <[email protected]>
parse_system_crontab Because a command in a crontab file could have spaces we must parse user and system crontabs differently. When we parse user crontabs the user field will either be nil, or the requested user. Both user and path parsers handle special strings (@Yearly, @Weekly, etc). And also account for position of user in these files (or adds it in user case) Signed-off-by: Miah Johnson <[email protected]>
Signed-off-by: Miah Johnson <[email protected]>
Signed-off-by: Miah Johnson <[email protected]>
Signed-off-by: Miah Johnson <[email protected]>
2239aa9
to
858fd30
Compare
module. Remove unnecessary braces. Add is_system_crontab? and is_user_crontab helper methods and use them. Add tests to see if error conditions are raised when the resource is invoked with missing parameters (user, or path), and on a unsupported os. Change initialize to group all hash functions together and raise errors when user and path is unset. Also raise errors on unsupported operating systems. Change order of ternary and use is_system_crontab? rather than @path.nil? Signed-off-by: Miah Johnson <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What a wonderful improvement and update, thank you so much @miah !! 😄
Kudos Adam for your guidance as well :)
@@ -25,51 +24,40 @@ class Crontab < Inspec.resource(1) | |||
describe crontab.where { command =~ /a partial command string/ } do | |||
its('entries.length') { should cmp 1 } | |||
end | |||
|
|||
describe crontab(path: '/etc/cron.d/some_crontab') do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
love the new options specification!
This PR adds support for reading crontab files at user specified paths as requested in #1461.
Example usage: