Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
inspec / lib / resources / crontab.rb
Size: Mime:
# encoding: utf-8

require 'utils/parser'
require 'utils/filter'

module Inspec::Resources
  class Crontab < Inspec.resource(1)
    name 'crontab'
    supports platform: 'unix'
    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(user: '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

      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
        its('commands') { should include '/path/to/some/script' }
      end
    "

    attr_reader :params

    include CommentParser

    def initialize(opts = nil)
      if opts.respond_to?(:fetch)
        Hash[opts.map { |k, v| [k.to_sym, v] }]
        @user = opts.fetch(:user, nil)
        @path = opts.fetch(:path, nil)
        raise Inspec::Exceptions::ResourceFailed, 'A user or path must be supplied.' if @user.nil? && @path.nil?
      else
        @user = opts
        @path = nil
      end
      @params = read_crontab
    end

    def read_crontab
      ct = is_system_crontab? ? inspec.file(@path).content : inspec.command(crontab_cmd).stdout
      ct.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?

      is_system_crontab? ? parse_system_crontab(data) : parse_user_crontab(data)
    end

    def crontab_cmd
      @user.nil? ? 'crontab -l' : "crontab -l -u #{@user}"
    end

    filter = FilterTable.create
    filter.register_column(:minutes,  field: 'minute')
          .register_column(:hours,    field: 'hour')
          .register_column(:days,     field: 'day')
          .register_column(:months,   field: 'month')
          .register_column(:weekdays, field: 'weekday')
          .register_column(:user,     field: 'user')
          .register_column(:commands, field: 'command')

    # rebuild the crontab line from raw content
    filter.register_custom_property(:content) { |t, _|
      t.entries.map do |e|
        [e.minute, e.hour, e.day, e.month, e.weekday, e.user, e.command].compact.join(' ')
      end.join("\n")
    }

    filter.install_filter_methods_on_resource(self, :params)

    def to_s
      if is_system_crontab?
        "crontab for path #{@path}"
      elsif is_user_crontab?
        "crontab for user #{@user}"
      else
        'crontab for current user'
      end
    end

    private

    def is_system_crontab?
      !@path.nil?
    end

    def is_user_crontab?
      !@user.nil?
    end

    def parse_system_crontab(data)
      case data
      when /@hourly .*/
        elements = data.split(/\s+/, 3)
        { 'minute' => '0', 'hour' => '*', 'day' => '*', 'month' => '*', 'weekday' => '*', 'user' => elements.at(1), 'command' => elements.at(2) }
      when /@(midnight|daily) .*/
        elements = data.split(/\s+/, 3)
        { 'minute' => '0', 'hour' => '0', 'day' => '*', 'month' => '*', 'weekday' => '*', 'user' => elements.at(1), 'command' => elements.at(2) }
      when /@weekly .*/
        elements = data.split(/\s+/, 3)
        { 'minute' => '0', 'hour' => '0', 'day' => '*', 'month' => '*', 'weekday' => '0', 'user' => elements.at(1), 'command' => elements.at(2) }
      when /@monthly ./
        elements = data.split(/\s+/, 3)
        { 'minute' => '0', 'hour' => '0', 'day' => '1', 'month' => '*', 'weekday' => '*', 'user' => elements.at(1), 'command' => elements.at(2) }
      when /@(annually|yearly) .*/
        elements = data.split(/\s+/, 3)
        { 'minute' => '0', 'hour' => '0', 'day' => '1', 'month' => '1', 'weekday' => '*', 'user' => elements.at(1), 'command' => elements.at(2) }
      when /@reboot .*/
        elements = data.split(/\s+/, 3)
        { 'minute' => '-1', 'hour' => '-1', 'day' => '-1', 'month' => '-1', 'weekday' => '-1', 'user' => elements.at(1), 'command' => elements.at(2) }
      else
        elements = data.split(/\s+/, 7)
        {
          'minute'  => elements.at(0),
          'hour'    => elements.at(1),
          'day'     => elements.at(2),
          'month'   => elements.at(3),
          'weekday' => elements.at(4),
          'user'    => elements.at(5),
          'command' => elements.at(6),
        }
      end
    end

    def parse_user_crontab(data)
      case data
      when /@hourly .*/
        { 'minute' => '0', 'hour' => '*', 'day' => '*', 'month' => '*', 'weekday' => '*', 'user' => @user, 'command' => data.split(/\s+/, 2).at(1) }
      when /@(midnight|daily) .*/
        { 'minute' => '0', 'hour' => '0', 'day' => '*', 'month' => '*', 'weekday' => '*', 'user' => @user, 'command' => data.split(/\s+/, 2).at(1) }
      when /@weekly .*/
        { 'minute' => '0', 'hour' => '0', 'day' => '*', 'month' => '*', 'weekday' => '0', 'user' => @user, 'command' => data.split(/\s+/, 2).at(1) }
      when /@monthly ./
        { 'minute' => '0', 'hour' => '0', 'day' => '1', 'month' => '*', 'weekday' => '*', 'user' => @user, 'command' => data.split(/\s+/, 2).at(1) }
      when /@(annually|yearly) .*/
        { 'minute' => '0', 'hour' => '0', 'day' => '1', 'month' => '1', 'weekday' => '*', 'user' => @user, 'command' => data.split(/\s+/, 2).at(1) }
      when /@reboot .*/
        { 'minute' => '-1', 'hour' => '-1', 'day' => '-1', 'month' => '-1', 'weekday' => '-1', 'user' => @user, 'command' => data.split(/\s+/, 2).at(1) }
      else
        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),
          'user'    => @user,
          'command' => elements.at(5),
        }
      end
    end
  end
end