inspec/lib/resources/auditd_rules.rb
Adam Leff 577688a3a0 Placing all resources in the Inspec::Resources namespace
Many of the resources are named as a top-level class with a fairly generic class name, such as "OS". This causes an issue specifically with kitchen-google which depends on a gem which depends on the "os" gem which itself defines an OS class with a different superclass. This prevents users from using TK, Google Compute, and Inspec without this fix.

Some mocked commands had their digest changed as well due to the new indentation, specifically in the User and RegistryKey classes.

I strongly recommend viewing this diff with `git diff --ignore-space-change`
to see the *real* changes. :)
2016-03-08 13:40:16 -05:00

205 lines
5.6 KiB
Ruby

# encoding: utf-8
# copyright: 2015, Vulcano Security GmbH
# author: Christoph Hartmann
# author: Dominik Richter
# license: All rights reserved
require 'forwardable'
require 'utils/filter_array'
module Inspec::Resources
class AuditdRulesLegacy
def initialize(content)
@content = content
@opts = {
assignment_re: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
multiple_values: true,
}
end
def params
@params ||= SimpleConfig.new(@content, @opts).params
end
def method_missing(name)
params[name.to_s]
end
def status(name)
@status_opts = {
assignment_re: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
multiple_values: false,
}
@status_content ||= inspec.command('/sbin/auditctl -s').stdout.chomp
@status_params = SimpleConfig.new(@status_content, @status_opts).params
status = @status_params['AUDIT_STATUS']
return nil if status.nil?
items = Hash[status.scan(/([^=]+)=(\w*)\s*/)]
items[name]
end
def to_s
'Audit Daemon Rules (for auditd version < 2.3)'
end
end
# rubocop:disable Metrics/ClassLength
class AuditDaemonRules < Inspec.resource(1)
extend Forwardable
attr_accessor :rules, :lines
name 'auditd_rules'
desc 'Use the auditd_rules 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.'
example "
# syntax for auditd < 2.3
describe auditd_rules do
its('LIST_RULES') {should contain_match(/^exit,always arch=.* key=time-change syscall=adjtimex,settimeofday/) }
its('LIST_RULES') {should contain_match(/^exit,always arch=.* key=time-change syscall=stime,settimeofday,adjtimex/) }
its('LIST_RULES') {should contain_match(/^exit,always arch=.* key=time-change syscall=clock_settime/)}
its('LIST_RULES') {should contain_match(/^exit,always watch=\/etc\/localtime perm=wa key=time-change/)}
end
# syntax for auditd >= 2.3
describe auditd_rules.syscall('open').action do
it { should eq(['always']) }
end
describe auditd_rules.key('sshd_config') do
its(:permissions) { should contain_match(/x/) }
end
describe auditd_rules do
its(:lines) { should contain_match(%r{-w /etc/ssh/sshd_config/}) }
end
"
def initialize
@content = inspec.command('/sbin/auditctl -l').stdout.chomp
if @content =~ /^LIST_RULES:/
# do not warn on centos 5
unless inspec.os[:family] == 'centos' && inspec.os[:release].to_i == 5
warn '[WARN] this version of auditd is outdated. Updating it allows for using more precise matchers.'
end
@legacy = AuditdRulesLegacy.new(@content)
else
parse_content
end
end
# non-legacy instances are not asked for `its('LIST_RULES')`
# rubocop:disable Style/MethodName
def LIST_RULES
return @legacy.LIST_RULES if @legacy
fail 'Using legacy auditd_rules LIST_RULES interface with non-legacy audit package. Please use the new syntax.'
end
def status(name = nil)
return @legacy.status(name) if @legacy
@status_content ||= inspec.command('/sbin/auditctl -s').stdout.chomp
@status_params ||= Hash[@status_content.scan(/^([^ ]+) (.*)$/)]
return @status_params[name] if name
@status_params
end
def parse_content
@rules = {
syscalls: [],
files: [],
}
@lines = @content.lines.map(&:chomp)
lines.each do |line|
if is_syscall?(line)
syscalls = get_syscalls line
action, list = get_action_list line
fields, opts = get_fields line
# create a 'flatter' structure because sanity
syscalls.each do |s|
@rules[:syscalls] << { syscall: s, list: list, action: action, fields: fields }.merge(opts)
end
elsif is_file?(line)
file = get_file line
perms = get_permissions line
key = get_key line
@rules[:files] << { file: file, key: key, permissions: perms }
end
end
end
def syscall(name)
select_name(:syscall, name)
end
def file(name)
select_name(:file, name)
end
# both files and syscalls have `key` identifiers
def key(name)
res = rules.values.flatten.find_all { |rule| rule[:key] == name }
FilterArray.new(res)
end
def to_s
'Audit Daemon Rules'
end
private
def select_name(key, name)
plural = "#{key}s".to_sym
res = rules[plural].find_all { |rule| rule[key] == name }
FilterArray.new(res)
end
def is_syscall?(line)
line.match(/\ -S /)
end
def is_file?(line)
line.match(/-w /)
end
def get_syscalls(line)
line.scan(/-S ([^ ]+) /).flatten.first.split(',')
end
def get_action_list(line)
line.scan(/-a ([^,]+),([^ ]+)/).flatten
end
# NB only in file lines
def get_key(line)
line.match(/-k ([^ ]+)/)[1]
end
# NOTE there are NO precautions wrt. filenames containing spaces in auditctl
# `auditctl -w /foo\ bar` gives the following line: `-w /foo bar -p rwxa`
def get_file(line)
line.match(/-w (.+) -p/)[1]
end
def get_permissions(line)
line.match(/-p ([^ ]+)/)[1]
end
def get_fields(line)
fields = line.gsub(/-[aS] [^ ]+ /, '').split('-F ').map { |l| l.split(' ') }.flatten
opts = {}
fields.find_all { |x| x.match(/[a-z]+=.*/) }.each do |kv|
k, v = kv.split('=')
opts[k.to_sym] = v
end
[fields, opts]
end
end
end