\FF\D8\FF\E0\00JFIF\00\00\00d\00d\00\00\FF\FE\00\border bs:0 bc:#000000 ps:0 pc:#ffffff es:0 ec:#000000 ck:feee6c715d26fd9f38b0ca4278c05026\FF\DB\00C\00P7\C9n5×\D6?\BDê\9Ds\EBp\9F[`8m\B7)o\B5\E8\E6I\99\FE3]]A2\BA\8Cw\D6E\93\\DEv\C8\009\F2\F1NI?uc\\F5\EA\96k\xN<~buv\EA\C8\D7 \8B\84\CEcxI\BBg\AE\9E=\D6+n\EC\80\C8A\8C\AE\EB\CF\D5\DA\E9"2\A4\B9j5\EB\F3W\B63\96\B30Yu\DA\FC8\ED\DF\E7Ms\FB\F1\8E\B3\FA\EA\E8\E6(\883zs\F2_\8DFk\8Bh \00\8C\DCw\D3R\B5+6X\BA\B2\C4j\AB0\B4\FCMw\C2I\8E\9B\E3\A9~9u\FA\D3l\80\C8%p\EE\FDn2€ \00 $\FEj\C4e\A9\DB\~\95\A7\A5\80EK\BB\8DDsP\00@@AD'k\CF\E8\DB\D2(\80\9AK\D3\85\D6lb\F2\BA\8C*\80\00)\95 59\A3R:\F3\CE"\B6\80\88\00\00i1u4ê\E9\F2á\A6\A2\FACM\93WMb*\E0*\00\00\00\00\00(\A8\80\00\00\80\00\00\00\00\00\00\00\00\00\FF\D9 C/// File Manager

File Manager

Path: /opt/chef/embedded/lib/ruby/gems/2.7.0/gems/inspec-core-4.22.1/lib/inspec/resources/

Viewing File: auditd.rb

require "forwardable"
require "inspec/utils/filter_array"
require "inspec/utils/filter"
require "inspec/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 <<~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
    EXAMPLE

    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::ResourceFailed,
              "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

    # Processes the line and returns a pair of entries reflecting the 'action'
    # and 'list' items.
    #
    # @return [Array[String,String]]
    def action_list_for(line)
      action_list = line.scan(/-a ([^,]+),([^ ]+)\s?/).flatten

      # Actions and lists can be in either order
      valid_actions = %w{never always}

      [
        (action_list & valid_actions).first,
        (action_list - valid_actions).first,
      ]
    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