Commit 3edd1496 authored by Daniel Salzman's avatar Daniel Salzman

conf: allow multivalued acl.address and acl.key, separate deny

parent 59d0264e
......@@ -82,21 +82,18 @@ Access control list (ACL)
=========================
ACL list specifies which remotes are allowed to send the server a specific
query or do a specific action. A remote can be a single IP address or a
network subnet. Also a TSIG key can be specified::
request. A remote can be a single IP address or a network subnet. Also a TSIG
key can be specified::
acl:
- id: single_rule
address: 192.168.1.1 # Single IP address
action: [notify, update] # Allow zone notifications and updates zone
- id: address_rule
address: [2001:db8::1, 192.168.2.0/24] # Allowed IP address list
action: [transfer, update] # Allow zone transfers and updates
- id: subnet_rule
address: 192.168.2.0/24 # Network subnet
action: transfer # Allow zone transfers
- id: deny_rule
address: 192.168.2.100 # Negative match
action: deny # The remote query is denied
- id: deny_rule # Negative match rule
address: 192.168.2.100
action: transfer
deny: on # The request is denied
- id: key_rule
key: key1 # Access based just on TSIG key
......@@ -106,7 +103,7 @@ These rules can then be referenced from a zone :ref:`template_acl`::
zone:
- domain: example.com
acl: [single_rule, deny_rule, subnet_rule, key_rule]
acl: [address_rule, deny_rule, key_rule]
Slave zone
==========
......
......@@ -297,7 +297,7 @@ Shared key secret.
Default: empty
.SH ACL SECTION
.sp
Access control list rules definition.
Access control list rule definition.
.INDENT 0.0
.INDENT 3.5
.sp
......@@ -305,9 +305,10 @@ Access control list rules definition.
.ft C
acl:
\- id: STR
address: ADDR[/INT]
key: key_id
action: deny | transfer | notify | update ...
address: ADDR[/INT] ...
key: key_id ...
action: transfer | notify | update ...
deny: BOOL
.ft P
.fi
.UNINDENT
......@@ -317,13 +318,14 @@ acl:
An ACL rule identifier.
.SS address
.sp
A single IP address or network subnet with the given prefix the query
must match.
An ordered list of IP addresses or network subnets. The query must match
one of them. Empty value means that address match is not required.
.sp
Default: empty
.SS key
.sp
A \fI\%reference\fP to the TSIG key the query must match.
An ordered list of \fI\%reference\fPs to TSIG keys. The query must
match one of them. Empty value means that TSIG key is not required.
.sp
Default: empty
.SS action
......@@ -333,8 +335,6 @@ An ordered list of allowed actions.
Possible values:
.INDENT 0.0
.IP \(bu 2
\fBdeny\fP \- Block the matching query
.IP \(bu 2
\fBtransfer\fP \- Allow zone transfer
.IP \(bu 2
\fBnotify\fP \- Allow incoming notify
......@@ -342,7 +342,13 @@ Possible values:
\fBupdate\fP \- Allow zone updates
.UNINDENT
.sp
Default: deny
Default: empty
.SS deny
.sp
Deny if \fI\%address\fP, \fI\%key\fP and
\fI\%action\fP match.
.sp
Default: off
.SH CONTROL SECTION
.sp
Configuration of the server remote control.
......
......@@ -351,15 +351,16 @@ Default: empty
ACL section
===========
Access control list rules definition.
Access control list rule definition.
::
acl:
- id: STR
address: ADDR[/INT]
key: key_id
action: deny | transfer | notify | update ...
address: ADDR[/INT] ...
key: key_id ...
action: transfer | notify | update ...
deny: BOOL
.. _acl_id:
......@@ -373,8 +374,8 @@ An ACL rule identifier.
address
-------
A single IP address or network subnet with the given prefix the query
must match.
An ordered list of IP addresses or network subnets. The query must match
one of them. Empty value means that address match is not required.
Default: empty
......@@ -383,7 +384,8 @@ Default: empty
key
---
A :ref:`reference<key_id>` to the TSIG key the query must match.
An ordered list of :ref:`reference<key_id>`\ s to TSIG keys. The query must
match one of them. Empty value means that TSIG key is not required.
Default: empty
......@@ -396,12 +398,21 @@ An ordered list of allowed actions.
Possible values:
- ``deny`` - Block the matching query
- ``transfer`` - Allow zone transfer
- ``notify`` - Allow incoming notify
- ``update`` - Allow zone updates
Default: deny
Default: empty
.. _acl_deny:
deny
----
Deny if :ref:`address<acl_address>`, :ref:`key<acl_key>` and
:ref:`action<acl_action>` match.
Default: off
.. _Control section:
......
......@@ -48,10 +48,9 @@ static const lookup_table_t key_algs[] = {
};
static const lookup_table_t acl_actions[] = {
{ ACL_ACTION_DENY, "deny" },
{ ACL_ACTION_XFER, "transfer" },
{ ACL_ACTION_NOTF, "notify" },
{ ACL_ACTION_DDNS, "update" },
{ ACL_ACTION_NOTIFY, "notify" },
{ ACL_ACTION_TRANSFER, "transfer" },
{ ACL_ACTION_UPDATE, "update" },
{ 0, NULL }
};
......@@ -107,11 +106,12 @@ static const yp_item_t desc_key[] = {
};
static const yp_item_t desc_acl[] = {
{ C_ID, YP_TSTR, YP_VNONE },
{ C_ADDR, YP_TNET, YP_VNONE },
{ C_KEY, YP_TREF, YP_VREF = { C_KEY }, YP_FNONE, { check_ref } },
{ C_ACTION, YP_TOPT, YP_VOPT = { acl_actions, ACL_ACTION_DENY }, YP_FMULTI },
{ C_COMMENT, YP_TSTR, YP_VNONE },
{ C_ID, YP_TSTR, YP_VNONE },
{ C_ADDR, YP_TNET, YP_VNONE, YP_FMULTI },
{ C_KEY, YP_TREF, YP_VREF = { C_KEY }, YP_FMULTI, { check_ref } },
{ C_ACTION, YP_TOPT, YP_VOPT = { acl_actions, ACL_ACTION_NONE }, YP_FMULTI },
{ C_DENY, YP_TBOOL, YP_VNONE },
{ C_COMMENT, YP_TSTR, YP_VNONE },
{ NULL }
};
......
......@@ -36,6 +36,7 @@
#define C_BG_WORKERS "\x12""background-workers"
#define C_COMMENT "\x07""comment"
#define C_CTL "\x07""control"
#define C_DENY "\x04""deny"
#define C_DISABLE_ANY "\x0B""disable-any"
#define C_DOMAIN "\x06""domain"
#define C_DNSSEC_ENABLE "\x0D""dnssec-enable"
......
......@@ -110,7 +110,7 @@ static int axfr_query_check(struct query_data *qdata)
{
/* Check valid zone, transaction security and contents. */
NS_NEED_ZONE(qdata, KNOT_RCODE_NOTAUTH);
NS_NEED_AUTH(qdata, qdata->zone->name, ACL_ACTION_XFER);
NS_NEED_AUTH(qdata, qdata->zone->name, ACL_ACTION_TRANSFER);
/* Check expiration. */
NS_NEED_ZONE_CONTENTS(qdata, KNOT_RCODE_SERVFAIL);
......
......@@ -832,7 +832,7 @@ int internet_query(knot_pkt_t *response, struct query_data *qdata)
/* No applicable ACL, refuse transaction security. */
if (knot_pkt_has_tsig(qdata->query)) {
/* We have been challenged... */
NS_NEED_AUTH(qdata, qdata->zone->name, ACL_ACTION_XFER);
NS_NEED_AUTH(qdata, qdata->zone->name, ACL_ACTION_TRANSFER);
/* Reserve space for TSIG. */
knot_pkt_reserve(response, knot_tsig_wire_maxsize(&qdata->sign.tsig_key));
......
......@@ -212,7 +212,7 @@ static int ixfr_query_check(struct query_data *qdata)
NS_NEED_QNAME(qdata, their_soa->owner, KNOT_RCODE_FORMERR);
/* Check transcation security and zone contents. */
NS_NEED_AUTH(qdata, qdata->zone->name, ACL_ACTION_XFER);
NS_NEED_AUTH(qdata, qdata->zone->name, ACL_ACTION_TRANSFER);
NS_NEED_ZONE_CONTENTS(qdata, KNOT_RCODE_SERVFAIL); /* Check expiration. */
return KNOT_STATE_DONE;
......
......@@ -54,7 +54,7 @@ static int notify_check_query(struct query_data *qdata)
/* Check valid zone, transaction security. */
zone_t *zone = (zone_t *)qdata->zone;
NS_NEED_ZONE(qdata, KNOT_RCODE_NOTAUTH);
NS_NEED_AUTH(qdata, zone->name, ACL_ACTION_NOTF);
NS_NEED_AUTH(qdata, zone->name, ACL_ACTION_NOTIFY);
return KNOT_STATE_DONE;
}
......
......@@ -419,7 +419,7 @@ static void forward_requests(zone_t *zone, list_t *requests)
static bool update_tsig_check(struct query_data *qdata, struct knot_request *req)
{
// Check that ACL is still valid.
if (!process_query_acl_check(qdata->zone->name, ACL_ACTION_DDNS, qdata)) {
if (!process_query_acl_check(qdata->zone->name, ACL_ACTION_UPDATE, qdata)) {
UPDATE_LOG(LOG_WARNING, "ACL check failed");
knot_wire_set_rcode(req->resp->wire, qdata->rcode);
return false;
......@@ -529,7 +529,7 @@ int update_query_process(knot_pkt_t *pkt, struct query_data *qdata)
/* Need valid transaction security. */
zone_t *zone = (zone_t *)qdata->zone;
NS_NEED_AUTH(qdata, zone->name, ACL_ACTION_DDNS);
NS_NEED_AUTH(qdata, zone->name, ACL_ACTION_UPDATE);
/* Check expiration. */
NS_NEED_ZONE_CONTENTS(qdata, KNOT_RCODE_SERVFAIL);
......
......@@ -96,49 +96,39 @@ bool acl_allowed(conf_val_t *acl, acl_action_t action,
}
while (acl->code == KNOT_EOK) {
/* Check if the action is allowed. */
bool match = false, deny = false;
conf_val_t action_val = conf_id_get(conf(), C_ACL, C_ACTION, acl);
while (action_val.code == KNOT_EOK) {
unsigned act = conf_opt(&action_val);
if (act & action) {
match = true;
}
if (act == ACL_ACTION_DENY) {
deny = true;
}
conf_val_next(&action_val);
}
if (!match) {
conf_val_next(acl);
continue;
}
conf_val_t val;
/* Check if the address prefix matches. */
conf_val_t addr_val = conf_id_get(conf(), C_ACL, C_ADDR, acl);
if (addr_val.code == KNOT_EOK) {
/* Check if the address matches the current acl address list. */
val = conf_id_get(conf(), C_ACL, C_ADDR, acl);
while (val.code == KNOT_EOK) {
unsigned prefix;
struct sockaddr_storage ss;
ss = conf_net(&addr_val, &prefix);
ss = conf_net(&val, &prefix);
if (!netblock_match(addr, &ss, prefix)) {
conf_val_next(acl);
conf_val_next(&val);
continue;
}
break;
}
/* Check for address match or empty list. */
if (val.code != KNOT_EOK && val.code != KNOT_ENOENT) {
goto next_acl;
}
/* Check if the key matches. */
/* Check if the key matches the current acl key list. */
conf_val_t key_val = conf_id_get(conf(), C_ACL, C_KEY, acl);
if (key_val.code == KNOT_EOK) {
while (key_val.code == KNOT_EOK) {
/* No key provided, but required. */
if (tsig->name == NULL) {
conf_val_next(acl);
conf_val_next(&key_val);
continue;
}
/* Compare key names. */
const knot_dname_t *key_name = conf_dname(&key_val);
if (knot_dname_cmp(key_name, tsig->name) != 0) {
conf_val_next(acl);
conf_val_next(&key_val);
continue;
}
......@@ -146,30 +136,50 @@ bool acl_allowed(conf_val_t *acl, acl_action_t action,
conf_val_t alg_val = conf_id_get(conf(), C_KEY, C_ALG,
&key_val);
if (conf_opt(&alg_val) != tsig->algorithm) {
conf_val_next(acl);
conf_val_next(&key_val);
continue;
}
/* No key required, but provided. */
} else if (tsig->name != NULL) {
conf_val_next(acl);
continue;
break;
}
/* Check for key match or empty list without key provided. */
if (key_val.code != KNOT_EOK &&
!(key_val.code == KNOT_ENOENT && tsig->name == NULL)) {
goto next_acl;
}
if (deny) {
conf_val_next(acl);
continue;
/* Check if the action is allowed. */
val = conf_id_get(conf(), C_ACL, C_ACTION, acl);
while (val.code == KNOT_EOK) {
if (conf_opt(&val) != action) {
conf_val_next(&val);
continue;
}
break;
}
/* Check for action match. */
if (val.code != KNOT_EOK) {
goto next_acl;
}
/* Check if denied. */
val = conf_id_get(conf(), C_ACL, C_DENY, acl);
if (conf_bool(&val)) {
return false;
}
/* Fill the output with tsig secret. */
/* Fill the output with tsig secret if provided. */
if (tsig->name != NULL) {
conf_val_t secret_val = conf_id_get(conf(), C_KEY,
C_SECRET, &key_val);
conf_data(&secret_val);
tsig->secret.data = (uint8_t *)secret_val.data;
tsig->secret.size = secret_val.len;
val = conf_id_get(conf(), C_KEY, C_SECRET, &key_val);
conf_data(&val);
tsig->secret.data = (uint8_t *)val.data;
tsig->secret.size = val.len;
}
return true;
next_acl:
conf_val_next(acl);
}
return false;
......
......@@ -32,10 +32,10 @@
/*! \brief ACL actions. */
typedef enum {
ACL_ACTION_DENY = 0,
ACL_ACTION_XFER = 1,
ACL_ACTION_NOTF = 2,
ACL_ACTION_DDNS = 3
ACL_ACTION_NONE = 0,
ACL_ACTION_NOTIFY = 1,
ACL_ACTION_TRANSFER = 2,
ACL_ACTION_UPDATE = 3
} acl_action_t;
/*!
......
......@@ -64,6 +64,7 @@ check-local: $(check_PROGRAMS)
-L $(top_builddir)/tests/runtests.log \
$(check_PROGRAMS)
acl_SOURCES = acl.c test_conf.h
process_query_SOURCES = process_query.c fake_server.h test_conf.h
process_answer_SOURCES = process_answer.c fake_server.h test_conf.h
CLEANFILES = runtests.log
......@@ -18,10 +18,10 @@
#include <sys/socket.h>
#include <tap/basic.h>
#include "test_conf.h"
#include "libknot/libknot.h"
#include "libknot/internal/sockaddr.h"
#include "knot/updates/acl.h"
#include "knot/conf/conf.h"
static void check_sockaddr_set(struct sockaddr_storage *ss, int family,
const char *straddr, int port)
......@@ -30,10 +30,8 @@ static void check_sockaddr_set(struct sockaddr_storage *ss, int family,
ok(ret == KNOT_EOK, "set address '%s'", straddr);
}
int main(int argc, char *argv[])
static void test_netblock_match(void)
{
plan_lazy();
int ret;
struct sockaddr_storage t = { 0 };
......@@ -102,6 +100,142 @@ int main(int argc, char *argv[])
ok(ret == true, "match: ipv6 - last byte, precise prefix");
ret = netblock_match(&t, &ref6, 127);
ok(ret == false, "match: ipv6 - last byte, not match");
}
#define ZONE "example.zone"
#define KEY1 "key1_md5"
#define KEY2 "key2_md5"
#define KEY3 "key3_sha256"
static void test_acl_allowed(void)
{
int ret;
conf_val_t acl;
struct sockaddr_storage addr = { 0 };
knot_dname_t *zone_name = knot_dname_from_str_alloc(ZONE);
ok(zone_name != NULL, "create zone dname");
knot_dname_t *key1_name = knot_dname_from_str_alloc(KEY1);
ok(key1_name != NULL, "create "KEY1);
knot_dname_t *key2_name = knot_dname_from_str_alloc(KEY2);
ok(key2_name != NULL, "create "KEY2);
knot_dname_t *key3_name = knot_dname_from_str_alloc(KEY3);
ok(key3_name != NULL, "create "KEY3);
knot_tsig_key_t key0 = { NULL };
knot_tsig_key_t key1 = { key1_name, DNSSEC_TSIG_HMAC_MD5 };
knot_tsig_key_t key2 = { key2_name, DNSSEC_TSIG_HMAC_MD5 };
knot_tsig_key_t key3 = { key3_name, DNSSEC_TSIG_HMAC_SHA256 };
const char *conf_str =
"key:\n"
" - id: "KEY1"\n"
" algorithm: hmac-md5\n"
" secret: Zm9v\n"
" - id: "KEY2"\n"
" algorithm: hmac-md5\n"
" secret: Zm9v\n"
" - id: "KEY3"\n"
" algorithm: hmac-sha256\n"
" secret: Zm8=\n"
"\n"
"acl:\n"
" - id: acl_key_addr\n"
" address: [ 2001::1 ]\n"
" key: [ key1_md5 ]\n"
" action: [ transfer ]\n"
" - id: acl_deny\n"
" address: [ 240.0.0.2 ]\n"
" action: [ notify ]\n"
" deny: on\n"
" - id: acl_multi_addr\n"
" address: [ 192.168.1.1, 240.0.0.0/24 ]\n"
" action: [ notify, update ]\n"
" - id: acl_multi_key\n"
" key: [ key2_md5, key3_sha256 ]\n"
" action: [ notify, update ]\n"
"\n"
"zone:\n"
" - domain: "ZONE"\n"
" acl: [ acl_key_addr, acl_deny, acl_multi_addr, acl_multi_key]";
ret = test_conf(conf_str, NULL);
ok(ret == KNOT_EOK, "Prepare configuration");
acl = conf_zone_get(conf(), C_ACL, zone_name);
ok(acl.code == KNOT_EOK, "Get zone ACL");
check_sockaddr_set(&addr, AF_INET6, "2001::1", 0);
ret = acl_allowed(&acl, ACL_ACTION_TRANSFER, &addr, &key1);
ok(ret == true, "Address, key, action match");
acl = conf_zone_get(conf(), C_ACL, zone_name);
ok(acl.code == KNOT_EOK, "Get zone ACL");
check_sockaddr_set(&addr, AF_INET6, "2001::2", 0);
ret = acl_allowed(&acl, ACL_ACTION_TRANSFER, &addr, &key1);
ok(ret == false, "Address not match, key, action match");
acl = conf_zone_get(conf(), C_ACL, zone_name);
ok(acl.code == KNOT_EOK, "Get zone ACL");
check_sockaddr_set(&addr, AF_INET6, "2001::1", 0);
ret = acl_allowed(&acl, ACL_ACTION_TRANSFER, &addr, &key0);
ok(ret == false, "Address match, no key, action match");
acl = conf_zone_get(conf(), C_ACL, zone_name);
ok(acl.code == KNOT_EOK, "Get zone ACL");
check_sockaddr_set(&addr, AF_INET6, "2001::1", 0);
ret = acl_allowed(&acl, ACL_ACTION_TRANSFER, &addr, &key2);
ok(ret == false, "Address match, key not match, action match");
acl = conf_zone_get(conf(), C_ACL, zone_name);
ok(acl.code == KNOT_EOK, "Get zone ACL");
check_sockaddr_set(&addr, AF_INET6, "2001::1", 0);
ret = acl_allowed(&acl, ACL_ACTION_NOTIFY, &addr, &key1);
ok(ret == false, "Address, key match, action not match");
acl = conf_zone_get(conf(), C_ACL, zone_name);
ok(acl.code == KNOT_EOK, "Get zone ACL");
check_sockaddr_set(&addr, AF_INET, "240.0.0.1", 0);
ret = acl_allowed(&acl, ACL_ACTION_NOTIFY, &addr, &key0);
ok(ret == true, "Second address match, no key, action match");
acl = conf_zone_get(conf(), C_ACL, zone_name);
ok(acl.code == KNOT_EOK, "Get zone ACL");
check_sockaddr_set(&addr, AF_INET, "240.0.0.1", 0);
ret = acl_allowed(&acl, ACL_ACTION_NOTIFY, &addr, &key1);
ok(ret == false, "Second address match, extra key, action match");
acl = conf_zone_get(conf(), C_ACL, zone_name);
ok(acl.code == KNOT_EOK, "Get zone ACL");
check_sockaddr_set(&addr, AF_INET, "240.0.0.2", 0);
ret = acl_allowed(&acl, ACL_ACTION_NOTIFY, &addr, &key0);
ok(ret == false, "Denied address match, no key, action match");
acl = conf_zone_get(conf(), C_ACL, zone_name);
ok(acl.code == KNOT_EOK, "Get zone ACL");
check_sockaddr_set(&addr, AF_INET, "240.0.0.2", 0);
ret = acl_allowed(&acl, ACL_ACTION_UPDATE, &addr, &key0);
ok(ret == true, "Denied address match, no key, action not match");
acl = conf_zone_get(conf(), C_ACL, zone_name);
ok(acl.code == KNOT_EOK, "Get zone ACL");
check_sockaddr_set(&addr, AF_INET, "1.1.1.1", 0);
ret = acl_allowed(&acl, ACL_ACTION_UPDATE, &addr, &key3);
ok(ret == true, "Arbitrary address, second key, action match");
conf_free(conf(), false);
knot_dname_free(&zone_name, NULL);
knot_dname_free(&key1_name, NULL);
knot_dname_free(&key2_name, NULL);
knot_dname_free(&key3_name, NULL);
}
int main(int argc, char *argv[])
{
plan_lazy();
test_netblock_match();
test_acl_allowed();
return 0;
}
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