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 / auditd.rb
Size: Mime:
# encoding: utf-8

require 'forwardable'
require 'utils/filter_array'
require 'utils/filter'
require 'utils/parser'

module Inspec::Resources
  class AuditDaemon < Inspec.resource(1)
    extend Forwardable
    attr_accessor :lines
    attr_reader :params

    name 'auditd'
    supports platform: 'unix'
    desc 'Use the auditd InSpec audit resource to test the rules for logging that exist on the system. The audit.rules file is typically located under /etc/audit/ and contains the list of rules that define what is captured in log files. These rules are output using the auditcl -l command.'
    example "
      describe auditd.syscall('chown').where {arch == 'b32'} do
        its('action') { should eq ['always'] }
        its('list') { should eq ['exit'] }
      end

      describe auditd.where {key == 'privileged'} do
        its('permissions') { should include ['x'] }
      end

      describe auditd do
        its('lines') { should include %r(-w /etc/ssh/sshd_config) }
      end
    "

    def initialize
      unless inspec.command('/sbin/auditctl').exist?
        raise Inspec::Exceptions::ResourceFailed,
              'Command `/sbin/auditctl` does not exist'
      end

      auditctl_cmd = '/sbin/auditctl -l'
      result = inspec.command(auditctl_cmd)

      if result.exit_status != 0
        raise Inspec::Exceptions::ResourceFailed,
              "Command `#{auditctl_cmd}` failed with error: #{result.stderr}"
      end

      @content = result.stdout
      @params = []

      if @content =~ /^LIST_RULES:/
        raise Inspec::Exceptions::RsourceFailed,
              'The version of audit is outdated.' \
              'The `auditd` resource supports versions of audit >= 2.3.'
      end
      parse_content
    end

    filter = FilterTable.create
    filter.register_column(:file,         field: 'file')
          .register_column(:list,         field: 'list')
          .register_column(:action,       field: 'action')
          .register_column(:fields,       field: 'fields')
          .register_column(:fields_nokey, field: 'fields_nokey')
          .register_column(:syscall,      field: 'syscall')
          .register_column(:key,          field: 'key')
          .register_column(:arch,         field: 'arch')
          .register_column(:path,         field: 'path')
          .register_column(:permissions,  field: 'permissions')
          .register_column(:exit,         field: 'exit')

    filter.install_filter_methods_on_resource(self, :params)

    def status(name = nil)
      @status_content ||= inspec.command('/sbin/auditctl -s').stdout.chomp

      # See: https://github.com/inspec/inspec/issues/3113
      if @status_content =~ /^AUDIT_STATUS/
        @status_content = @status_content.gsub('AUDIT_STATUS: ', '')
                                         .tr(' ', "\n")
                                         .tr('=', ' ')
      end

      @status_params ||= Hash[@status_content.scan(/^([^ ]+) (.*)$/)]

      return @status_params[name] if name
      @status_params
    end

    def parse_content
      @lines = @content.lines.map(&:chomp)

      lines.each do |line|
        if is_file_syscall_syntax?(line)
          file_syscall_syntax_rules_for(line)
        end

        if is_syscall?(line)
          syscall_rules_for(line)

        elsif is_file?(line)
          file_rules_for(line)
        end
      end
    end

    def file_syscall_syntax_rules_for(line)
      file = file_syscall_syntax_for(line)
      action, list = action_list_for(line)
      fields = rule_fields_for(line)
      key_field, fields_nokey = remove_key_from(fields)
      key = key_in(key_field.join(''))
      perms = perms_in(fields)

      @params.push(
        {
          'file' => file,
          'list' => list,
          'action' => action,
          'fields' => fields,
          'permissions' => perms,
          'key' => key,
          'fields_nokey' => fields_nokey,
        },
      )
    end

    def syscall_rules_for(line)
      syscalls = syscalls_for(line)
      action, list = action_list_for(line)
      fields = rule_fields_for(line)
      key_field, fields_nokey = remove_key_from(fields)
      key = key_in(key_field.join(''))
      arch = arch_in(fields)
      path = path_in(fields)
      perms = perms_in(fields)
      exit_field = exit_in(fields)

      syscalls.each do |s|
        @params.push(
          {
            'syscall' => s,
            'list' => list,
            'action' => action,
            'fields' => fields,
            'key' => key,
            'arch' => arch,
            'path' => path,
            'permissions' => perms,
            'exit' => exit_field,
            'fields_nokey' => fields_nokey,
          },
        )
      end
    end

    def file_rules_for(line)
      file = file_for(line)
      perms = permissions_for(line)
      key = key_for(line)

      @params.push(
        {
          'file' => file,
          'key' => key,
          'permissions' => perms,
        },
      )
    end

    def to_s
      'Auditd Rules'
    end

    private

    def is_syscall?(line)
      line.match(/-S /)
    end

    def is_file?(line)
      line.match(/-w /)
    end

    def is_file_syscall_syntax?(line)
      line.match(/-F path=/)
    end

    def syscalls_for(line)
      line.scan(/-S ([^ ]+)\s?/).flatten.first.split(',')
    end

    def action_list_for(line)
      line.scan(/-a ([^,]+),([^ ]+)\s?/).flatten
    end

    def key_for(line)
      line.match(/-k ([^ ]+)\s?/)[1] if line.include?('-k ')
    end

    def file_for(line)
      line.match(/-w ([^ ]+)\s?/)[1]
    end

    def file_syscall_syntax_for(line)
      line.match(/-F path=(\S+)\s?/)[1]
    end

    def permissions_for(line)
      line.match(/-p ([^ ]+)/)[1].scan(/\w/)
    end

    def rule_fields_for(line)
      line.gsub(/-[aS] [^ ]+ /, '').split('-F ').map { |l| l.split(' ') }.flatten
    end

    def arch_in(fields)
      fields.each do |field|
        return field.match(/arch=(\S+)\s?/)[1] if field.start_with?('arch=')
      end
      nil
    end

    def perms_in(fields)
      fields.each do |field|
        return field.match(/perm=(\S+)\s?/)[1].scan(/\w/) if field.start_with?('perm=')
      end
      nil
    end

    def path_in(fields)
      fields.each do |field|
        return field.match(/path=(\S+)\s?/)[1] if field.start_with?('path=')
      end
      nil
    end

    def exit_in(fields)
      fields.each do |field|
        return field.match(/exit=(\S+)\s?/)[1] if field.start_with?('exit=')
      end
      nil
    end

    def key_in(field)
      _, v = field.split('=')
      v
    end

    def remove_key_from(fields)
      fields.partition { |x| x.start_with? 'key' }
    end
  end
end