diff options
author | Kaarle Ritvanen <kaarle.ritvanen@datakunkku.fi> | 2013-02-08 07:19:34 +0000 |
---|---|---|
committer | Kaarle Ritvanen <kaarle.ritvanen@datakunkku.fi> | 2013-02-08 07:19:34 +0000 |
commit | c609c91fe3b2640f4714360f1d93170b775e2171 (patch) | |
tree | c805a791c5b8e401f166f30134498a1e672bd2df | |
parent | 8a4a82b055a101ae79cedbdd426d704f81654ab7 (diff) | |
download | awall-c609c91fe3b2640f4714360f1d93170b775e2171.tar.bz2 awall-c609c91fe3b2640f4714360f1d93170b775e2171.tar.xz |
overhaul of policy file handling
private policies which can be imported but not directly enabled
show more information about policies with awall list -a, fixes #1467
override policy file paths using AWALL_PATH_* environment variables
-rw-r--r-- | Makefile | 3 | ||||
-rwxr-xr-x | awall-cli | 82 | ||||
-rw-r--r-- | awall/policy.lua | 212 | ||||
-rw-r--r-- | awall/util.lua | 14 |
4 files changed, 160 insertions, 151 deletions
@@ -1,5 +1,5 @@ # Installer Makefile for Alpine Wall -# Copyright (C) 2012 Kaarle Ritvanen +# Copyright (C) 2012-2013 Kaarle Ritvanen # See LICENSE file for license details ROOT_DIR := / @@ -42,6 +42,7 @@ $(eval $(call rename,sample-policy.json,$(poldir)/sample/sample-policy.json,644) $(eval $(call mkdir,$(confdir))) $(eval $(call mkdir,$(confdir)/optional)) $(eval $(call mkdir,$(poldir)/optional)) +$(eval $(call mkdir,$(poldir)/private)) $(eval $(call mkdir,var/run/awall)) install: $(foreach f,$(files),$(ROOT_DIR)/$(f)) @@ -11,9 +11,6 @@ require 'lfs' require 'signal' require 'stringy' -short_opts = 'fo:V' -long_opts = {force='f', ['output-dir']='o', verify='V'} - function help() io.stderr:write([[ Alpine Wall @@ -54,7 +51,7 @@ Enable/disable optional policies: awall {enable|disable} <policy>... List optional policies: - awall list + awall list [-a|--all] The 'enabled' status means that the policy has been enabled by the user. The 'disabled' status means that the policy is not in @@ -62,6 +59,9 @@ List optional policies: enabled by the user but is in use because it is required by another policy which is in use. + Normally, the command lists only optional policies. Specifying + --all makes it list all policies and more information about them. + Dump variable and zone definitions: awall dump [level] @@ -71,18 +71,6 @@ Dump variable and zone definitions: os.exit(1) end -params = {} - -if stringy.endswith(arg[0], '/awall-cli') then - basedir = string.sub(arg[0], 1, -11) - params.i = {basedir..'/json'} - params.I = {} - - short_opts = short_opts..'i:I:' - long_opts['input-dir'] = 'i' - long_opts['import-path'] = 'I' -end - if not arg[1] then help() end if not stringy.startswith(arg[1], '-') then @@ -90,12 +78,18 @@ if not stringy.startswith(arg[1], '-') then table.remove(arg, 1) end -opts, opind = alt_getopt.get_opts(arg, short_opts, long_opts) +opts, opind = alt_getopt.get_opts( + arg, + 'afo:V', + {all='a', force='f', ['output-dir']='o', verify='V'} +) for switch, value in pairs(opts) do - if switch == 'f' then force = true + if switch == 'a' then all = true + elseif switch == 'f' then force = true + elseif switch == 'c' then verbose = true elseif switch == 'V' then verify = true elseif switch == 'o' then outputdir = value - else table.insert(params[switch], value) end + else assert(false) end end if not mode then @@ -111,25 +105,63 @@ if not util.contains({'translate', 'activate', 'fallback', 'flush', 'enable', 'disable', 'list', 'dump'}, mode) then help() end +pol_paths = {} +for i, cls in ipairs{'mandatory', 'optional', 'private'} do + path = os.getenv('AWALL_PATH_'..string.upper(cls)) + if path then pol_paths[cls] = util.split(path, ':') end +end -require 'awall.uerror' +if stringy.endswith(arg[0], '/awall-cli') then + basedir = string.sub(arg[0], 1, -11) + if not pol_paths.mandatory then + pol_paths.mandatory = {'/etc/awall'} + end + table.insert(pol_paths.mandatory, basedir..'/json') +end + +local uerror = require('awall.uerror') -if not awall.uerror.call( +if not uerror.call( function() require 'awall' - policyset = awall.PolicySet.new(params.i, params.I) + policyset = awall.PolicySet.new(pol_paths) if mode == 'list' then - util.printtabular(policyset:list()) + imported = policyset:load().policies + data = {} + + for i, name in util.sortedkeys(policyset.policies) do + policy = policyset.policies[name] + + if all or policy.type == 'optional' then + if policy.enabled then status = 'enabled' + elseif util.contains(imported, name) then status = 'required' + else status = 'disabled' end + + polinfo = {name, status, policy:load().description} + + if all then + table.insert(polinfo, 2, policy.type) + table.insert(polinfo, 4, policy.path) + end + + table.insert(data, polinfo) + end + end + + util.printtabular(data) os.exit() end if util.contains({'disable', 'enable'}, mode) then if opind > #arg then help() end repeat - policyset[mode](policyset, arg[opind]) + name = arg[opind] + policy = policyset.policies[name] + if not policy then uerror.raise('No such policy: '..name) end + policy[mode](policy) opind = opind + 1 until opind > #arg os.exit() @@ -246,7 +278,7 @@ if not awall.uerror.call( os.exit(1) end - if awall.uerror.call(config.activate, config) then + if uerror.call(config.activate, config) then if not force then io.stderr:write('New firewall configuration activated\n') diff --git a/awall/policy.lua b/awall/policy.lua index 8633fb2..f9b023e 100644 --- a/awall/policy.lua +++ b/awall/policy.lua @@ -8,15 +8,14 @@ module(..., package.seeall) require 'json' require 'lfs' -require 'lpc' require 'awall.dependency' -require 'awall.object' +local class = require('awall.object').class local raise = require('awall.uerror').raise local util = require('awall.util') -local PolicyConfig = awall.object.class() +local PolicyConfig = class() function PolicyConfig:init(data, source, policies) self.data = data @@ -59,102 +58,115 @@ function PolicyConfig:expand() end +local Policy = class() -local function open(name, dirs) - if not string.match(name, '^[%w-]+$') then - raise('Invalid characters in policy name: '..name) - end - for i, dir in ipairs(dirs) do - local path = dir..'/'..name..'.json' - file = io.open(path) - if file then return file, path end - end -end +function Policy:init() self.enabled = self.type == 'mandatory' end -local function find(name, dirs) - local file, path = open(name, dirs) - if file then file:close() end - return path -end +function Policy:load() + local file = io.open(self.path) + if not file then raise('Unable to read policy file '..self.path) end -local function list(dirs) - local allnames = {} - local res = {} - - for i, dir in ipairs(dirs) do - local names = {} - local paths = {} + local data = '' + for line in file:lines() do data = data..line end + file:close() - for fname in lfs.dir(dir) do - local si, ei, name = string.find(fname, '^([%w-]+)%.json$') - if name then - if util.contains(allnames, name) then - raise('Duplicate policy name: '..name) - end - table.insert(allnames, name) + local success, res = pcall(json.decode, data) + if success then return res end + raise(res..' while parsing '..self.path) +end - table.insert(names, name) - paths[name] = dir..'/'..fname - end - end +function Policy:checkoptional() + if self.type ~= 'optional' then raise('Not an optional policy: '..name) end +end - table.sort(names) - for i, name in ipairs(names) do - table.insert(res, {name, paths[name]}) - end - end +function Policy:enable() + self:checkoptional() + if self.enabled then raise('Policy already enabled: '..self.name) end + assert(lfs.link(self.path, self.confdir..'/'..self.fname, true)) +end - return res +function Policy:disable() + self:checkoptional() + if not self.enabled then raise('Policy already disabled: '..self.name) end + assert(os.remove(self.confdir..'/'..self.fname)) end -PolicySet = awall.object.class() +local defdirs = { + mandatory={'/etc/awall', '/usr/share/awall/mandatory'}, + optional={'/etc/awall/optional', '/usr/share/awall/optional'}, + private={'/etc/awall/private', '/usr/share/awall/private'} +} -function PolicySet:init(confdirs, importdirs) - self.autodirs = confdirs or {'/usr/share/awall/mandatory', '/etc/awall'} - self.confdir = self.autodirs[#self.autodirs] - self.importdirs = importdirs or {'/usr/share/awall/optional', - '/etc/awall/optional'} -end +PolicySet = class() +function PolicySet:init(dirs) + local confdir = (dirs.mandatory or defdirs.mandatory)[1] + self.policies = {} -function PolicySet:loadJSON(name, fname) - local file - if fname then - file = io.open(fname) - else - file, fname = open(name, self.importdirs) - end - if not file then raise('Unable to read policy file '..fname) end + for i, cls in ipairs{'private', 'optional', 'mandatory'} do + for i, dir in ipairs(dirs[cls] or defdirs[cls]) do + for fname in lfs.dir(dir) do + local si, ei, name = string.find(fname, '^([%w-]+)%.json$') + if name then + local pol = self.policies[name] - local data = '' - for line in file:lines() do data = data..line end - file:close() + local path = dir..'/'..fname + if string.sub(path, 1, 1) ~= '/' then + path = lfs.currentdir()..'/'..path + end - local success, res = pcall(json.decode, data) - if success then return res end - raise(res..' while parsing '..fname) + local attrs = lfs.attributes(path) + local loc = attrs.dev..':'..attrs.ino + + if pol then + if pol.loc ~= loc then + raise('Duplicate policy name: '..name) + end + + if dir == confdir and pol.type == 'optional' then + pol.enabled = true + else pol.type = cls end + + else + self.policies[name] = Policy.morph{ + name=name, + type=cls, + path=path, + fname=fname, + loc=loc, + confdir=confdir + } + end + end + end + end + end end function PolicySet:load() - - local policies = {} - local function require(name, fname) - if policies[name] then return end + local imported = {} + + local function require(policy) + if imported[policy.name] then return end - local policy = self:loadJSON(name, fname) - policies[name] = policy + local data = policy:load() + imported[policy.name] = data - if not policy.after then policy.after = policy.import end - for i, iname in util.listpairs(policy.import) do require(iname) end + if not data.after then data.after = data.import end + for i, name in util.listpairs(data.import) do + require(self.policies[name]) + end end - for i, pol in ipairs(list(self.autodirs)) do require(unpack(pol)) end + for name, policy in pairs(self.policies) do + if policy.enabled then require(policy) end + end - local order = awall.dependency.order(policies) + local order = awall.dependency.order(imported) if type(order) ~= 'table' then raise('Circular ordering directives: '..order) end @@ -164,7 +176,7 @@ function PolicySet:load() local source = {} for i, name in ipairs(order) do - for cls, objs in pairs(policies[name]) do + for cls, objs in pairs(imported[name]) do if not util.contains({'description', 'import', 'after', 'before'}, cls) then if not source[cls] then source[cls] = {} end @@ -188,55 +200,5 @@ function PolicySet:load() end end - return PolicyConfig.new(input, source, util.keys(policies)) -end - - -function PolicySet:findsymlink(name) - local symlink = find(name, {self.confdir}) - if symlink and lfs.symlinkattributes(symlink).mode ~= 'link' then - raise('Not an optional policy: '..name) - end - return symlink -end - -function PolicySet:enable(name) - if self:findsymlink(name) then raise('Policy already enabled: '..name) - else - local target = find(name, self.importdirs) - if not target then raise('Policy not found: '..name) end - if string.sub(target, 1, 1) ~= '/' then - target = lfs.currentdir()..'/'..target - end - - local pid, stdin, stdout = lpc.run('ln', '-s', target, self.confdir) - stdin:close() - stdout:close() - assert(lpc.wait(pid) == 0) - end -end - -function PolicySet:disable(name) - local symlink = self:findsymlink(name) - if not symlink then raise('Policy not enabled: '..name) end - assert(os.remove(symlink)) -end - -function PolicySet:list() - local imported = self:load().policies - local res = {} - - for i, pol in ipairs(list(self.importdirs)) do - local name = pol[1] - - local status - if self:findsymlink(name) then status = 'enabled' - elseif util.contains(imported, name) then status = 'required' - else status = 'disabled' end - - table.insert(res, - {name, status, self:loadJSON(name, pol[2]).description}) - end - - return res + return PolicyConfig.new(input, source, util.keys(imported)) end diff --git a/awall/util.lua b/awall/util.lua index 5676f1d..4360198 100644 --- a/awall/util.lua +++ b/awall/util.lua @@ -7,6 +7,20 @@ See LICENSE file for license details module(..., package.seeall) +function split(s, sep) + if s == '' then return {} end + local res = {} + while true do + local si, ei = string.find(s, sep, 1, true) + if not si then + table.insert(res, s) + return res + end + table.insert(res, string.sub(s, 1, si - 1)) + s = string.sub(s, ei + 1, -1) + end +end + function list(var) if not var then return {} end if type(var) ~= 'table' then return {var} end |