aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKaarle Ritvanen <kaarle.ritvanen@datakunkku.fi>2013-02-08 07:19:34 +0000
committerKaarle Ritvanen <kaarle.ritvanen@datakunkku.fi>2013-02-08 07:19:34 +0000
commitc609c91fe3b2640f4714360f1d93170b775e2171 (patch)
treec805a791c5b8e401f166f30134498a1e672bd2df
parent8a4a82b055a101ae79cedbdd426d704f81654ab7 (diff)
downloadawall-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--Makefile3
-rwxr-xr-xawall-cli82
-rw-r--r--awall/policy.lua212
-rw-r--r--awall/util.lua14
4 files changed, 160 insertions, 151 deletions
diff --git a/Makefile b/Makefile
index 3d10dac..3614c71 100644
--- a/Makefile
+++ b/Makefile
@@ -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))
diff --git a/awall-cli b/awall-cli
index c86eb8f..154fbdd 100755
--- a/awall-cli
+++ b/awall-cli
@@ -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