[v9_10] further restrict update-policy local

4762.	[func]		"update-policy local" is now restricted to updates
                from local addresses. (Previously, other addresses
                were allowed so long as updates were signed by the
                local session key.) [RT #45492]
This commit is contained in:
Evan Hunt 2017-10-06 15:36:18 -07:00
parent 3402ce550a
commit dddf97d534
18 changed files with 280 additions and 73 deletions

View file

@ -1,3 +1,8 @@
4762. [func] "update-policy local" is now restricted to updates
from local addresses. (Previously, other addresses
were allowed so long as updates were signed by the
local session key.) [RT #45492]
4761. [protocol] Add support for DOA. [RT #45612]
4759. [func] Add logging channel "trust-anchor-telementry" to

View file

@ -171,6 +171,7 @@ EXTERN isc_boolean_t ns_g_notcp INIT(ISC_FALSE);
EXTERN isc_boolean_t ns_g_disable6 INIT(ISC_FALSE);
EXTERN isc_boolean_t ns_g_disable4 INIT(ISC_FALSE);
EXTERN unsigned int ns_g_tat_interval INIT(24*3600);
EXTERN isc_boolean_t ns_g_fixedlocal INIT(ISC_FALSE);
#ifdef HAVE_GEOIP

View file

@ -15,8 +15,6 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: interfacemgr.c,v 1.101 2011/11/09 18:44:03 each Exp $ */
/*! \file */
#include <config.h>
@ -949,11 +947,22 @@ do_scan(ns_interfacemgr_t *mgr, ns_listenlist_t *ext_listen,
}
if (adjusting == ISC_FALSE) {
/*
* If running with -T fixedlocal, then we only
* want 127.0.0.1 and ::1 in the localhost ACL.
*/
if (ns_g_fixedlocal &&
!isc_netaddr_isloopback(&interface.address))
{
goto listenon;
}
result = setup_locals(mgr, &interface);
if (result != ISC_R_SUCCESS)
goto ignore_interface;
}
listenon:
ll = (family == AF_INET) ? mgr->listenon4 : mgr->listenon6;
dolistenon = ISC_TRUE;
for (le = ISC_LIST_HEAD(ll->elts);

View file

@ -611,17 +611,25 @@ parse_command_line(int argc, char *argv[]) {
dns_zone_mkey_month = atoi(p);
if (dns_zone_mkey_month < dns_zone_mkey_day)
ns_main_earlyfatal("bad mkeytimer");
} else if (!strcmp(isc_commandline_argument, "notcp"))
} else if (!strcmp(isc_commandline_argument, "notcp")) {
ns_g_notcp = ISC_TRUE;
else if (!strncmp(isc_commandline_argument, "tat=", 4))
} else if (!strncmp(isc_commandline_argument,
"tat=", 4))
{
ns_g_tat_interval =
atoi(isc_commandline_argument + 4);
else if (!strcmp(isc_commandline_argument,
"keepstderr"))
} else if (!strcmp(isc_commandline_argument,
"keepstderr"))
{
ns_g_keepstderr = ISC_TRUE;
else
} else if (!strcmp(isc_commandline_argument,
"fixedlocal"))
{
ns_g_fixedlocal = ISC_TRUE;
} else {
fprintf(stderr, "unknown -T flag '%s\n",
isc_commandline_argument);
}
break;
case 'U':
ns_g_udpdisp = parse_int(isc_commandline_argument,

View file

@ -813,8 +813,11 @@ typedef struct {
/* The signature's name if the request was signed. */
dns_name_t *signer;
/* The address of the client if the request was received via TCP. */
isc_netaddr_t *tcpaddr;
/* The address of the client. */
isc_netaddr_t *addr;
/* Whether the request was sent via TCP. */
isc_boolean_t tcp;
/* The ssu table to check against. */
dns_ssutable_t *table;
@ -835,16 +838,17 @@ ssu_checkrule(void *data, dns_rdataset_t *rrset) {
if (rrset->type == dns_rdatatype_rrsig ||
rrset->type == dns_rdatatype_nsec)
return (ISC_R_SUCCESS);
result = dns_ssutable_checkrules(ssuinfo->table, ssuinfo->signer,
ssuinfo->name, ssuinfo->tcpaddr,
rrset->type, ssuinfo->key);
result = dns_ssutable_checkrules2(ssuinfo->table, ssuinfo->signer,
ssuinfo->name, ssuinfo->addr,
ssuinfo->tcp, &ns_g_server->aclenv,
rrset->type, ssuinfo->key);
return (result == ISC_TRUE ? ISC_R_SUCCESS : ISC_R_FAILURE);
}
static isc_boolean_t
ssu_checkall(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
dns_ssutable_t *ssutable, dns_name_t *signer,
isc_netaddr_t *tcpaddr, dst_key_t *key)
isc_netaddr_t *addr, isc_boolean_t tcp, dst_key_t *key)
{
isc_result_t result;
ssu_check_t ssuinfo;
@ -852,7 +856,8 @@ ssu_checkall(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
ssuinfo.name = name;
ssuinfo.table = ssutable;
ssuinfo.signer = signer;
ssuinfo.tcpaddr = tcpaddr;
ssuinfo.addr = addr;
ssuinfo.tcp = tcp;
ssuinfo.key = key;
result = foreach_rrset(db, ver, name, ssu_checkrule, &ssuinfo);
return (ISC_TF(result == ISC_R_SUCCESS));
@ -2686,38 +2691,33 @@ update_action(isc_task_t *task, isc_event_t *event) {
}
if (ssutable != NULL) {
isc_netaddr_t *tcpaddr, netaddr;
isc_netaddr_t netaddr;
dst_key_t *tsigkey = NULL;
/*
* If this is a TCP connection then pass the
* address of the client through for tcp-self
* and 6to4-self otherwise pass NULL. This
* provides weak address based authentication.
*/
if (TCPCLIENT(client)) {
isc_netaddr_fromsockaddr(&netaddr,
&client->peeraddr);
tcpaddr = &netaddr;
} else
tcpaddr = NULL;
isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr);
if (client->message->tsigkey != NULL)
tsigkey = client->message->tsigkey->key;
if (rdata.type != dns_rdatatype_any) {
if (!dns_ssutable_checkrules(ssutable,
client->signer,
name, tcpaddr,
rdata.type,
tsigkey))
if (!dns_ssutable_checkrules2
(ssutable, client->signer, name, &netaddr,
ISC_TF(TCPCLIENT(client)),
&ns_g_server->aclenv,
rdata.type, tsigkey))
{
FAILC(DNS_R_REFUSED,
"rejected by secure update");
}
} else {
if (!ssu_checkall(db, ver, name, ssutable,
client->signer, tcpaddr,
client->signer,
&netaddr,
ISC_TF(TCPCLIENT(client)),
tsigkey))
{
FAILC(DNS_R_REFUSED,
"rejected by secure update");
}
}
}
}

View file

@ -378,7 +378,7 @@ configure_zone_ssutable(const cfg_obj_t *zconfig, dns_zone_t *zone,
result = dns_ssutable_addrule(table, ISC_TRUE,
ns_g_server->session_keyname,
DNS_SSUMATCHTYPE_SUBDOMAIN,
DNS_SSUMATCHTYPE_LOCAL,
dns_zone_getorigin(zone),
1, &any);

View file

@ -20,13 +20,12 @@
#
rm -f */named.memstats
rm -f */named.run
rm -f */named.run */ans.run
rm -f Kxxx.*
rm -f dig.out.*
rm -f jp.out.ns3.*
rm -f ns*/named.lock
rm -f ns1/*.jnl ns2/*.jnl ns3/*.jnl
rm -f ns1/example.db ns1/unixtime.db ns1/update.db ns1/other.db ns1/keytests.db
rm -f */*.jnl
rm -f ns1/example.db ns1/unixtime.db ns1/yyyymmddvv.db ns1/update.db ns1/other.db ns1/keytests.db
rm -f ns1/many.test.db
rm -f ns1/md5.key ns1/sha1.key ns1/sha224.key ns1/sha256.key ns1/sha384.key
@ -42,6 +41,7 @@ rm -f ns3/example.db
rm -f ns3/many.test.bk
rm -f ns3/nsec3param.test.db
rm -f ns3/too-big.test.db
rm -f ns5/local.db
rm -f nsupdate.out*
rm -f typelist.out.*
rm -f ns1/sample.db

View file

@ -0,0 +1,20 @@
; Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, You can obtain one at http://mozilla.org/MPL/2.0/.
$ORIGIN .
$TTL 300 ; 5 minutes
local.nil IN SOA ns5.local.nil. hostmaster.local.nil. (
1 ; serial
2000 ; refresh (2000 seconds)
2000 ; retry (2000 seconds)
1814400 ; expire (3 weeks)
3600 ; minimum (1 hour)
)
local.nil. NS ns5.local.nil.
ns5.local.nil. A 10.53.0.5
$ORIGIN local.nil.
a A 10.10.10.10

View file

@ -0,0 +1 @@
-m record,size,mctx -T clienttest -c named.conf -d 99 -g -U 4 -T fixedlocal

View file

@ -0,0 +1,37 @@
/*
* Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
controls { /* empty */ };
options {
query-source address 10.53.0.5;
notify-source 10.53.0.5;
transfer-source 10.53.0.5;
port 5300;
pid-file "named.pid";
session-keyfile "session.key";
listen-on { 10.53.0.5; };
recursion no;
notify yes;
minimal-responses no;
};
key rndc_key {
secret "1234abcd8765";
algorithm hmac-sha256;
};
controls {
inet 10.53.0.5 port 9953 allow { any; } keys { rndc_key; };
};
zone "local.nil" {
type master;
file "local.db";
update-policy local;
};

View file

@ -66,3 +66,5 @@ $DDNSCONFGEN -q -r $RANDFILE -a hmac-sha512 -k sha512-key -z keytests.nil > ns1/
cp ns1/sample.db.in ns1/sample.db
cp ns2/sample.db.in ns2/sample.db
cp -f ns5/local.db.in ns5/local.db

View file

@ -475,6 +475,44 @@ then
echo "I:failed"; status=1
fi
n=`expr $n + 1`
ret=0
echo "I:check that 'update-policy local' works from localhost address ($n)"
$NSUPDATE -p 5300 -k ns5/session.key > nsupdate.out.$n 2>&1 << END || ret=1
server 10.53.0.5 5300
local 127.0.0.1 5300
update add fromlocal.local.nil. 600 A 1.2.3.4
send
END
grep REFUSED nsupdate.out.$n > /dev/null 2>&1 && ret=1
$DIG @10.53.0.5 -p 5300 \
+tcp +noadd +nosea +nostat +noquest +nocomm +nocmd \
fromlocal.local.nil. > dig.out.ns5.$n || ret=1
grep fromlocal dig.out.ns5.$n > /dev/null 2>&1 || ret=1
if test $ret -ne 0
then
echo "I:failed"; status=1
fi
n=`expr $n + 1`
ret=0
echo "I:check that 'update-policy local' fails from non-localhost address ($n)"
$NSUPDATE -p 5300 -k ns5/session.key > nsupdate.out.$n 2>&1 << END && ret=1
server 10.53.0.5 5300
local 10.53.0.1 5300
update add nonlocal.local.nil. 600 A 4.3.2.1
send
END
grep REFUSED nsupdate.out.$n > /dev/null 2>&1 || ret=1
$DIG @10.53.0.5 -p 5300 \
+tcp +noadd +nosea +nostat +noquest +nocomm +nocmd \
nonlocal.local.nil. > dig.out.ns5.$n || ret=1
grep nonlocal dig.out.ns5.$n > /dev/null 2>&1 && ret=1
if test $ret -ne 0
then
echo "I:failed"; status=1
fi
n=`expr $n + 1`
ret=0
echo "I:check that changes to the DNSKEY RRset TTL do not have side effects ($n)"

View file

@ -12184,38 +12184,52 @@ example.com. NS ns2.example.net.
is present, it is a configuration error for the
<command>allow-update</command> statement to be
present. The <command>update-policy</command> statement
only examines the signer of a message; the source
(except when set to <literal>local</literal>) only
examines the signer of a message; the source
address is not relevant.
</para>
<para>
There is a pre-defined <command>update-policy</command>
rule which can be switched on with the command
A pre-defined <command>update-policy</command> rule can be
switched on with the command
<command>update-policy local;</command>.
Switching on this rule in a zone causes
<command>named</command> to generate a TSIG session
key and place it in a file, and to allow that key
to update the zone. (By default, the file is
<filename>/var/run/named/session.key</filename>, the key
name is "local-ddns" and the key algorithm is HMAC-SHA256,
but these values are configurable with the
<command>named</command> to generate a TSIG session key and
place it in a file. That key will then be allowed to update
the zone, if the update request is sent from localhost.
By default, the session key is stored in the file
<filename>/var/run/named/session.key</filename>; the key name
is "local-ddns" and the key algorithm is HMAC-SHA256.
These values are configurable with the
<command>session-keyfile</command>,
<command>session-keyname</command> and
<command>session-keyalg</command> options, respectively).
</para>
<para>
A client running on the local system, and with appropriate
permissions, may read that file and use the key to sign update
requests. The zone's update policy will be set to allow that
key to change any record within the zone. Assuming the
key name is "local-ddns", this policy is equivalent to:
A client on the local system, if it is run with appropriate
permissions, may read the session key from the key file and
use the key to sign update requests. The zone's update
policy will be set to allow that key to change any record
within the zone. Assuming the key name is "local-ddns",
this policy is:
</para>
<programlisting>update-policy { grant local-ddns zonesub any; };
</programlisting>
<para>
The command <command>nsupdate -l</command> sends update
requests to localhost, and signs them using the session key.
...with an additional restriction that only clients
connecting from the local system will be permitted to send
updates.
</para>
<para>
Note that only one session key is generated; all zones
configured to use <command>update-policy local</command>
will accept the same key.
</para>
<para>
The command <command>nsupdate -l</command> implements this
feature, sending requests to localhost and signing them using
the key retrieved from the session key file.
</para>
<para>

View file

@ -134,6 +134,15 @@
<filename>bind.keys</filename>. [RT #46155]
</para>
</listitem>
<listitem>
<para>
Previously, <command>update-policy local;</command> accepted
updates from any source so long as they were signed by the
locally-generated session key. This has been further restricted;
updates are now only accepted from locally configured addresses.
[RT #45492]
</para>
</listitem>
<listitem>
<para>
<command>dig +ednsopt</command> now accepts the names

View file

@ -15,8 +15,6 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
/* $Id: ssu.h,v 1.28 2011/01/06 23:47:00 tbox Exp $ */
#ifndef DNS_SSU_H
#define DNS_SSU_H 1
@ -24,6 +22,7 @@
#include <isc/lang.h>
#include <dns/acl.h>
#include <dns/types.h>
#include <dst/dst.h>
@ -42,8 +41,10 @@ ISC_LANG_BEGINDECLS
#define DNS_SSUMATCHTYPE_TCPSELF 10
#define DNS_SSUMATCHTYPE_6TO4SELF 11
#define DNS_SSUMATCHTYPE_EXTERNAL 12
#define DNS_SSUMATCHTYPE_DLZ 13
#define DNS_SSUMATCHTYPE_MAX 12 /* max value */
#define DNS_SSUMATCHTYPE_LOCAL 13
#define DNS_SSUMATCHTYPE_MAX 13 /* max value */
#define DNS_SSUMATCHTYPE_DLZ 14 /* intentionally higher than _MAX */
isc_result_t
dns_ssutable_create(isc_mem_t *mctx, dns_ssutable_t **table);
@ -132,7 +133,12 @@ dns_ssutable_addrule(dns_ssutable_t *table, isc_boolean_t grant,
isc_boolean_t
dns_ssutable_checkrules(dns_ssutable_t *table, dns_name_t *signer,
dns_name_t *name, isc_netaddr_t *tcpaddr,
dns_name_t *name, isc_netaddr_t *addr,
dns_rdatatype_t type, const dst_key_t *key);
isc_boolean_t
dns_ssutable_checkrules2(dns_ssutable_t *table, dns_name_t *signer,
dns_name_t *name, isc_netaddr_t *addr,
isc_boolean_t tcp, const dns_aclenv_t *env,
dns_rdatatype_t type, const dst_key_t *key);
/*%<
* Checks that the attempted update of (name, type) is allowed according
@ -140,11 +146,19 @@ dns_ssutable_checkrules(dns_ssutable_t *table, dns_name_t *signer,
* no rules are matched, access is denied.
*
* Notes:
* 'tcpaddr' should only be set if the request received
* via TCP. This provides a weak assurance that the
* request was not spoofed. 'tcpaddr' is to to validate
* DNS_SSUMATCHTYPE_TCPSELF and DNS_SSUMATCHTYPE_6TO4SELF
* rules.
* In dns_ssutable_checkrules(), 'addr' should only be
* set if the request received via TCP. This provides a
* weak assurance that the request was not spoofed.
* 'addr' is to to validate DNS_SSUMATCHTYPE_TCPSELF
* and DNS_SSUMATCHTYPE_6TO4SELF rules.
*
* In dns_ssutable_checkrules2(), 'addr' can also be passed for
* UDP requests and TCP is specified via the 'tcp' parameter.
* In addition to DNS_SSUMATCHTYPE_TCPSELF and
* tcp_ssumatchtype_6to4self rules, the address
* also be used to check DNS_SSUMATCHTYPE_LOCAL rules.
* If 'addr' is set then 'env' must also be set so that
* requests from non-localhost addresses can be rejected.
*
* For DNS_SSUMATCHTYPE_TCPSELF the addresses are mapped to
* the standard reverse names under IN-ADDR.ARPA and IP6.ARPA.
@ -160,8 +174,10 @@ dns_ssutable_checkrules(dns_ssutable_t *table, dns_name_t *signer,
* Requires:
*\li 'table' is a valid SSU table
*\li 'signer' is NULL or a valid absolute name
*\li 'tcpaddr' is NULL or a valid network address.
*\li 'addr' is NULL or a valid network address.
*\li 'aclenv' is NULL or a valid ACL environment.
*\li 'name' is a valid absolute name
*\li if 'addr' is not NULL, 'env' is not NULL.
*/

View file

@ -348,9 +348,20 @@ stf_from_address(dns_name_t *stfself, isc_netaddr_t *tcpaddr) {
isc_boolean_t
dns_ssutable_checkrules(dns_ssutable_t *table, dns_name_t *signer,
dns_name_t *name, isc_netaddr_t *tcpaddr,
dns_rdatatype_t type,
const dst_key_t *key)
dns_name_t *name, isc_netaddr_t *addr,
dns_rdatatype_t type, const dst_key_t *key)
{
return (dns_ssutable_checkrules2
(table, signer, name, addr,
addr == NULL ? ISC_FALSE : ISC_TRUE,
NULL, type, key));
}
isc_boolean_t
dns_ssutable_checkrules2(dns_ssutable_t *table, dns_name_t *signer,
dns_name_t *name, isc_netaddr_t *addr,
isc_boolean_t tcp, const dns_aclenv_t *env,
dns_rdatatype_t type, const dst_key_t *key)
{
dns_ssurule_t *rule;
unsigned int i;
@ -359,12 +370,14 @@ dns_ssutable_checkrules(dns_ssutable_t *table, dns_name_t *signer,
dns_name_t *tcpself;
dns_name_t *stfself;
isc_result_t result;
int match;
REQUIRE(VALID_SSUTABLE(table));
REQUIRE(signer == NULL || dns_name_isabsolute(signer));
REQUIRE(dns_name_isabsolute(name));
REQUIRE(addr == NULL || env != NULL);
if (signer == NULL && tcpaddr == NULL)
if (signer == NULL && addr == NULL)
return (ISC_FALSE);
for (rule = ISC_LIST_HEAD(table->rules);
@ -373,6 +386,7 @@ dns_ssutable_checkrules(dns_ssutable_t *table, dns_name_t *signer,
{
switch (rule->matchtype) {
case DNS_SSUMATCHTYPE_NAME:
case DNS_SSUMATCHTYPE_LOCAL:
case DNS_SSUMATCHTYPE_SUBDOMAIN:
case DNS_SSUMATCHTYPE_WILDCARD:
case DNS_SSUMATCHTYPE_SELF:
@ -398,7 +412,7 @@ dns_ssutable_checkrules(dns_ssutable_t *table, dns_name_t *signer,
break;
case DNS_SSUMATCHTYPE_TCPSELF:
case DNS_SSUMATCHTYPE_6TO4SELF:
if (tcpaddr == NULL)
if (!tcp || addr == NULL)
continue;
break;
}
@ -412,6 +426,20 @@ dns_ssutable_checkrules(dns_ssutable_t *table, dns_name_t *signer,
if (!dns_name_issubdomain(name, rule->name))
continue;
break;
case DNS_SSUMATCHTYPE_LOCAL:
if (addr == NULL) {
continue;
}
if (!dns_name_issubdomain(name, rule->name)) {
continue;
}
dns_acl_match(addr, NULL, env->localhost,
NULL, &match, NULL);
if (match == 0) {
continue;
}
break;
case DNS_SSUMATCHTYPE_WILDCARD:
if (!dns_name_matcheswildcard(name, rule->name))
continue;
@ -461,7 +489,7 @@ dns_ssutable_checkrules(dns_ssutable_t *table, dns_name_t *signer,
case DNS_SSUMATCHTYPE_TCPSELF:
dns_fixedname_init(&fixed);
tcpself = dns_fixedname_name(&fixed);
reverse_from_address(tcpself, tcpaddr);
reverse_from_address(tcpself, addr);
if (dns_name_iswildcard(rule->identity)) {
if (!dns_name_matcheswildcard(tcpself,
rule->identity))
@ -476,7 +504,7 @@ dns_ssutable_checkrules(dns_ssutable_t *table, dns_name_t *signer,
case DNS_SSUMATCHTYPE_6TO4SELF:
dns_fixedname_init(&fixed);
stfself = dns_fixedname_name(&fixed);
stf_from_address(stfself, tcpaddr);
stf_from_address(stfself, addr);
if (dns_name_iswildcard(rule->identity)) {
if (!dns_name_matcheswildcard(stfself,
rule->identity))
@ -490,13 +518,13 @@ dns_ssutable_checkrules(dns_ssutable_t *table, dns_name_t *signer,
break;
case DNS_SSUMATCHTYPE_EXTERNAL:
if (!dns_ssu_external_match(rule->identity, signer,
name, tcpaddr, type, key,
name, addr, type, key,
table->mctx))
continue;
break;
case DNS_SSUMATCHTYPE_DLZ:
if (!dns_dlz_ssumatch(table->dlzdatabase, signer,
name, tcpaddr, type, key))
name, addr, type, key))
continue;
break;
}

View file

@ -181,6 +181,12 @@ isc_netaddr_prefixok(const isc_netaddr_t *na, unsigned int prefixlen);
* ISC_R_FAILURE extra bits.
*/
isc_boolean_t
isc_netaddr_isloopback(const isc_netaddr_t *na);
/*
* Test whether the netaddr 'na' is a loopback IPv4 or IPv6 address (in
* 127.0.0.0/8 or ::1).
*/
ISC_LANG_ENDDECLS
#endif /* ISC_NETADDR_H */

View file

@ -448,3 +448,16 @@ isc_netaddr_fromv4mapped(isc_netaddr_t *t, const isc_netaddr_t *s) {
memmove(&t->type.in, (char *)&src->type.in6 + 12, 4);
return;
}
isc_boolean_t
isc_netaddr_isloopback(const isc_netaddr_t *na) {
switch (na->family) {
case AF_INET:
return (ISC_TF((ntohl(na->type.in.s_addr) & 0xff000000U) ==
0x7f000000U));
case AF_INET6:
return (IN6_IS_ADDR_LOOPBACK(&na->type.in6));
default:
return (ISC_FALSE);
}
}