From: mb Date: Wed, 4 Nov 2020 16:00:28 +0000 (+0100) Subject: RPZ: provide rpz-client-ip trigger and actions X-Git-Tag: release-1.14.0rc1~62^2~53^2^2~21 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bd0c910830808b6d039bc4ac2655a95efd3991fe;p=thirdparty%2Funbound.git RPZ: provide rpz-client-ip trigger and actions --- diff --git a/services/localzone.c b/services/localzone.c index b5d8472bc..5e2104a7c 100644 --- a/services/localzone.c +++ b/services/localzone.c @@ -1561,7 +1561,7 @@ local_zones_zone_answer(struct local_zone* z, struct module_env* env, lz_type == local_zone_truncate)? LDNS_RCODE_NOERROR:LDNS_RCODE_NXDOMAIN; rcode = lz_type == local_zone_truncate ? (rcode|BIT_TC) : rcode; - if(z->soa) + if(z != NULL && z->soa) return local_encode(qinfo, env, edns, repinfo, buf, temp, z->soa, 0, rcode); local_error_encode(qinfo, env, edns, repinfo, buf, temp, rcode, @@ -1578,7 +1578,7 @@ local_zones_zone_answer(struct local_zone* z, struct module_env* env, * does not, then we should make this noerror/nodata */ if(ld && ld->rrsets) { int rcode = LDNS_RCODE_NOERROR; - if(z->soa) + if(z != NULL && z->soa) return local_encode(qinfo, env, edns, repinfo, buf, temp, z->soa, 0, rcode); local_error_encode(qinfo, env, edns, repinfo, buf, temp, rcode, diff --git a/services/rpz.c b/services/rpz.c index fb047a7f2..f8fa803de 100644 --- a/services/rpz.c +++ b/services/rpz.c @@ -51,6 +51,8 @@ #include "util/locks.h" #include "util/regional.h" +typedef struct resp_addr rpz_aclnode_type; + /** string for RPZ action enum */ const char* rpz_action_to_string(enum rpz_action a) @@ -305,6 +307,7 @@ void rpz_delete(struct rpz* r) return; local_zones_delete(r->local_zones); respip_set_delete(r->respip_set); + respip_set_delete(r->client_set); regional_destroy(r->region); free(r->taglist); free(r->log_name); @@ -317,12 +320,16 @@ rpz_clear(struct rpz* r) /* must hold write lock on auth_zone */ local_zones_delete(r->local_zones); respip_set_delete(r->respip_set); + respip_set_delete(r->client_set); if(!(r->local_zones = local_zones_create())){ return 0; } if(!(r->respip_set = respip_set_create())) { return 0; } + if(!(r->client_set = respip_set_create())) { + return 0; + } return 1; } @@ -332,6 +339,10 @@ rpz_finish_config(struct rpz* r) lock_rw_wrlock(&r->respip_set->lock); addr_tree_init_parents(&r->respip_set->ip_tree); lock_rw_unlock(&r->respip_set->lock); + + lock_rw_wrlock(&r->client_set->lock); + addr_tree_init_parents(&r->client_set->ip_tree); + lock_rw_unlock(&r->client_set->lock); } /** new rrset containing CNAME override, does not yet contain a dname */ @@ -398,6 +409,12 @@ rpz_create(struct config_auth* p) if(!(r->respip_set = respip_set_create())) { goto err; } + + /* (ab)use respip for client acl */ + if(!(r->client_set = respip_set_create())) { + goto err; + } + r->taglistlen = p->rpz_taglistlen; r->taglist = memdup(p->rpz_taglist, r->taglistlen); if(p->rpz_action_override) { @@ -440,6 +457,8 @@ err: local_zones_delete(r->local_zones); if(r->respip_set) respip_set_delete(r->respip_set); + if(r->client_set) + respip_set_delete(r->client_set); if(r->taglist) free(r->taglist); if(r->region) @@ -541,52 +560,36 @@ rpz_insert_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, return; } -/** Insert RR into RPZ's respip_set */ static int -rpz_insert_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, - enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl, - uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len) +rpz_insert_ipaddr_based_trigger(struct respip_set* set, struct sockaddr_storage* addr, + socklen_t addrlen, int net, enum rpz_action a, uint16_t rrtype, + uint16_t rrclass, uint32_t ttl, uint8_t* rdata, size_t rdata_len, + uint8_t* rr, size_t rr_len) { struct resp_addr* node; - struct sockaddr_storage addr; - socklen_t addrlen; - int net, af; char* rrstr; enum respip_action respa = rpz_action_to_respip_action(a); - if(a == RPZ_INVALID_ACTION || respa == respip_invalid) { - verbose(VERB_ALGO, "RPZ: skipping unsupported action: %s", - rpz_action_to_string(a)); - return 0; - } - - if(a == RPZ_TCP_ONLY_ACTION) { - verbose(VERB_ALGO, "RPZ: insert respip trigger: tcp-only"); - } - - if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) - return 0; - - lock_rw_wrlock(&r->respip_set->lock); + lock_rw_wrlock(&set->lock); rrstr = sldns_wire2str_rr(rr, rr_len); if(!rrstr) { log_err("malloc error while inserting RPZ respip trigger"); - lock_rw_unlock(&r->respip_set->lock); + lock_rw_unlock(&set->lock); return 0; } - if(!(node=respip_sockaddr_find_or_create(r->respip_set, &addr, addrlen, + if(!(node=respip_sockaddr_find_or_create(set, addr, addrlen, net, 1, rrstr))) { - lock_rw_unlock(&r->respip_set->lock); + lock_rw_unlock(&set->lock); free(rrstr); return 0; } lock_rw_wrlock(&node->lock); - lock_rw_unlock(&r->respip_set->lock); + lock_rw_unlock(&set->lock); node->action = respa; if(a == RPZ_LOCAL_DATA_ACTION) { - respip_enter_rr(r->respip_set->region, node, rrtype, + respip_enter_rr(set->region, node, rrtype, rrclass, ttl, rdata, rdata_len, rrstr, ""); } lock_rw_unlock(&node->lock); @@ -594,6 +597,60 @@ rpz_insert_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, return 1; } +static int +rpz_insert_client_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, + enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl, + uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len) +{ + struct sockaddr_storage addr; + socklen_t addrlen; + int net, af; + enum respip_action respa = rpz_action_to_respip_action(a); + + verbose(VERB_ALGO, "RPZ: insert client ip trigger: %s", rpz_action_to_string(a)); + if(a == RPZ_INVALID_ACTION || respa == respip_invalid) { + verbose(VERB_ALGO, "RPZ: skipping unsupported action: %s", + rpz_action_to_string(a)); + return 0; + } + + if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) { + verbose(VERB_ALGO, "RPZ: unable to parse client ip"); + return 0; + } + + return rpz_insert_ipaddr_based_trigger(r->client_set, &addr, addrlen, net, + a, rrtype, rrclass, ttl, rdata, rdata_len, rr, rr_len); +} + +/** Insert RR into RPZ's respip_set */ +static int +rpz_insert_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, + enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl, + uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len) +{ + struct sockaddr_storage addr; + socklen_t addrlen; + int net, af; + enum respip_action respa = rpz_action_to_respip_action(a); + + verbose(VERB_ALGO, "RPZ: insert response ip trigger: %s", rpz_action_to_string(a)); + + if(a == RPZ_INVALID_ACTION || respa == respip_invalid) { + verbose(VERB_ALGO, "RPZ: skipping unsupported action: %s", + rpz_action_to_string(a)); + return 0; + } + + if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) { + verbose(VERB_ALGO, "RPZ: unable to parse response ip"); + return 0; + } + + return rpz_insert_ipaddr_based_trigger(r->respip_set, &addr, addrlen, net, + a, rrtype, rrclass, ttl, rdata, rdata_len, rr, rr_len); +} + int rpz_insert_rr(struct rpz* r, uint8_t* azname, size_t aznamelen, uint8_t* dname, size_t dnamelen, uint16_t rr_type, uint16_t rr_class, uint32_t rr_ttl, @@ -643,14 +700,17 @@ rpz_insert_rr(struct rpz* r, uint8_t* azname, size_t aznamelen, uint8_t* dname, rpz_insert_qname_trigger(r, policydname, policydnamelen, a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, rr_len); - } - else if(t == RPZ_RESPONSE_IP_TRIGGER) { + } else if(t == RPZ_RESPONSE_IP_TRIGGER) { rpz_insert_response_ip_trigger(r, policydname, policydnamelen, a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, rr_len); free(policydname); - } - else { + } else if(t == RPZ_CLIENT_IP_TRIGGER) { + rpz_insert_client_ip_trigger(r, policydname, policydnamelen, + a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, + rr_len); + free(policydname); + } else { free(policydname); verbose(VERB_ALGO, "RPZ: skipping unsupported trigger: %s", rpz_trigger_to_string(t)); @@ -954,52 +1014,166 @@ log_rpz_apply(uint8_t* dname, enum rpz_action a, struct query_info* qinfo, log_nametypeclass(0, txt, qinfo->qname, qinfo->qtype, qinfo->qclass); } -int -rpz_apply_qname_trigger(struct auth_zones* az, struct module_env* env, - struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf, - struct regional* temp, struct comm_reply* repinfo, - uint8_t* taglist, size_t taglen, struct ub_server_stats* stats) +static enum rpz_action +rpz_apply_client_ip_trigger(struct rpz* r, struct comm_reply* repinfo) +{ + struct resp_addr* raddr = NULL; + enum rpz_action action = RPZ_INVALID_ACTION; + struct sockaddr_storage* addr = &repinfo->addr; + socklen_t addrlen = repinfo->addrlen; + + lock_rw_rdlock(&r->client_set->lock); + + raddr = (struct resp_addr*)addr_tree_lookup(&r->client_set->ip_tree, + addr, addrlen); + if(raddr != NULL) { + action = respip_action_to_rpz_action(raddr->action); + lock_rw_unlock(&raddr->lock); + } + + verbose(VERB_ALGO, "RPZ: apply client ip trigger: found=%d action=%s", + raddr != NULL, rpz_action_to_string(action)); + + lock_rw_unlock(&r->client_set->lock); + + return action; +} + +static inline +enum rpz_action +rpz_resolve_client_action_and_zone(struct auth_zones* az, struct query_info* qinfo, + struct comm_reply* repinfo, uint8_t* taglist, size_t taglen, + struct ub_server_stats* stats, + /* output parameters */ + struct local_zone** z_out, struct auth_zone** a_out, struct rpz** r_out ) { + struct auth_zone* a = NULL; struct rpz* r = NULL; - struct auth_zone* a; - int ret; - enum localzone_type lzt; struct local_zone* z = NULL; - struct local_data* ld = NULL; + enum rpz_action action = RPZ_PASSTHRU_ACTION; + lock_rw_rdlock(&az->rpz_lock); + for(a = az->rpz_first; a; a = a->rpz_az_next) { lock_rw_rdlock(&a->lock); r = a->rpz; - if(!r->taglist || taglist_intersect(r->taglist, - r->taglistlen, taglist, taglen)) { - z = rpz_find_zone(r, qinfo->qname, qinfo->qname_len, - qinfo->qclass, 0, 0, 0); - if(z && r->action_override == RPZ_DISABLED_ACTION) { - if(r->log) - log_rpz_apply(z->name, - r->action_override, - qinfo, repinfo, r->log_name); - /* TODO only register stats when stats_extended? - * */ - stats->rpz_action[r->action_override]++; - lock_rw_unlock(&z->lock); - z = NULL; - } - if(z) - break; + if(r->taglist && !taglist_intersect(r->taglist, + r->taglistlen, taglist, taglen)) { + lock_rw_unlock(&a->lock); + continue; + } + z = rpz_find_zone(r, qinfo->qname, qinfo->qname_len, + qinfo->qclass, 0, 0, 0); + action = rpz_apply_client_ip_trigger(r, repinfo); + if(z && r->action_override == RPZ_DISABLED_ACTION) { + if(r->log) + log_rpz_apply(z->name, + r->action_override, + qinfo, repinfo, r->log_name); + /* TODO only register stats when stats_extended? */ + stats->rpz_action[r->action_override]++; + lock_rw_unlock(&z->lock); + z = NULL; } - lock_rw_unlock(&a->lock); /* not found in this auth_zone */ + if(z) { + break; + } + /* not found in this auth_zone */ + lock_rw_unlock(&a->lock); } + lock_rw_unlock(&az->rpz_lock); - if(!z) - return 0; /* not holding auth_zone.lock anymore */ + *r_out = r; + *a_out = a; + *z_out = z; - log_assert(r); - if(r->action_override == RPZ_NO_OVERRIDE_ACTION) - lzt = z->type; - else + return action; +} + +static inline int +rpz_resolve_final_localzone_action(struct rpz* r, struct local_zone* z, enum rpz_action client_action) +{ + enum localzone_type lzt; + if(r->action_override == RPZ_NO_OVERRIDE_ACTION) { + switch (client_action) { + case RPZ_NODATA_ACTION: + case RPZ_NXDOMAIN_ACTION: + case RPZ_DROP_ACTION: + case RPZ_TCP_ONLY_ACTION: + case RPZ_PASSTHRU_ACTION: + lzt = rpz_action_to_localzone_type(client_action); + break; + case RPZ_LOCAL_DATA_ACTION: + verbose(VERB_ALGO, + "RPZ: client ip trigger with local-data unimplemented:" + " defaulting to rpz-passthru"); + lzt = rpz_action_to_localzone_type(RPZ_PASSTHRU_ACTION); + break; + case RPZ_INVALID_ACTION: + lzt = z->type; + break; + default: + lzt = z->type; + break; + } + } else { lzt = rpz_action_to_localzone_type(r->action_override); + } + return lzt; +} + +static inline int +rpz_is_udp_query(struct comm_reply* repinfo) { + return repinfo != NULL + ? (repinfo->c != NULL + ? repinfo->c->type == comm_udp + : 0) + : 0; +} + +int +rpz_apply_qname_trigger(struct auth_zones* az, struct module_env* env, + struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf, + struct regional* temp, struct comm_reply* repinfo, + uint8_t* taglist, size_t taglen, struct ub_server_stats* stats) +{ + struct rpz* r = NULL; + struct auth_zone* a = NULL; + struct local_zone* z = NULL; + struct local_data* ld = NULL; + int ret; + enum localzone_type lzt; + enum rpz_action client_action; + + client_action = rpz_resolve_client_action_and_zone( + az, qinfo, repinfo, taglist, taglen, stats, &z, &a, &r); + + verbose(VERB_ALGO, "RPZ: qname trigger: client action=%s", + rpz_action_to_string(client_action)); + + if(!z) { + verbose(VERB_ALGO, "RPZ: client action without zone"); + if(client_action == RPZ_PASSTHRU_ACTION + || client_action == RPZ_INVALID_ACTION + || (client_action == RPZ_TCP_ONLY_ACTION + && !rpz_is_udp_query(repinfo))) { + return 0; + } + // XXX: log_rpz_apply not possbile because no zone + stats->rpz_action[client_action]++; + local_zones_zone_answer(NULL /*no zone*/, env, qinfo, edns, + repinfo, buf, temp, 0 /* no local data used */, + rpz_action_to_localzone_type(client_action)); + return 1; + } + + log_assert(r); + + lzt = rpz_resolve_final_localzone_action(r, z, client_action); + + verbose(VERB_ALGO, "RPZ: final client action=%s", + rpz_action_to_string(localzone_type_to_rpz_action(lzt))); if(r->action_override == RPZ_CNAME_OVERRIDE_ACTION) { qinfo->local_alias = @@ -1040,7 +1214,7 @@ rpz_apply_qname_trigger(struct auth_zones* az, struct module_env* env, lock_rw_unlock(&a->lock); return !qinfo->local_alias; } -verbose(VERB_ALGO, "xxxxxx repinfo=%p is_udp=%d", repinfo, repinfo->c->type == comm_udp); + ret = local_zones_zone_answer(z, env, qinfo, edns, repinfo, buf, temp, 0 /* no local data used */, lzt); if(r->log) diff --git a/services/rpz.h b/services/rpz.h index 77a2db55c..1210f2039 100644 --- a/services/rpz.h +++ b/services/rpz.h @@ -92,6 +92,7 @@ enum rpz_action { struct rpz { struct local_zones* local_zones; struct respip_set* respip_set; + struct respip_set* client_set; uint8_t* taglist; size_t taglistlen; enum rpz_action action_override; diff --git a/testdata/rpz_clientip.rpl b/testdata/rpz_clientip.rpl new file mode 100644 index 000000000..bd559871b --- /dev/null +++ b/testdata/rpz_clientip.rpl @@ -0,0 +1,180 @@ +; config options +server: + module-config: "respip validator iterator" + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: no + access-control: 192.0.0.0/8 allow + +rpz: + name: "rpz.example.com." + zonefile: +TEMPFILE_NAME rpz.example.com +TEMPFILE_CONTENTS rpz.example.com +$ORIGIN example.com. +rpz 3600 IN SOA ns1.rpz.example.com. hostmaster.rpz.example.com. ( + 1379078166 28800 7200 604800 7200 ) + 3600 IN NS ns1.rpz.example.com. + 3600 IN NS ns2.rpz.example.com. +$ORIGIN rpz.example.com. +24.0.0.0.192.rpz-client-ip CNAME . +24.0.1.0.192.rpz-client-ip CNAME *. +24.0.2.0.192.rpz-client-ip CNAME rpz-drop. +24.0.3.0.192.rpz-client-ip CNAME rpz-passthru. +24.0.4.0.192.rpz-client-ip CNAME rpz-tcp-only. +TEMPFILE_END + +stub-zone: + name: "a." + stub-addr: 10.20.30.40 +CONFIG_END + +SCENARIO_BEGIN Test RPZ client ip triggers + +RANGE_BEGIN 0 100 + ADDRESS 10.20.30.40 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +a. IN NS +SECTION ANSWER +a. IN NS ns.a. +SECTION ADDITIONAL +ns.a IN A 10.20.30.40 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +a.a. IN TXT +SECTION ANSWER +a.a. IN TXT "upstream txt rr a.a." +ENTRY_END + +RANGE_END + +; unrelated client ip address -- passthru + +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +a.a. IN TXT +ENTRY_END + +STEP 11 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +a.a. IN TXT +SECTION ANSWER +a.a. IN TXT "upstream txt rr a.a." +ENTRY_END + +; should be NXDOMAIN + +STEP 20 QUERY ADDRESS 192.0.0.1 +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +a.a. IN TXT +ENTRY_END + +STEP 21 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA RD RA NXDOMAIN +SECTION QUESTION +a.a. IN TXT +SECTION ANSWER +ENTRY_END + +; should be NODATA + +STEP 30 QUERY ADDRESS 192.0.1.1 +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +a.a. IN TXT +ENTRY_END + +STEP 31 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA RD RA NOERROR +SECTION QUESTION +a.a. IN TXT +SECTION ANSWER +ENTRY_END + +; should be PASSTHRU + +STEP 40 QUERY ADDRESS 192.0.3.1 +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +a.a. IN TXT +ENTRY_END + +STEP 41 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +a.a. IN TXT +SECTION ANSWER +a.a. IN TXT "upstream txt rr a.a." +ENTRY_END + +; should be TRUNCATED + +STEP 50 QUERY ADDRESS 192.0.4.1 +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +a.a. IN TXT +ENTRY_END + +STEP 51 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA TC RD RA NOERROR +SECTION QUESTION +a.a. IN TXT +SECTION ANSWER +ENTRY_END + +; should not be TRUNCATED via TCP + +STEP 52 QUERY ADDRESS 192.0.4.1 +ENTRY_BEGIN +MATCH TCP +REPLY RD +SECTION QUESTION +a.a. IN TXT +ENTRY_END + +STEP 53 CHECK_ANSWER +ENTRY_BEGIN +MATCH all TCP +REPLY QR RD RA NOERROR +SECTION QUESTION +a.a. IN TXT +SECTION ANSWER +a.a. IN TXT "upstream txt rr a.a." +ENTRY_END + +; should be DROPPED + +STEP 90 QUERY ADDRESS 192.0.2.1 +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +a.a. IN TXT +ENTRY_END + +SCENARIO_END