Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • knot/knot-resolver
  • dkg/resolver
  • sbalazik/resolver
  • anb/knot-resolver
  • tkrizek/knot-resolver
  • jono/knot-resolver
  • analogic/knot-resolver
  • flokli/knot-resolver
  • hectorm/knot-resolver
  • aisha/knot-resolver
10 results
Show changes
Commits on Source (24)
......@@ -14,3 +14,7 @@ bogus_log_mod = shared_module(
install: true,
install_dir: modules_dir,
)
integr_tests += [
['bogus_log', join_paths(meson.current_source_dir(), 'test.integr')],
]
......@@ -18,42 +18,46 @@ Firewall rules are declarative and consist of filters and actions. Filters have
modules = { 'daf' }
-- Block all queries with QNAME = example.com
daf.add 'qname = example.com deny'
daf.add('qname = example.com deny')
-- Filters can be combined using AND/OR...
-- Block all queries with QNAME match regex and coming from given subnet
daf.add 'qname ~ %w+.example.com AND src = 192.0.2.0/24 deny'
daf.add('qname ~ %w+.example.com AND src = 192.0.2.0/24 deny')
-- We also can reroute addresses in response to alternate target
-- This reroutes 1.2.3.4 to localhost
daf.add 'src = 127.0.0.0/8 reroute 192.0.2.1-127.0.0.1'
-- This reroutes 192.0.2.1 to localhost
daf.add('src = 127.0.0.0/8 reroute 192.0.2.1-127.0.0.1')
-- Subnets work too, this reroutes a whole subnet
-- e.g. 192.0.2.55 to 127.0.0.55
daf.add 'src = 127.0.0.0/8 reroute 192.0.2.0/24-127.0.0.0'
daf.add('src = 127.0.0.0/8 reroute 192.0.2.0/24-127.0.0.0')
-- This rewrites all A answers for 'example.com' from
-- whatever the original address was to 127.0.0.2
daf.add 'src = 127.0.0.0/8 rewrite example.com A 127.0.0.2'
daf.add('src = 127.0.0.0/8 rewrite example.com A 127.0.0.2')
-- Mirror queries matching given name to DNS logger
daf.add 'qname ~ %w+.example.com mirror 127.0.0.2'
daf.add 'qname ~ example-%d.com mirror 127.0.0.3@5353'
daf.add('qname ~ %w+.example.com mirror 127.0.0.2')
daf.add('qname ~ example-%d.com mirror 127.0.0.3@5353')
-- Forward queries from subnet
daf.add 'src = 127.0.0.1/8 forward 127.0.0.1@5353'
daf.add('src = 127.0.0.1/8 forward 127.0.0.1@5353')
-- Forward to multiple targets
daf.add 'src = 127.0.0.1/8 forward 127.0.0.1@5353,127.0.0.2@5353'
daf.add('src = 127.0.0.1/8 forward 127.0.0.1@5353,127.0.0.2@5353')
-- Truncate queries based on destination IPs
daf.add 'dst = 192.0.2.51 truncate'
daf.add('dst = 192.0.2.51 truncate')
-- Disable a rule
daf.disable 2
daf.disable(2)
-- Enable a rule
daf.enable 2
daf.enable(2)
-- Delete a rule
daf.del 2
daf.del(2)
.. warning:: Only the first matching rule's action is executed. Defining
additional actions for the same matching rule, e.g. ``src = 127.0.0.1/8``,
will have no effect.
If you're not sure what firewall rules are in effect, see ``daf.rules``:
......
......@@ -118,7 +118,7 @@ function ruleControl(cell, type, url, action) {
url: 'daf/' + row.data('rule-id') + url,
type: type,
success: action,
fail: function (data) {
error: function (data) {
row.show();
const reason = data.responseText.length > 0 ? data.responseText : 'internal error';
cell.find('.alert').remove();
......@@ -172,7 +172,7 @@ function loadTable(resp) {
}
}
$(function() {
document.addEventListener("DOMContentLoaded", () => {
/* Load the filter table. */
$.ajax({
url: 'daf',
......@@ -181,7 +181,7 @@ $(function() {
success: loadTable
});
/* Listen for counter updates */
const wsStats = (secure ? 'wss://' : 'ws://') + location.host + '/daf';
const wsStats = ('https:' == document.location.protocol ? 'wss://' : 'ws://') + location.host + '/daf';
const ws = new Socket(wsStats);
var lastRateUpdate = Date.now();
ws.onmessage = function(evt) {
......
......@@ -6,7 +6,11 @@ if not policy then modules.load('policy') end
-- Actions
local actions = {
pass = 1, deny = 2, drop = 3, tc = 4, truncate = 4,
pass = function() return policy.PASS end,
deny = function () return policy.DENY end,
drop = function() return policy.DROP end,
tc = function() return policy.TC end,
truncate = function() return policy.TC end,
forward = function (g)
local addrs = {}
local tok = g()
......@@ -75,6 +79,9 @@ end
local function parse_rule(g)
-- Allow action without filter
local tok = g()
if tok == nil then
error('empty rule is not allowed')
end
if not filters[tok:lower()] then
return tok, nil
end
......@@ -137,9 +144,10 @@ local M = {
-- @function Cleanup module
function M.deinit()
if http and http.endpoints then
http.endpoints['/daf'] = nil
http.endpoints['/daf.js'] = nil
if http then
local endpoints = http.configs._builtin.webmgmt.endpoints
endpoints['/daf'] = nil
endpoints['/daf.js'] = nil
http.snippets['/daf'] = nil
end
end
......@@ -176,10 +184,10 @@ end
-- @function Remove a rule
function M.del(id)
for _, r in ipairs(M.rules) do
for key, r in ipairs(M.rules) do
if r.rule.id == id then
policy.del(id)
table.remove(M.rules, id)
table.remove(M.rules, key)
return true
end
end
......@@ -213,9 +221,13 @@ function M.enable(id)
end
local function consensus(op, ...)
local ret = true
local ret = false
local results = map(string.format(op, ...))
for _, r in ipairs(results) do
for idx, r in ipairs(results) do
if idx == 1 then
-- non-empty table, init to true
ret = true
end
ret = ret and r
end
return ret
......@@ -246,7 +258,7 @@ local function api(h, stream)
local path = h:get(':path')
local id = tonumber(path:match '/([^/]*)$')
if id then
if consensus('daf.del "%s"', id) then
if consensus('daf.del(%s)', id) then
return tojson(true)
end
return 404, '"No such rule"' -- Not found
......@@ -323,12 +335,23 @@ local function publish(_, ws)
end
end
function M.init()
-- avoid ordering problem between HTTP and daf module
event.after(0, M.config)
end
-- @function Configure module
function M.config()
if not http or not http.endpoints then return end
if not http then
if verbose() then
log('[daf ] HTTP API unavailable because HTTP module is not loaded, use modules.load("http")')
end
return
end
local endpoints = http.configs._builtin.webmgmt.endpoints
-- Export API and data publisher
http.endpoints['/daf.js'] = http.page('daf.js', 'daf')
http.endpoints['/daf'] = {'application/json', api, publish}
endpoints['/daf.js'] = http.page('daf.js', 'daf')
endpoints['/daf'] = {'application/json', api, publish}
-- Export snippet
http.snippets['/daf'] = {'Application Firewall', [[
<script type="text/javascript" src="daf.js"></script>
......
-- SPDX-License-Identifier: GPL-3.0-or-later
-- do not attempt to contact outside world, operate only on cache
net.ipv4 = false
net.ipv6 = false
-- do not listen, test is driven by config code
env.KRESD_NO_LISTEN = true
modules.load('hints > iterate')
modules.load('daf')
hints['pass.'] = '127.0.0.1'
hints['deny.'] = '127.0.0.1'
hints['deny.'] = '127.0.0.1'
hints['drop.'] = '127.0.0.1'
hints['del.'] = '127.0.0.1'
hints['del2.'] = '127.0.0.1'
hints['toggle.'] = '127.0.0.1'
local function check_answer(desc, qname, qtype, expected_rcode)
qtype_str = kres.tostring.type[qtype]
callback = function(pkt)
same(pkt:rcode(), expected_rcode,
desc .. ': expecting answer for query ' .. qname .. ' ' .. qtype_str
.. ' with rcode ' .. kres.tostring.rcode[expected_rcode])
ok((pkt:ancount() > 0) == (pkt:rcode() == kres.rcode.NOERROR),
desc ..': checking number of answers for ' .. qname .. ' ' .. qtype_str)
end
resolve(qname, qtype, kres.class.IN, {}, callback)
end
local function test_sanity()
check_answer('daf sanity (no rules)', 'pass.', kres.type.A, kres.rcode.NOERROR)
check_answer('daf sanity (no rules)', 'deny.', kres.type.A, kres.rcode.NOERROR)
check_answer('daf sanity (no rules)', 'drop.', kres.type.A, kres.rcode.NOERROR)
check_answer('daf sanity (no rules)', 'del.', kres.type.A, kres.rcode.NOERROR)
check_answer('daf sanity (no rules)', 'del2.', kres.type.A, kres.rcode.NOERROR)
check_answer('daf sanity (no rules)', 'toggle.', kres.type.A, kres.rcode.NOERROR)
end
local function test_basic_actions()
daf.add('qname = pass. pass')
daf.add('qname = deny. deny')
daf.add('qname = drop. drop')
check_answer('daf pass action', 'pass.', kres.type.A, kres.rcode.NOERROR)
check_answer('daf deny action', 'deny.', kres.type.A, kres.rcode.NXDOMAIN)
check_answer('daf drop action', 'drop.', kres.type.A, kres.rcode.SERVFAIL)
end
local function test_del()
-- first matching rule is used
local first = daf.add('qname = del. deny')
local second = daf.add('qname = del2. deny')
check_answer('daf del - first rule active',
'del.', kres.type.A, kres.rcode.NXDOMAIN)
check_answer('daf del - second rule active',
'del2.', kres.type.A, kres.rcode.NXDOMAIN)
daf.del(first.rule.id)
check_answer('daf del - first rule deleted',
'del.', kres.type.A, kres.rcode.NOERROR)
daf.del(second.rule.id)
check_answer('daf del - second rule deleted',
'del2.', kres.type.A, kres.rcode.NOERROR)
end
local function test_toggle()
local toggle = daf.add('qname = toggle. deny')
check_answer('daf - toggle active',
'toggle.', kres.type.A, kres.rcode.NXDOMAIN)
daf.disable(toggle.rule.id)
check_answer('daf - toggle disabled',
'toggle.', kres.type.A, kres.rcode.NOERROR)
daf.enable(toggle.rule.id)
check_answer('daf - toggle enabled',
'toggle.', kres.type.A, kres.rcode.NXDOMAIN)
end
return {
test_sanity, -- must be first, expects no daf rules
test_basic_actions,
test_del,
test_toggle,
}
-- SPDX-License-Identifier: GPL-3.0-or-later
-- check prerequisites
local has_http = pcall(require, 'kres_modules.http') and pcall(require, 'http.request')
if not has_http then
pass('skipping daf module test because http its not installed')
done()
else
local request = require('http.request')
modules.load('http')
modules.load('daf')
local bound
for _ = 1,1000 do
bound, _err = pcall(net.listen, '127.0.0.1', math.random(40000, 49999), { kind = 'webmgmt'})
if bound then
break
end
end
assert(bound, 'unable to bind a port for HTTP module (1000 attempts)')
-- globals for this module
local _, host, port, baseuri
local function start_server()
local server_fd = next(http.servers)
assert(server_fd)
local server = http.servers[server_fd].server
ok(server ~= nil, 'creates server instance')
_, host, port = server:localname()
ok(host and port, 'binds to an interface')
baseuri = string.format('http://%s:%d/daf', host, port)
end
-- helper for returning useful values to test on
-- local function http_get(uri)
-- local headers, stream = assert(request.new_from_uri(uri):go())
-- local body = assert(stream:get_body_as_string())
-- return tonumber(headers:get(':status')), body, headers:get('content-type')
-- end
local function http_req(uri, method, reqbody)
local req = assert(request.new_from_uri(baseuri .. uri))
req.headers:upsert(':method', method)
req:set_body(reqbody)
local headers, stream = assert(req:go())
local ansbody = assert(stream:get_body_as_string())
return tonumber(headers:get(':status')), ansbody, headers:get('content-type')
end
local function http_get(uri)
return http_req(uri, 'GET')
end
-- compare two tables, expected value is specified as JSON
-- comparison relies on table_print which sorts table keys
local function compare_tables(expectedjson, gotjson, desc)
same(
table_print(fromjson(expectedjson)),
table_print(fromjson(gotjson)),
desc)
end
-- test whether http interface responds and binds
local function test_daf_api()
local code, body, mime
-- rule listing /daf
code, body, mime = http_get('/')
same(code, 200, 'rule listing return 200 OK')
same(body, '{}', 'daf rule list is empty after start')
same(mime, 'application/json', 'daf rule list has application/json content type')
-- get non-existing rule
code, body = http_req('/0', 'GET')
same(code, 404, 'non-existing rule retrieval returns 404')
same(body, '"No such rule"', 'explanatory message is present')
-- delete non-existing rule
code, body = http_req('/0', 'DELETE')
same(code, 404, 'non-existing rule deletion returns 404')
same(body, '"No such rule"', 'explanatory message is present')
-- bad PATCH
code = http_req('/0', 'PATCH')
same(code, 400, 'PATCH detects missing parameters')
-- bad POST
code = http_req('/', 'POST')
same(code, 500, 'POST without parameters is detected')
-- POST first new rule
code, body, mime = http_req('/', 'POST', 'src = 192.0.2.0 pass')
same(code, 200, 'first POST succeeds')
compare_tables(body,
'{"count":0,"active":true,"id":0,"info":"src = 192.0.2.0 pass"}',
'POST returns new rule in JSON')
same(mime, 'application/json', 'rule has application/json content type')
-- GET first rule
code, body, mime = http_req('/0', 'GET')
same(code, 200, 'GET for first rule succeeds')
compare_tables(body,
'{"count":0,"active":true,"id":0,"info":"src = 192.0.2.0 pass"}',
'POST returns new rule in JSON')
same(mime, 'application/json', 'rule has application/json content type')
-- POST second new rule
code, body, mime = http_req('/', 'POST', 'src = 192.0.2.1 pass')
same(code, 200, 'second POST succeeds')
compare_tables(body,
'{"count":0,"active":true,"id":1,"info":"src = 192.0.2.1 pass"}',
'POST returns new rule in JSON')
same(mime, 'application/json', 'rule has application/json content type')
-- GET second rule
code, body, mime = http_req('/1', 'GET')
same(code, 200, 'GET for second rule succeeds')
compare_tables(body,
'{"count":0,"active":true,"id":1,"info":"src = 192.0.2.1 pass"}',
'POST returns new rule in JSON')
same(mime, 'application/json', 'rule has application/json content type')
-- PATCH first rule
code, body, mime = http_req('/0/active/false', 'PATCH')
same(code, 200, 'PATCH for first rule succeeds')
same(body, 'true', 'PATCH returns success in body')
same(mime, 'application/json', 'PATCH return value has application/json content type')
-- GET modified first rule
code, body, mime = http_req('/0', 'GET')
same(code, 200, 'GET for first rule succeeds')
compare_tables(body,
'{"count":0,"active":false,"id":0,"info":"src = 192.0.2.0 pass"}',
'GET returns modified rule in JSON')
same(mime, 'application/json', 'rule has application/json content type')
-- GET both rules
code, body, mime = http_req('/', 'GET')
same(code, 200, 'GET for both rule succeeds')
compare_tables(body, [[
[
{"count":0,"active":false,"info":"src = 192.0.2.0 pass","id":0},
{"count":0,"active":true,"info":"src = 192.0.2.1 pass","id":1}]
]],
'GET returns both rules in JSON including modifications')
same(mime, 'application/json', 'rule list has application/json content type')
-- PATCH first rule back to original state
code, body, mime = http_req('/0/active/true', 'PATCH')
same(code, 200, 'PATCH for first rule succeeds')
same(body, 'true', 'PATCH returns success in body')
same(mime, 'application/json', 'PATCH return value has application/json content type')
-- GET modified (reversed) first rule
code, body, mime = http_req('/0', 'GET')
same(code, 200, 'GET for first rule succeeds')
compare_tables(body,
'{"count":0,"active":true,"id":0,"info":"src = 192.0.2.0 pass"}',
'GET returns modified rule in JSON')
same(mime, 'application/json', 'rule has application/json content type')
-- DELETE first rule
code, body, mime = http_req('/0', 'DELETE')
same(code, 200, 'DELETE for first rule succeeds')
same(body, 'true', 'DELETE returns success in body')
same(mime, 'application/json', 'DELETE return value has application/json content type')
-- GET deleted (first) rule
code, body = http_req('/0', 'GET')
same(code, 404, 'GET for deleted fails with 404')
same(body, '"No such rule"', 'failed GET contains explanatory message')
-- GET second rule
code, body, mime = http_req('/1', 'GET')
same(code, 200, 'GET for second rule still succeeds')
compare_tables(body,
'{"count":0,"active":true,"id":1,"info":"src = 192.0.2.1 pass"}',
'POST returns new rule in JSON')
same(mime, 'application/json', 'rule has application/json content type')
-- GET list of all rules
code, body, mime = http_req('/', 'GET')
same(code, 200, 'GET returns list with the remaining rule')
compare_tables(body,
'[{"count":0,"active":true,"id":1,"info":"src = 192.0.2.1 pass"}]',
'rule list contains only the remaining rule in JSON')
same(mime, 'application/json', 'rule has application/json content type')
-- try to DELETE first rule again
code, body = http_req('/0', 'DELETE')
same(code, 404, 'DELETE for already deleted rule fails with 404')
same(body, '"No such rule"', 'DELETE explains failure')
-- DELETE second rule
code, body, mime = http_req('/1', 'DELETE')
same(code, 200, 'DELETE for second rule succeeds')
same(body, 'true', 'DELETE returns success in body')
same(mime, 'application/json', 'DELETE return value has application/json content type')
-- GET (supposedly empty) list of all rules
code, body, mime = http_req('/', 'GET')
same(code, 200, 'GET returns list with the remaining rule')
compare_tables(body, '[]', 'rule list is now empty JSON list')
same(mime, 'application/json', 'rule has application/json content type')
end
-- plan tests
local tests = {
start_server,
test_daf_api,
}
return tests
end
# LUA module: daf
# SPDX-License-Identifier: GPL-3.0-or-later
config_tests += [
['daf', files('daf.test.lua')],
['daf_http', files('daf_http.test.lua')],
]
integr_tests += [
['daf', join_paths(meson.current_source_dir(), 'test.integr')],
]
lua_mod_src += [
files('daf.lua'),
]
......
......@@ -17,3 +17,7 @@ hints_mod = shared_module(
install: true,
install_dir: modules_dir,
)
config_tests += [
['hints', files('tests/hints.test.lua'), ['skip_asan']],
]
......@@ -222,9 +222,14 @@ local function route(endpoints)
local h = assert(stream:get_headers())
local m = h:get(':method')
local path = h:get(':path')
-- Upgrade connection to WebSocket
local ws = http_websocket.new_from_stream(stream, h)
if ws then
if verbose() then
log('[http] %s %s HTTP/%d web socket open',
m, path, connection.version)
end
assert(ws:accept { protocols = {'json'} })
-- Continue streaming results to client
local ep = endpoints[path]
......@@ -233,23 +238,34 @@ local function route(endpoints)
cb(h, ws)
end
ws:close()
if verbose() then
log('[http] %s %s HTTP/%d web socket closed',
m, path, connection.version)
end
return
else
local ok, err, reason = http_util.yieldable_pcall(serve, endpoints, h, stream)
if not ok or err then
if err ~= '404' and verbose() then
log('[http] %s %s: %s (%s)', m, path, err or '500', reason)
err = err or '500'
if verbose() then
log('[http] %s %s HTTTP/%d %s %s',
m, path, connection.version, err, reason or '')
end
-- Method is not supported
local hsend = http_headers.new()
hsend:append(':status', err or '500')
hsend:append(':status', err)
if reason then
assert(stream:write_headers(hsend, false))
assert(stream:write_chunk(reason, true))
else
assert(stream:write_headers(hsend, true))
end
else if verbose() then
log('[http] %s %s HTTP/%d 200',
m, path, connection.version)
end
end
end
end
end
......
modules/http/static/favicon.ico

1.46 KiB | W: 32px | H: 32px

modules/http/static/favicon.ico

1.51 KiB | W: 32px | H: 32px

modules/http/static/favicon.ico
modules/http/static/favicon.ico
modules/http/static/favicon.ico
modules/http/static/favicon.ico
  • 2-up
  • Swipe
  • Onion skin
......@@ -20,18 +20,15 @@ lua_mod_src = [ # add lua modules without separate meson.build
files('workarounds/workarounds.lua'),
]
# When adding tests, prefer to use module's meson.build (if it exists).
config_tests += [
['predict', files('predict/predict.test.lua')],
['hints', files('hints/tests/hints.test.lua'), ['skip_asan']],
['nsid', files('nsid/nsid.test.lua')],
['dns64', files('dns64/dns64.test.lua')],
['ta_update', files('ta_update/ta_update.test.lua'), ['snowflake']],
['prefill', files('prefill/prefill.test/prefill.test.lua')],
]
integr_tests += [
['bogus_log', join_paths(meson.current_source_dir(), 'bogus_log', 'test.integr')],
['daf', join_paths(meson.current_source_dir(), 'daf', 'test.integr')],
['rebinding', join_paths(meson.current_source_dir(), 'rebinding', 'test.integr')],
['serve_stale', join_paths(meson.current_source_dir(), 'serve_stale', 'test.integr')],
# NOTE: ta_update may pass in cases when it should fail due to race conditions
......
......@@ -17,3 +17,7 @@ nsid_mod = shared_module(
install: true,
install_dir: modules_dir,
)
config_tests += [
['nsid', files('nsid.test.lua')],
]
......@@ -29,7 +29,7 @@ foreach config_test : config_tests
run_configtest,
args: [
'-c', files('test.cfg'),
'-f', '1',
'-n'
],
env: conftest_env,
suite: [
......
deckard @ 6ed64e90
Subproject commit 62a1736bebb41232055950ee27c7f27efe5708e7
Subproject commit 6ed64e906a4766f3c553bdb04d713c5048038962