access-control

git-svn-id: file:///svn/unbound/trunk@769 be551aaa-1e26-0410-a405-d3ace91eadb9
This commit is contained in:
Wouter Wijngaards 2007-11-19 15:32:55 +00:00
parent fc8657c421
commit eda6528c14
18 changed files with 1007 additions and 834 deletions

View File

@ -92,6 +92,7 @@ morechecks(struct config_file* cfg)
int i; int i;
struct sockaddr_storage a; struct sockaddr_storage a;
socklen_t alen; socklen_t alen;
struct config_acl* acl;
for(i=0; i<cfg->num_ifs; i++) { for(i=0; i<cfg->num_ifs; i++) {
if(!ipstrtoaddr(cfg->ifs[i], UNBOUND_DNS_PORT, &a, &alen)) { if(!ipstrtoaddr(cfg->ifs[i], UNBOUND_DNS_PORT, &a, &alen)) {
fatal_exit("cannot parse interface specified as '%s'", fatal_exit("cannot parse interface specified as '%s'",
@ -105,6 +106,13 @@ morechecks(struct config_file* cfg)
"specified as '%s'", cfg->out_ifs[i]); "specified as '%s'", cfg->out_ifs[i]);
} }
} }
for(acl=cfg->acls; acl; acl = acl->next) {
if(!netblockstrtoaddr(acl->address, UNBOUND_DNS_PORT,
&a, &alen, &i)) {
fatal_exit("cannot parse access control address %s %s",
acl->address, acl->control);
}
}
if(cfg->verbosity < 0) if(cfg->verbosity < 0)
fatal_exit("verbosity value < 0"); fatal_exit("verbosity value < 0");

View File

@ -116,7 +116,6 @@ acl_list_str_cfg(struct acl_list* acl, const char* str, const char* s2,
{ {
struct sockaddr_storage addr; struct sockaddr_storage addr;
int net; int net;
char* s = NULL;
socklen_t addrlen; socklen_t addrlen;
enum acl_access control; enum acl_access control;
if(strcmp(s2, "allow") == 0) if(strcmp(s2, "allow") == 0)
@ -129,33 +128,10 @@ acl_list_str_cfg(struct acl_list* acl, const char* str, const char* s2,
log_err("access control type %s unknown", str); log_err("access control type %s unknown", str);
return 0; return 0;
} }
net = (str_is_ip6(str)?128:32); if(!netblockstrtoaddr(str, UNBOUND_DNS_PORT, &addr, &addrlen, &net)) {
if((s=strchr(str, '/'))) { log_err("cannot parse access control: %s %s", str, s2);
if(atoi(s+1) > net) {
log_err("acl netblock too large: %s", str);
return 0;
}
net = atoi(s+1);
if(net == 0 && strcmp(s+1, "0") != 0) {
log_err("cannot parse acl netblock:"
" '%s'", str);
return 0;
}
if(!(s = strdup(str))) {
log_err("out of memory");
return 0;
}
*strchr(s, '/') = '\0';
}
if(!ipstrtoaddr(s?s:str, UNBOUND_DNS_PORT, &addr, &addrlen)) {
free(s);
log_err("cannot parse acl ip address: '%s'", str);
return 0; return 0;
} }
if(s) {
free(s);
addr_mask(&addr, addrlen, net);
}
if(!acl_list_insert(acl, &addr, addrlen, net, control, if(!acl_list_insert(acl, &addr, addrlen, net, control,
complain_duplicates)) { complain_duplicates)) {
log_err("out of memory"); log_err("out of memory");

View File

@ -42,6 +42,7 @@
#include "config.h" #include "config.h"
#include "daemon/daemon.h" #include "daemon/daemon.h"
#include "daemon/worker.h" #include "daemon/worker.h"
#include "daemon/acl_list.h"
#include "util/log.h" #include "util/log.h"
#include "util/config_file.h" #include "util/config_file.h"
#include "util/data/msgreply.h" #include "util/data/msgreply.h"
@ -134,6 +135,12 @@ daemon_init()
return NULL; return NULL;
} }
alloc_init(&daemon->superalloc, NULL, 0); alloc_init(&daemon->superalloc, NULL, 0);
daemon->acl = acl_list_create();
if(!daemon->acl) {
free(daemon->env);
free(daemon);
return NULL;
}
return daemon; return daemon;
} }
@ -397,6 +404,8 @@ void
daemon_fork(struct daemon* daemon) daemon_fork(struct daemon* daemon)
{ {
log_assert(daemon); log_assert(daemon);
if(!acl_list_apply_cfg(daemon->acl, daemon->cfg))
fatal_exit("Could not setup access control list");
/* setup modules */ /* setup modules */
daemon_setup_modules(daemon); daemon_setup_modules(daemon);
@ -465,6 +474,7 @@ daemon_delete(struct daemon* daemon)
infra_delete(daemon->env->infra_cache); infra_delete(daemon->env->infra_cache);
} }
alloc_clear(&daemon->superalloc); alloc_clear(&daemon->superalloc);
acl_list_delete(daemon->acl);
free(daemon->pidfile); free(daemon->pidfile);
free(daemon->env); free(daemon->env);
free(daemon); free(daemon);

View File

@ -50,6 +50,7 @@ struct listen_port;
struct slabhash; struct slabhash;
struct module_env; struct module_env;
struct rrset_cache; struct rrset_cache;
struct acl_list;
/** /**
* Structure holding worker list. * Structure holding worker list.
@ -78,6 +79,8 @@ struct daemon {
int num_modules; int num_modules;
/** the module callbacks, array of num_modules length */ /** the module callbacks, array of num_modules length */
struct module_func_block** modfunc; struct module_func_block** modfunc;
/** access control, which client IPs are allowed to connect */
struct acl_list* acl;
}; };
/** /**

View File

@ -45,6 +45,7 @@
#include "util/random.h" #include "util/random.h"
#include "daemon/worker.h" #include "daemon/worker.h"
#include "daemon/daemon.h" #include "daemon/daemon.h"
#include "daemon/acl_list.h"
#include "util/netevent.h" #include "util/netevent.h"
#include "util/config_file.h" #include "util/config_file.h"
#include "util/module.h" #include "util/module.h"
@ -663,12 +664,28 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
struct lruhash_entry* e; struct lruhash_entry* e;
struct query_info qinfo; struct query_info qinfo;
struct edns_data edns; struct edns_data edns;
enum acl_access acl;
if(error != NETEVENT_NOERROR) { if(error != NETEVENT_NOERROR) {
/* some bad tcp query DNS formats give these error calls */ /* some bad tcp query DNS formats give these error calls */
verbose(VERB_ALGO, "handle request called with err=%d", error); verbose(VERB_ALGO, "handle request called with err=%d", error);
return 0; return 0;
} }
acl = acl_list_lookup(worker->daemon->acl, &repinfo->addr,
repinfo->addrlen);
if(acl == acl_deny) {
comm_point_drop_reply(repinfo);
return 0;
} else if(acl == acl_refuse) {
ldns_buffer_set_limit(c->buffer, LDNS_HEADER_SIZE);
ldns_buffer_write_at(c->buffer, 4,
(uint8_t*)"\0\0\0\0\0\0\0\0", 8);
LDNS_QR_SET(ldns_buffer_begin(c->buffer));
LDNS_RCODE_SET(ldns_buffer_begin(c->buffer),
LDNS_RCODE_REFUSED);
log_buf(VERB_ALGO, "refuse", c->buffer);
return 1;
}
if((ret=worker_check_request(c->buffer, worker)) != 0) { if((ret=worker_check_request(c->buffer, worker)) != 0) {
verbose(VERB_ALGO, "worker check request: bad query."); verbose(VERB_ALGO, "worker check request: bad query.");
if(ret != -1) { if(ret != -1) {

View File

@ -7,6 +7,8 @@
- README talks about gnu make. - README talks about gnu make.
- 0.8: unit test for addr_mask and fixups for it. - 0.8: unit test for addr_mask and fixups for it.
and unit test for addr_in_common(). and unit test for addr_in_common().
- 0.8: access-control config file element.
and unit test rpl replay file.
16 November 2007: Wouter 16 November 2007: Wouter
- privilege separation is not needed in unbound at this time. - privilege separation is not needed in unbound at this time.

View File

@ -113,6 +113,15 @@ server:
# Enable TCP, "yes" or "no". # Enable TCP, "yes" or "no".
# do-tcp: yes # do-tcp: yes
# control which clients are allowed to make (recursive) queries
# to this server. Specify classless netblocks with /size and action.
# By default everything is refused, except for localhost.
# Choose deny (drop message), refuse (polite error reply), allow.
# access-control: 0.0.0.0/0 refuse
# access-control: 127.0.0.0/8 allow
# access-control: ::0/0 refuse
# access-control: ::1 allow
# if given, a chroot(2) is done to the given directory. # if given, a chroot(2) is done to the given directory.
# i.e. you can chroot to the working directory, for example, # i.e. you can chroot to the working directory, for example,
# for extra security, but make sure all files are in that directory. # for extra security, but make sure all files are in that directory.

View File

@ -48,6 +48,12 @@ server:
# logfile: "/etc/unbound/unbound.log" #uncomment to use logfile. # logfile: "/etc/unbound/unbound.log" #uncomment to use logfile.
pidfile: "/etc/unbound/unbound.pid" pidfile: "/etc/unbound/unbound.pid"
# verbosity: 1 # uncomment and increase to get more logging. # verbosity: 1 # uncomment and increase to get more logging.
# listen on all interfaces, answer queries from the local subnet.
interface: 0.0.0.0
interface: ::0
access-control: 10.0.0.0/8 allow
access-control: 2001:DB8::/64 allow
.fi .fi
.Sh FILE FORMAT .Sh FILE FORMAT
There must be whitespace between keywords. Attribute keywords end with a colon ':'. An attribute There must be whitespace between keywords. Attribute keywords end with a colon ':'. An attribute
@ -155,6 +161,13 @@ Enable or disable whether ip6 queries are answered. Default is yes.
Enable or disable whether UDP queries are answered. Default is yes. Enable or disable whether UDP queries are answered. Default is yes.
.It \fBdo-tcp:\fR <yes or no> .It \fBdo-tcp:\fR <yes or no>
Enable or disable whether TCP queries are answered. Default is yes. Enable or disable whether TCP queries are answered. Default is yes.
.It \fBaccess-control:\fR <IP netblock> <action>
The netblock is given as an IP4 or IP6 address with /size appended for a
classless network block. The action can be deny, refuse or allow.
Deny stops queries from hosts from that netblock.
Refuse stops queries too, but sends a DNS rcode REFUSED error message back.
Allow gives access to clients from that netblock.
By default only localhost is allowed, the rest is refused.
.It \fBchroot:\fR <directory> .It \fBchroot:\fR <directory>
If given a chroot is done to the given directory. The default is If given a chroot is done to the given directory. The default is
"/etc/unbound". If you give "" no chroot is performed. "/etc/unbound". If you give "" no chroot is performed.

View File

@ -114,35 +114,11 @@ donotq_str_cfg(struct iter_donotq* dq, const char* str)
{ {
struct sockaddr_storage addr; struct sockaddr_storage addr;
int net; int net;
char* s = NULL;
socklen_t addrlen; socklen_t addrlen;
net = (str_is_ip6(str)?128:32); if(!netblockstrtoaddr(str, UNBOUND_DNS_PORT, &addr, &addrlen, &net)) {
if((s=strchr(str, '/'))) { log_err("cannot parse donotquery netblock: %s", str);
if(atoi(s+1) > net) {
log_err("netblock too large: %s", str);
return 0;
}
net = atoi(s+1);
if(net == 0 && strcmp(s+1, "0") != 0) {
log_err("cannot parse donotquery netblock:"
" '%s'", str);
return 0;
}
if(!(s = strdup(str))) {
log_err("out of memory");
return 0;
}
*strchr(s, '/') = '\0';
}
if(!ipstrtoaddr(s?s:str, UNBOUND_DNS_PORT, &addr, &addrlen)) {
free(s);
log_err("cannot parse donotquery ip address: '%s'", str);
return 0; return 0;
} }
if(s) {
free(s);
addr_mask(&addr, addrlen, net);
}
if(!donotq_insert(dq, &addr, addrlen, net)) { if(!donotq_insert(dq, &addr, addrlen, net)) {
log_err("out of memory"); log_err("out of memory");
return 0; return 0;

View File

@ -229,6 +229,11 @@ replay_moment_read(char* remain, FILE* in, const char* name, int* lineno,
while(isspace((int)*remain)) while(isspace((int)*remain))
remain++; remain++;
if(parse_keyword(&remain, "ADDRESS")) { if(parse_keyword(&remain, "ADDRESS")) {
while(isspace((int)*remain))
remain++;
if(strlen(remain) > 0) /* remove \n */
remain[strlen(remain)-1] = 0;
printf("remain '%s'\n", remain);
if(!extstrtoaddr(remain, &mom->addr, &mom->addrlen)) { if(!extstrtoaddr(remain, &mom->addr, &mom->addrlen)) {
log_err("line %d: could not parse ADDRESS: %s", log_err("line %d: could not parse ADDRESS: %s",
*lineno, remain); *lineno, remain);

52
testdata/acl.rpl vendored Normal file
View File

@ -0,0 +1,52 @@
; config options
server:
hide-identity: no
hide-version: no
identity: "test-identity"
version: "test-version"
access-control: 20.0.0.0/8 allow
access-control: 20.40.0.0/16 refuse
access-control: 20.40.80.0/24 deny
CONFIG_END
SCENARIO_BEGIN Test access control list
; version.bind.
; allow
STEP 1 QUERY ADDRESS 20.1.2.3
ENTRY_BEGIN
SECTION QUESTION
version.bind. CH TXT
ENTRY_END
STEP 2 CHECK_ANSWER
ENTRY_BEGIN
MATCH all
REPLY QR RA
SECTION QUESTION
version.bind. CH TXT
SECTION ANSWER
version.bind. 0 CH TXT "test-version"
ENTRY_END
; refuse
STEP 3 QUERY ADDRESS 20.40.2.3
ENTRY_BEGIN
SECTION QUESTION
version.bind. CH TXT
ENTRY_END
STEP 4 CHECK_ANSWER
ENTRY_BEGIN
MATCH all
REPLY QR REFUSED
ENTRY_END
; deny (drop)
STEP 5 QUERY ADDRESS 20.40.80.3
ENTRY_BEGIN
SECTION QUESTION
version.bind. CH TXT
ENTRY_END
; no answer must be pending
SCENARIO_END

File diff suppressed because it is too large Load Diff

View File

@ -144,6 +144,7 @@ forward-addr{COLON} { YDOUT; return VAR_FORWARD_ADDR;}
forward-host{COLON} { YDOUT; return VAR_FORWARD_HOST;} forward-host{COLON} { YDOUT; return VAR_FORWARD_HOST;}
do-not-query-address{COLON} { YDOUT; return VAR_DO_NOT_QUERY_ADDRESS;} do-not-query-address{COLON} { YDOUT; return VAR_DO_NOT_QUERY_ADDRESS;}
do-not-query-localhost{COLON} { YDOUT; return VAR_DO_NOT_QUERY_LOCALHOST;} do-not-query-localhost{COLON} { YDOUT; return VAR_DO_NOT_QUERY_LOCALHOST;}
access-control{COLON} { YDOUT; return VAR_ACCESS_CONTROL;}
hide-identity{COLON} { YDOUT; return VAR_HIDE_IDENTITY;} hide-identity{COLON} { YDOUT; return VAR_HIDE_IDENTITY;}
hide-version{COLON} { YDOUT; return VAR_HIDE_VERSION;} hide-version{COLON} { YDOUT; return VAR_HIDE_VERSION;}
identity{COLON} { YDOUT; return VAR_IDENTITY;} identity{COLON} { YDOUT; return VAR_IDENTITY;}

File diff suppressed because it is too large Load Diff

View File

@ -108,7 +108,8 @@
VAR_ROOT_HINTS = 324, VAR_ROOT_HINTS = 324,
VAR_DO_NOT_QUERY_LOCALHOST = 325, VAR_DO_NOT_QUERY_LOCALHOST = 325,
VAR_CACHE_MAX_TTL = 326, VAR_CACHE_MAX_TTL = 326,
VAR_HARDEN_DNNSEC_STRIPPED = 327 VAR_HARDEN_DNNSEC_STRIPPED = 327,
VAR_ACCESS_CONTROL = 328
}; };
#endif #endif
/* Tokens. */ /* Tokens. */
@ -182,6 +183,7 @@
#define VAR_DO_NOT_QUERY_LOCALHOST 325 #define VAR_DO_NOT_QUERY_LOCALHOST 325
#define VAR_CACHE_MAX_TTL 326 #define VAR_CACHE_MAX_TTL 326
#define VAR_HARDEN_DNNSEC_STRIPPED 327 #define VAR_HARDEN_DNNSEC_STRIPPED 327
#define VAR_ACCESS_CONTROL 328
@ -193,7 +195,7 @@ typedef union YYSTYPE
char* str; char* str;
} }
/* Line 1489 of yacc.c. */ /* Line 1489 of yacc.c. */
#line 197 "util/configparser.h" #line 199 "util/configparser.h"
YYSTYPE; YYSTYPE;
# define yystype YYSTYPE /* obsolescent; will be withdrawn */ # define yystype YYSTYPE /* obsolescent; will be withdrawn */
# define YYSTYPE_IS_DECLARED 1 # define YYSTYPE_IS_DECLARED 1

View File

@ -86,7 +86,7 @@ extern struct config_parser_state* cfg_parser;
%token VAR_KEY_CACHE_SLABS VAR_TRUSTED_KEYS_FILE %token VAR_KEY_CACHE_SLABS VAR_TRUSTED_KEYS_FILE
%token VAR_VAL_NSEC3_KEYSIZE_ITERATIONS VAR_USE_SYSLOG %token VAR_VAL_NSEC3_KEYSIZE_ITERATIONS VAR_USE_SYSLOG
%token VAR_OUTGOING_INTERFACE VAR_ROOT_HINTS VAR_DO_NOT_QUERY_LOCALHOST %token VAR_OUTGOING_INTERFACE VAR_ROOT_HINTS VAR_DO_NOT_QUERY_LOCALHOST
%token VAR_CACHE_MAX_TTL VAR_HARDEN_DNNSEC_STRIPPED %token VAR_CACHE_MAX_TTL VAR_HARDEN_DNNSEC_STRIPPED VAR_ACCESS_CONTROL
%% %%
toplevelvars: /* empty */ | toplevelvars toplevelvar ; toplevelvars: /* empty */ | toplevelvars toplevelvar ;
@ -124,7 +124,7 @@ content_server: server_num_threads | server_verbosity | server_port |
server_trusted_keys_file | server_val_nsec3_keysize_iterations | server_trusted_keys_file | server_val_nsec3_keysize_iterations |
server_use_syslog | server_outgoing_interface | server_root_hints | server_use_syslog | server_outgoing_interface | server_root_hints |
server_do_not_query_localhost | server_cache_max_ttl | server_do_not_query_localhost | server_cache_max_ttl |
server_harden_dnssec_stripped server_harden_dnssec_stripped | server_access_control
; ;
stubstart: VAR_STUB_ZONE stubstart: VAR_STUB_ZONE
{ {
@ -574,6 +574,23 @@ server_do_not_query_localhost: VAR_DO_NOT_QUERY_LOCALHOST STRING
free($2); free($2);
} }
; ;
server_access_control: VAR_ACCESS_CONTROL STRING STRING
{
OUTYY(("P(server_access_control:%s %s)\n", $2, $3));
if(strcmp($3, "deny")!=0 && strcmp($3, "refuse")!=0 &&
strcmp($3, "allow")!=0) {
yyerror("expected deny, refuse or allow in "
"access control action");
} else {
struct config_acl* n = calloc(1, sizeof(*n));
if(!n) fatal_exit("out of memory adding acl");
n->address = $2;
n->control = $3;
n->next = cfg_parser->cfg->acls;
cfg_parser->cfg->acls = n;
}
}
;
server_module_conf: VAR_MODULE_CONF STRING server_module_conf: VAR_MODULE_CONF STRING
{ {
OUTYY(("P(server_module_conf:%s)\n", $2)); OUTYY(("P(server_module_conf:%s)\n", $2));

View File

@ -229,6 +229,39 @@ ipstrtoaddr(const char* ip, int port, struct sockaddr_storage* addr,
return 1; return 1;
} }
int netblockstrtoaddr(const char* str, int port, struct sockaddr_storage* addr,
socklen_t* addrlen, int* net)
{
char* s = NULL;
*net = (str_is_ip6(str)?128:32);
if((s=strchr(str, '/'))) {
if(atoi(s+1) > *net) {
log_err("netblock too large: %s", str);
return 0;
}
*net = atoi(s+1);
if(net == 0 && strcmp(s+1, "0") != 0) {
log_err("cannot parse netblock: '%s'", str);
return 0;
}
if(!(s = strdup(str))) {
log_err("out of memory");
return 0;
}
*strchr(s, '/') = '\0';
}
if(!ipstrtoaddr(s?s:str, port, addr, addrlen)) {
free(s);
log_err("cannot parse ip address: '%s'", str);
return 0;
}
if(s) {
free(s);
addr_mask(addr, *addrlen, *net);
}
return 1;
}
void void
log_nametypeclass(enum verbosity_value v, const char* str, uint8_t* name, log_nametypeclass(enum verbosity_value v, const char* str, uint8_t* name,
uint16_t type, uint16_t dclass) uint16_t type, uint16_t dclass)

View File

@ -186,6 +186,19 @@ int extstrtoaddr(const char* str, struct sockaddr_storage* addr,
int ipstrtoaddr(const char* ip, int port, struct sockaddr_storage* addr, int ipstrtoaddr(const char* ip, int port, struct sockaddr_storage* addr,
socklen_t* addrlen); socklen_t* addrlen);
/**
* Convert ip netblock (ip/netsize) string and port to sockaddr.
* *SLOW*, does a malloc internally to avoid writing over 'ip' string.
* @param ip: ip4 or ip6 address string.
* @param port: port number, host format.
* @param addr: where to store sockaddr.
* @param addrlen: length of stored sockaddr is returned.
* @param net: netblock size is returned.
* @return 0 on error.
*/
int netblockstrtoaddr(const char* ip, int port, struct sockaddr_storage* addr,
socklen_t* addrlen, int* net);
/** /**
* Print string with neat domain name, type and class. * Print string with neat domain name, type and class.
* @param v: at what verbosity level to print this. * @param v: at what verbosity level to print this.