2015-07-26 20:44:01 +00:00
# encoding: utf-8
# copyright: 2015, Vulcano Security GmbH
2015-10-06 16:55:44 +00:00
# author: Christoph Hartmann
# author: Dominik Richter
2015-07-26 20:44:01 +00:00
# license: All rights reserved
2016-01-28 13:13:01 +00:00
require 'forwardable'
2016-01-29 08:21:35 +00:00
require 'utils/filter_array'
2015-07-26 20:44:01 +00:00
2016-01-28 13:13:01 +00:00
class AuditdRulesLegacy
def initialize ( content )
@content = content
2015-07-26 20:44:01 +00:00
@opts = {
assignment_re : / ^ \ s*([^:]*?) \ s*: \ s*(.*?) \ s*$ / ,
2015-09-09 16:52:27 +00:00
multiple_values : true ,
2015-07-26 20:44:01 +00:00
}
2015-08-28 23:04:52 +00:00
end
def params
@params || = SimpleConfig . new ( @content , @opts ) . params
2015-07-26 20:44:01 +00:00
end
2015-09-03 18:43:58 +00:00
def method_missing ( name )
2015-08-28 23:04:52 +00:00
params [ name . to_s ]
2015-07-26 20:44:01 +00:00
end
2015-09-03 18:43:58 +00:00
def status ( name )
2015-07-26 20:44:01 +00:00
@status_opts = {
assignment_re : / ^ \ s*([^:]*?) \ s*: \ s*(.*?) \ s*$ / ,
2015-09-09 16:52:27 +00:00
multiple_values : false ,
2015-07-26 20:44:01 +00:00
}
2015-10-26 03:04:18 +00:00
@status_content || = inspec . command ( '/sbin/auditctl -s' ) . stdout . chomp
2015-07-26 20:44:01 +00:00
@status_params = SimpleConfig . new ( @status_content , @status_opts ) . params
2015-09-09 16:52:27 +00:00
2015-09-03 18:35:23 +00:00
status = @status_params [ 'AUDIT_STATUS' ]
2015-09-09 16:52:27 +00:00
return nil if status . nil?
2015-07-26 20:44:01 +00:00
items = Hash [ status . scan ( / ([^=]+)=( \ w*) \ s* / ) ]
2015-09-03 18:45:37 +00:00
items [ name ]
2015-07-26 20:44:01 +00:00
end
2016-01-28 13:13:01 +00:00
def to_s
2016-02-08 20:35:30 +00:00
'Audit Daemon Rules (for auditd version < 2.3)'
2016-01-28 13:13:01 +00:00
end
end
2016-01-29 16:16:27 +00:00
# rubocop:disable Metrics/ClassLength
2016-01-28 13:13:01 +00:00
class AuditDaemonRules < Inspec . resource ( 1 )
extend Forwardable
2016-01-28 16:08:41 +00:00
attr_accessor :rules , :lines
2016-01-28 13:13:01 +00:00
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 "
2016-02-08 20:35:30 +00:00
# syntax for auditd < 2.3
2016-01-28 13:13:01 +00:00
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
2016-01-28 16:08:41 +00:00
2016-02-08 20:35:30 +00:00
# syntax for auditd >= 2.3
2016-01-29 15:29:26 +00:00
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
2016-01-28 13:13:01 +00:00
"
def initialize
@content = inspec . command ( '/sbin/auditctl -l' ) . stdout . chomp
2016-01-29 16:16:27 +00:00
if @content =~ / ^LIST_RULES: /
2016-02-05 16:12:49 +00:00
# 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
2016-01-28 13:13:01 +00:00
@legacy = AuditdRulesLegacy . new ( @content )
else
parse_content
end
end
# non-legacy instances are not asked for `its('LIST_RULES')`
2016-01-29 16:16:27 +00:00
# rubocop:disable Style/MethodName
2016-01-28 16:08:41 +00:00
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
2016-01-28 13:13:01 +00:00
2016-01-29 09:03:17 +00:00
def status ( name = nil )
return @legacy . status ( name ) if @legacy
@status_content || = inspec . command ( '/sbin/auditctl -s' ) . stdout . chomp
2016-01-29 16:16:27 +00:00
@status_params || = Hash [ @status_content . scan ( / ^([^ ]+) (.*)$ / ) ]
2016-01-29 09:03:17 +00:00
return @status_params [ name ] if name
@status_params
end
2016-01-28 13:13:01 +00:00
def parse_content
2016-01-28 16:08:41 +00:00
@rules = {
syscalls : [ ] ,
2016-01-29 16:16:27 +00:00
files : [ ] ,
2016-01-28 16:08:41 +00:00
}
@lines = @content . lines . map ( & :chomp )
lines . each do | line |
if is_syscall? ( line )
syscalls = get_syscalls line
action , list = get_action_list line
2016-01-29 10:13:52 +00:00
fields , opts = get_fields line
2016-01-28 16:08:41 +00:00
# create a 'flatter' structure because sanity
syscalls . each do | s |
2016-01-29 10:13:52 +00:00
@rules [ :syscalls ] << { syscall : s , list : list , action : action , fields : fields } . merge ( opts )
2016-01-28 16:08:41 +00:00
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
2016-01-28 13:13:01 +00:00
2016-01-28 16:08:41 +00:00
def syscall ( name )
select_name ( :syscall , name )
end
def file ( name )
select_name ( :file , name )
2016-01-28 13:13:01 +00:00
end
2016-01-29 15:29:26 +00:00
# both files and syscalls have `key` identifiers
def key ( name )
res = rules . values . flatten . find_all { | rule | rule [ :key ] == name }
FilterArray . new ( res )
end
2015-07-26 20:44:01 +00:00
def to_s
2015-08-28 23:04:52 +00:00
'Audit Daemon Rules'
2015-07-26 20:44:01 +00:00
end
2016-01-28 16:08:41 +00:00
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 )
2016-01-29 16:16:27 +00:00
line . match ( / \ -S / )
2016-01-28 16:08:41 +00:00
end
def is_file? ( line )
2016-01-29 16:16:27 +00:00
line . match ( / -w / )
2016-01-28 16:08:41 +00:00
end
def get_syscalls ( line )
2016-01-29 10:13:52 +00:00
line . scan ( / -S ([^ ]+) / ) . flatten . first . split ( ',' )
2016-01-28 16:08:41 +00:00
end
def get_action_list ( line )
line . scan ( / -a ([^,]+),([^ ]+) / ) . flatten
end
2016-01-29 10:13:52 +00:00
# NB only in file lines
2016-01-28 16:08:41 +00:00
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 )
2016-01-29 16:16:27 +00:00
line . match ( / -w (.+) -p / ) [ 1 ]
2016-01-28 16:08:41 +00:00
end
def get_permissions ( line )
line . match ( / -p ([^ ]+) / ) [ 1 ]
end
2016-01-29 10:13:52 +00:00
def get_fields ( line )
2016-01-29 16:16:27 +00:00
fields = line . gsub ( / -[aS] [^ ]+ / , '' ) . split ( '-F ' ) . map { | l | l . split ( ' ' ) } . flatten
2016-01-29 10:13:52 +00:00
2016-01-28 16:08:41 +00:00
opts = { }
2016-01-29 16:16:27 +00:00
fields . find_all { | x | x . match ( / [a-z]+=.* / ) } . each do | kv |
2016-01-29 10:13:52 +00:00
k , v = kv . split ( '=' )
opts [ k . to_sym ] = v
2016-01-28 16:08:41 +00:00
end
2016-01-29 16:16:27 +00:00
[ fields , opts ]
2016-01-28 16:08:41 +00:00
end
2015-07-26 20:44:01 +00:00
end