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-03-08 18:06:55 +00:00
module Inspec::Resources
class AuditdRulesLegacy
def initialize ( content )
@content = content
@opts = {
assignment_re : / ^ \ s*([^:]*?) \ s*: \ s*(.*?) \ s*$ / ,
multiple_values : true ,
}
end
2015-08-28 23:04:52 +00:00
2016-03-08 18:06:55 +00:00
def params
@params || = SimpleConfig . new ( @content , @opts ) . params
end
2015-07-26 20:44:01 +00:00
2016-03-08 18:06:55 +00:00
def method_missing ( name )
params [ name . to_s ]
end
2015-07-26 20:44:01 +00:00
2016-03-08 18:06:55 +00:00
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
2015-09-09 16:52:27 +00:00
2016-03-08 18:06:55 +00:00
status = @status_params [ 'AUDIT_STATUS' ]
return nil if status . nil?
2015-07-26 20:44:01 +00:00
2016-03-08 18:06:55 +00:00
items = Hash [ status . scan ( / ([^=]+)=( \ w*) \ s* / ) ]
items [ name ]
end
2015-07-26 20:44:01 +00:00
2016-03-08 18:06:55 +00:00
def to_s
'Audit Daemon Rules (for auditd version < 2.3)'
end
2016-01-28 13:13:01 +00:00
end
2016-03-08 18:06:55 +00:00
# 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
2016-01-28 16:08:41 +00:00
2016-03-08 18:06:55 +00:00
# syntax for auditd >= 2.3
describe auditd_rules . syscall ( 'open' ) . action do
it { should eq ( [ 'always' ] ) }
end
2016-01-29 15:29:26 +00:00
2016-03-08 18:06:55 +00:00
describe auditd_rules . key ( 'sshd_config' ) do
2016-05-03 22:14:33 +00:00
its ( 'permissions' ) { should contain_match ( / x / ) }
2016-03-08 18:06:55 +00:00
end
2016-01-29 15:29:26 +00:00
2016-03-08 18:06:55 +00:00
describe auditd_rules do
2016-05-03 22:14:33 +00:00
its ( 'lines' ) { should contain_match ( %r{ -w /etc/ssh/sshd_config/ } ) }
2016-03-08 18:06:55 +00:00
end
"
2016-01-28 13:13:01 +00:00
2016-03-08 18:06:55 +00:00
def initialize
@content = inspec . command ( '/sbin/auditctl -l' ) . stdout . chomp
2016-01-28 13:13:01 +00:00
2016-03-08 18:06:55 +00:00
if @content =~ / ^LIST_RULES: /
# do not warn on centos 5
2016-08-03 17:18:24 +00:00
unless inspec . os [ :name ] == 'centos' && inspec . os [ :release ] . to_i == 5
2016-03-08 18:06:55 +00:00
warn '[WARN] this version of auditd is outdated. Updating it allows for using more precise matchers.'
end
@legacy = AuditdRulesLegacy . new ( @content )
else
parse_content
2016-02-05 16:12:49 +00:00
end
2016-01-28 13:13:01 +00:00
end
2016-03-08 18:06:55 +00:00
# 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
2016-01-28 13:13:01 +00:00
2016-03-08 18:06:55 +00:00
def status ( name = nil )
return @legacy . status ( name ) if @legacy
2016-01-29 09:03:17 +00:00
2016-03-08 18:06:55 +00:00
@status_content || = inspec . command ( '/sbin/auditctl -s' ) . stdout . chomp
@status_params || = Hash [ @status_content . scan ( / ^([^ ]+) (.*)$ / ) ]
2016-01-29 09:03:17 +00:00
2016-03-08 18:06:55 +00:00
return @status_params [ name ] if name
@status_params
end
2016-01-29 09:03:17 +00:00
2016-03-08 18:06:55 +00:00
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 }
2016-01-28 16:08:41 +00:00
end
end
end
2016-01-28 13:13:01 +00:00
2016-03-08 18:06:55 +00:00
def syscall ( name )
select_name ( :syscall , name )
end
2016-01-28 16:08:41 +00:00
2016-03-08 18:06:55 +00:00
def file ( name )
select_name ( :file , name )
end
2016-01-28 13:13:01 +00:00
2016-03-08 18:06:55 +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
2016-01-29 15:29:26 +00:00
2016-03-08 18:06:55 +00:00
def to_s
'Audit Daemon Rules'
end
2016-01-28 16:08:41 +00:00
2016-03-08 18:06:55 +00:00
private
2016-01-28 16:08:41 +00:00
2016-03-08 18:06:55 +00:00
def select_name ( key , name )
plural = " #{ key } s " . to_sym
res = rules [ plural ] . find_all { | rule | rule [ key ] == name }
FilterArray . new ( res )
end
2016-01-28 16:08:41 +00:00
2016-03-08 18:06:55 +00:00
def is_syscall? ( line )
line . match ( / \ -S / )
end
2016-01-28 16:08:41 +00:00
2016-03-08 18:06:55 +00:00
def is_file? ( line )
line . match ( / -w / )
end
2016-01-28 16:08:41 +00:00
2016-03-08 18:06:55 +00:00
def get_syscalls ( line )
line . scan ( / -S ([^ ]+) / ) . flatten . first . split ( ',' )
end
2016-01-28 16:08:41 +00:00
2016-03-08 18:06:55 +00:00
def get_action_list ( line )
line . scan ( / -a ([^,]+),([^ ]+) / ) . flatten
end
2016-01-28 16:08:41 +00:00
2016-03-08 18:06:55 +00:00
# NB only in file lines
def get_key ( line )
line . match ( / -k ([^ ]+) / ) [ 1 ]
end
2016-01-28 16:08:41 +00:00
2016-03-08 18:06:55 +00:00
# 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
2016-01-28 16:08:41 +00:00
2016-03-08 18:06:55 +00:00
def get_permissions ( line )
line . match ( / -p ([^ ]+) / ) [ 1 ]
end
2016-01-28 16:08:41 +00:00
2016-03-08 18:06:55 +00:00
def get_fields ( line )
fields = line . gsub ( / -[aS] [^ ]+ / , '' ) . split ( '-F ' ) . map { | l | l . split ( ' ' ) } . flatten
2016-01-29 10:13:52 +00:00
2016-03-08 18:06:55 +00:00
opts = { }
fields . find_all { | x | x . match ( / [a-z]+=.* / ) } . each do | kv |
k , v = kv . split ( '=' )
opts [ k . to_sym ] = v
end
2016-01-28 16:08:41 +00:00
2016-03-08 18:06:55 +00:00
[ fields , opts ]
end
2016-01-28 16:08:41 +00:00
end
2015-07-26 20:44:01 +00:00
end