Skip to content

Commit

Permalink
config: introduce audit options
Browse files Browse the repository at this point in the history
This patch introduces all audit options.

Part of tarantool#8861

NO_DOC=Was already described before.
  • Loading branch information
ImeevMA committed Aug 24, 2023
1 parent 9af36ae commit cbe39f7
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 12 deletions.
3 changes: 3 additions & 0 deletions changelogs/unreleased/gh-8861-audit-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## feature/config

* All audit options are now supported (gh-8861).
35 changes: 31 additions & 4 deletions src/box/lua/config/applier/box_cfg.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ local fio = require('fio')
local log = require('internal.config.utils.log')
local instance_config = require('internal.config.instance_config')

local has_audit = pcall(require, 'audit')

local function peer_uri(configdata, peer_name)
local iconfig = configdata._peers[peer_name].iconfig_def
return instance_config:instance_uri(iconfig, 'peer')
Expand Down Expand Up @@ -43,9 +45,8 @@ local function peer_uris(configdata)
return uris
end

local function log_destination(configdata)
local log = configdata:get('log', {use_default = true})
if log.to == 'stderr' then
local function log_destination(log)
if log.to == nil or log.to == 'stderr' or log.to == 'devnull' then
return box.NULL
elseif log.to == 'file' then
return ('file:%s'):format(log.file)
Expand Down Expand Up @@ -137,7 +138,33 @@ local function apply(config)
-- `log.nonblock`, `log.level`, `log.format`, 'log.modules'
-- options are marked with the `box_cfg` annotations and so
-- they're already added to `box_cfg`.
box_cfg.log = log_destination(configdata)
local cfg_log = configdata:get('log', {use_default = true})
box_cfg.log = log_destination(cfg_log)

-- Construct audit logger destination and audit filter (box_cfg.audit_log
-- and audit_filter).
--
-- `audit_log.nonblock`, `audit_log.filter` and 'audit_log.filter' options
-- are marked with the `box_cfg` annotations and so they're already added
-- to `box_cfg`.
--
-- Also, `audit_log.filter` cannot be validated in instance_config.lua at
-- the moment, so validate it here.
local audit_log = configdata:get('audit_log', {use_default = true})
if audit_log ~= nil and next(audit_log) ~= nil then
box_cfg.audit_log = log_destination(audit_log)
-- All options except audit_log.filter were already checked.
assert(has_audit or audit_log.filter ~= nil)
if not has_audit then
error('audit_log.filter: Module audit is not available', 0)
end
if audit_log.filter ~= nil then
assert(type(audit_log.filter) == 'table')
box_cfg.audit_filter = table.concat(audit_log.filter, ',')
else
box_cfg.audit_filter = 'compatibility'
end
end

local failover = configdata:get('replication.failover',
{use_default = true})
Expand Down
116 changes: 115 additions & 1 deletion src/box/lua/config/instance_config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ local urilib = require('uri')

local CONFIG_VERSION = 'dev'

local has_audit = pcall(require, 'audit')

-- Any configuration data should contain a version of the config
-- schema for which it is written.
--
Expand Down Expand Up @@ -358,6 +360,17 @@ local function feedback_validate(data, w)
w.error('Tarantool is built without feedback reports sending support')
end

local function audit_apply_default_if(_data, _w)
return has_audit
end

local function audit_validate(data, w)
if data == nil or has_audit then
return
end
w.error('Module audit is not available')
end

return schema.new('instance_config', schema.record({
config = schema.record({
version = schema.enum({
Expand Down Expand Up @@ -1694,7 +1707,108 @@ return schema.new('instance_config', schema.record({
end
end,
}),
})
}),
audit_log = schema.record({
to = schema.enum({
'devnull',
'file',
'pipe',
'syslog',
}, {
default = 'devnull',
apply_default_if = audit_apply_default_if,
validate = audit_validate,
}),
file = schema.scalar({
type = 'string',
default = 'var/log/{{ instance_name }}/audit.log',
apply_default_if = audit_apply_default_if,
validate = audit_validate,
}),
pipe = schema.scalar({
type = 'string',
default = box.NULL,
apply_default_if = audit_apply_default_if,
validate = audit_validate,
}),
syslog = schema.record({
identity = schema.scalar({
type = 'string',
default = 'tarantool',
apply_default_if = audit_apply_default_if,
validate = audit_validate,
}),
facility = schema.scalar({
type = 'string',
default = 'local7',
apply_default_if = audit_apply_default_if,
validate = audit_validate,
}),
server = schema.scalar({
type = 'string',
-- The logger tries /dev/log and then
-- /var/run/syslog if no server is provided.
default = box.NULL,
apply_default_if = audit_apply_default_if,
validate = audit_validate,
}),
}),
nonblock = schema.scalar({
type = 'boolean',
box_cfg = 'audit_nonblock',
box_cfg_nondynamic = true,
default = false,
apply_default_if = audit_apply_default_if,
validate = audit_validate,
}),
format = schema.enum({
'plain',
'json',
'csv',
}, {
box_cfg = 'audit_format',
default = 'json',
apply_default_if = audit_apply_default_if,
validate = audit_validate,
}),
filter = schema.set({
"audit_enable",
"custom",
"auth_ok",
"auth_fail",
"disconnect",
"user_create",
"user_drop",
"role_create",
"role_drop",
"user_enable",
"user_disable",
"user_grant_rights",
"user_revoke_rights",
"role_grant_rights",
"role_revoke_rights",
"password_change",
"access_denied",
"eval",
"call",
"space_select",
"space_create",
"space_alter",
"space_drop",
"space_insert",
"space_replace",
"space_delete",
"none",
"all",
"audit",
"auth",
"priv",
"ddl",
"dml",
"data_operations",
"compatibility",
}),
}),
}, {
-- This kind of validation cannot be implemented as the
-- 'validate' annotation of a particular schema node. There
Expand Down
15 changes: 14 additions & 1 deletion test/config-luatest/cluster_config_schema_test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ local cluster_config = require('internal.config.cluster_config')
local g = t.group()

local is_enterprise = require('tarantool').package == 'Tarantool Enterprise'
local has_audit = pcall(require, 'audit')

g.test_cluster_config = function()
local config = {
Expand Down Expand Up @@ -270,7 +271,19 @@ g.test_defaults = function()
sched_ref_quota = 300,
shard_index = "bucket_id",
sync_timeout = 1,
}
},
audit_log = has_audit and {
file = "var/log/{{ instance_name }}/audit.log",
format = "json",
nonblock = false,
pipe = box.NULL,
syslog = {
facility = "local7",
identity = "tarantool",
server = box.NULL
},
to = "devnull",
} or nil,
}
local res = cluster_config:apply_default({})
t.assert_equals(res, exp)
Expand Down
44 changes: 44 additions & 0 deletions test/config-luatest/config_test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ local json = require('json')

local g = t.group()

local has_audit = pcall(require, 'audit')

g.before_all(function()
treegen.init(g)
end)
Expand Down Expand Up @@ -779,3 +781,45 @@ g.test_metrics_options = function()
t.assert_equals(box.cfg.metrics.labels, {foo = 'bar'})
end)
end

g.test_audit_options = function()
t.skip_if(not has_audit)
local dir = treegen.prepare_directory(g, {}, {})
local config = [[
credentials:
users:
guest:
roles:
- super
iproto:
listen: unix/:./{{ instance_name }}.iproto
audit_log:
to: devnull
nonblock: true
format: csv
filter: [space_select, role_grant_rights]
groups:
group-001:
replicasets:
replicaset-001:
instances:
instance-001: {}
]]
local config_file = treegen.write_script(dir, 'config.yaml', config)
local opts = {
config_file = config_file,
alias = 'instance-001',
chdir = dir,
}
g.server = server:new(opts)
g.server:start()
g.server:exec(function()
t.assert_equals(box.cfg.audit_log, nil)
t.assert_equals(box.cfg.audit_nonblock, true)
t.assert_equals(box.cfg.audit_format, 'csv')
t.assert_equals(box.cfg.audit_filter, 'space_select,role_grant_rights')
end)
end
63 changes: 57 additions & 6 deletions test/config-luatest/instance_config_schema_test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ local instance_config = require('internal.config.instance_config')
local g = t.group()

local is_enterprise = require('tarantool').package == 'Tarantool Enterprise'
local has_audit = pcall(require, 'audit')

-- Check that all record element names can be found in the table and vice versa.
local function validate_fields(config, record)
Expand Down Expand Up @@ -980,6 +981,8 @@ g.test_box_cfg_coverage = function()
cluster_name = true,
log = true,
metrics = true,
audit_log = true,
audit_filter = true,

-- Controlled by the leader and database.mode options,
-- handled by the box_cfg applier.
Expand All @@ -994,19 +997,14 @@ g.test_box_cfg_coverage = function()

-- Moved to the CLI options (see gh-8876).
force_recovery = true,

-- TODO: Will be added in the scope of gh-8861.
audit_log = true,
audit_nonblock = true,
audit_format = true,
audit_filter = true,
}

-- There are options, where defaults are changed deliberately.
local ignore_default = {
-- box.cfg.log_nonblock is set to nil by default, but
-- actually it means false.
log_nonblock = true,
audit_nonblock = true,

-- Adjusted to use {{ instance_name }}.
custom_proc_title = true,
Expand Down Expand Up @@ -1036,6 +1034,10 @@ g.test_box_cfg_coverage = function()
if w.schema.box_cfg:startswith('feedback') then
return box.internal.feedback_daemon ~= nil
end
-- Skip audit options if audit is not available.
if w.schema.box_cfg:startswith('audit') and not has_audit then
return false
end
return true
end):map(function(w)
return w.schema.box_cfg, {
Expand Down Expand Up @@ -1292,3 +1294,52 @@ g.test_sharding = function()
local res = instance_config:apply_default({}).sharding
t.assert_equals(res, exp)
end

g.test_audit_unavailable = function()
t.skip_if(has_audit)
local iconfig = {
audit_log = {
to = 'file',
},
}
local err = '[instance_config] audit_log.to: Module audit is not available'
t.assert_error_msg_equals(err, function()
instance_config:validate(iconfig)
end)
end

g.test_audit_available = function()
t.skip_if(not has_audit)
local iconfig = {
audit_log = {
to = 'file',
file = 'one',
pipe = 'two',
syslog = {
identity = 'three',
facility = 'four',
server = 'five',
},
nonblock = true,
format = 'plain',
filter = {'all', 'none'}
},
}
instance_config:validate(iconfig)
validate_fields(iconfig.audit_log, instance_config.schema.fields.audit_log)

local exp = {
file = "var/log/{{ instance_name }}/audit.log",
format = "json",
nonblock = false,
pipe = box.NULL,
syslog = {
facility = "local7",
identity = "tarantool",
server = box.NULL
},
to = "devnull",
}
local res = instance_config:apply_default({}).audit_log
t.assert_equals(res, exp)
end

0 comments on commit cbe39f7

Please sign in to comment.