Commit 7b771974 authored by Marek Vavrusa's avatar Marek Vavrusa

modules/daf: trivial rule compiler implemented

the fw can now parse simple rules such as:
'qname = *.example.com AND src = 127.0.0.1/8 deny'

and turn it into filter actions.

this is a building block for custom firewall rules
based on query/answer contents that leverage
existing policy/view modules, but turn those into
easier to write (and eventually persistent) rule
sets
parent 264c08a0
local cqueues = require('cqueues')
-- Load dependent modules
if not view then modules.load('view') end
if not policy then modules.load('policy') end
-- Actions
local actions = {
pass = 1, deny = 2, drop = 3, tc = 4, forward = policy.FORWARD,
}
-- Filter rules per column
local filters = {
-- Filter on QNAME (either pattern or suffix match)
qname = function (g)
local op, val = g(), todname(g())
if op == '~' then return policy.pattern(true, val)
elseif op == '=' then return policy.suffix(true, {val})
else error(string.format('invalid operator "%s" on qname', op)) end
end,
-- Filter on source address
src = function (g)
local op = g()
if op ~= '=' then error('source address supports only "=" operator') end
return view.rule(true, g())
end
}
local function parse_filter(tok, g)
local filter = filters[tok]
if not filter then error(string.format('invalid filter "%s"', tok)) end
return filter(g)
end
local function parse_rule(g)
local f = parse_filter(g(), g)
-- Compose filter functions on conjunctions
-- or terminate filter chain and return
local tok = g()
while tok do
if tok == 'AND' then
local fnext = parse_filter(g(), g)
f = function (req, qry) return f(req, qry) and fnext(req, qry) end
elseif tok == 'OR' then
local fnext = parse_filter(g(), g)
f = function (req, qry) return f(req, qry) or fnext(req, qry) end
else
break
end
tok = g()
print('next token is', tok)
end
return tok, f
end
local function parse_query(g)
local ok, action, filter = pcall(parse_rule, g)
if not ok then return nil, action end
if not actions[action] then return nil, string.format('invalid action "%s"', action) end
-- Parse and interpret action
action = actions[action]
if type(action) == 'function' then
action = action(g())
end
return action, filter
end
-- Compile a rule described by query language
-- The query language is modelled by iptables/nftables
-- conj = AND | OR
-- op = IS | NOT | LIKE | IN
-- filter = <key> <op> <expr>
-- rule = <filter> | <filter> <conj> <rule>
-- action = PASS | DENY | DROP | TC | FORWARD
-- query = <rule> <action>
local function compile(query)
local g = string.gmatch(query, '%S+')
return parse_query(g)
end
-- Module declaration
local M = {
rules = {}
}
-- @function Public-facing API
......@@ -39,6 +118,16 @@ function M.config(conf)
http.snippets['/daf'] = {'Application Firewall', [[
<p>Hello world!</p>
]]}
M.rule('qname = *.example.com AND src = 127.0.0.1/8 deny')
-- M.rule('answer ~ (%w+).facebook.com AND src = 127.0.0.1/8 forward 8.8.8.8')
end
-- @function Add rule
function M.rule(rule)
local action, filter = compile(rule)
if not action then error(filter) end
table.insert(M.rules, {rule, action, filter})
print(action, filter, rule)
end
return M
\ No newline at end of file
......@@ -19,7 +19,9 @@ function view.addr(view, subnet, policy)
local subnet_cd = ffi.new('char[16]')
local family = C.kr_straddr_family(subnet)
local bitlen = C.kr_straddr_subnet(subnet_cd, subnet)
table.insert(view.subnet, {family, subnet_cd, bitlen, policy})
local t = {family, subnet_cd, bitlen, policy}
table.insert(view.subnet, t)
return t
end
-- @function Match IP against given subnet
......@@ -29,7 +31,6 @@ end
-- @function Find view for given request
local function evaluate(view, req)
local answer = req.answer
local client_key = req.qsource.key
local match_cb = (client_key ~= nil) and view.key[client_key:owner()] or nil
-- Search subnets otherwise
......@@ -45,6 +46,19 @@ local function evaluate(view, req)
return match_cb
end
-- @function Return view policy rule
function view.rule(action, subnet)
local subnet_cd = ffi.new('char[16]')
local family = C.kr_straddr_family(subnet)
local bitlen = C.kr_straddr_subnet(subnet_cd, subnet)
return function(req, _)
local src_addr = req.qsource.addr
if src_addr ~= nil and match_subnet(family, subnet_cd, bitlen, src_addr) then
return action
end
end
end
-- @function Module layers
view.layer = {
begin = function(state, req)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment