[server] Implement garbage cleanup mode for rule deletion

This is a significant commit that allows fwknopd to delete expired rules
from the firewall policy regardless of whether fwknopd is tracking them.
That is, a third party program could insert rules into the fwknopd
chains (iptables for now, but this will be extended to the other
firewalls) in order to take advantage of fwknopd rule deletion.
This commit is contained in:
Michael Rash 2015-07-13 21:29:16 -04:00
parent 41b137611f
commit ef9498f783
5 changed files with 188 additions and 136 deletions

View File

@ -66,7 +66,8 @@
int fw_config_init(fko_srv_options_t * const opts);
int fw_initialize(const fko_srv_options_t * const opts);
int fw_cleanup(const fko_srv_options_t * const opts);
void check_firewall_rules(const fko_srv_options_t * const opts);
void check_firewall_rules(const fko_srv_options_t * const opts,
const int chk_rm_all);
int fw_dump_rules(const fko_srv_options_t * const opts);
int process_spa_request(const fko_srv_options_t * const opts,
const acc_stanza_t * const acc, spa_data_t * const spadat);

View File

@ -71,7 +71,6 @@ rule_exists_no_chk_support(const fko_srv_options_t * const opts,
const unsigned int exp_ts)
{
int rule_exists=0;
char cmd_buf[CMD_BUFSIZE] = {0};
char ipt_line_buf[CMD_BUFSIZE] = {0};
char target_search[CMD_BUFSIZE] = {0};
char proto_search[CMD_BUFSIZE] = {0};
@ -483,7 +482,6 @@ jump_rule_exists_no_chk_support(const fko_srv_options_t * const opts,
const int chain_num)
{
int exists = 0;
char cmd_buf[CMD_BUFSIZE] = {0};
char chain_search[CMD_BUFSIZE] = {0};
snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_LIST_RULES_ARGS,
@ -1434,19 +1432,152 @@ process_spa_request(const fko_srv_options_t * const opts,
return(res);
}
static void
rm_expired_rules(const fko_srv_options_t * const opts,
const char * const ipt_output_buf,
char *ndx, struct fw_chain *ch, int cpos, time_t now)
{
char exp_str[12] = {0};
char rule_num_str[6] = {0};
char *rn_start, *rn_end, *tmp_mark;
int res, is_err, rn_offset=0, rule_num;
time_t rule_exp, min_exp = 0;
/* walk the list and process rules as needed.
*/
while (ndx != NULL) {
/* Jump forward and extract the timestamp
*/
ndx += strlen(EXPIRE_COMMENT_PREFIX);
/* remember this spot for when we look for the next
* rule.
*/
tmp_mark = ndx;
strlcpy(exp_str, ndx, sizeof(exp_str));
rule_exp = (time_t)atoll(exp_str);
if(rule_exp <= now)
{
/* Backtrack and get the rule number and delete it.
*/
rn_start = ndx;
while(--rn_start > ipt_output_buf)
{
if(*rn_start == '\n')
break;
}
if(*rn_start != '\n')
{
/* This should not happen. But if it does, complain,
* decrement the active rule value, and go on.
*/
log_msg(LOG_ERR,
"Rule parse error while finding rule line start in chain %i",
cpos);
if (ch[cpos].active_rules > 0)
ch[cpos].active_rules--;
break;
}
rn_start++;
rn_end = strchr(rn_start, ' ');
if(rn_end == NULL)
{
/* This should not happen. But if it does, complain,
* decrement the active rule value, and go on.
*/
log_msg(LOG_ERR,
"Rule parse error while finding rule number in chain %i",
cpos);
if (ch[cpos].active_rules > 0)
ch[cpos].active_rules--;
break;
}
strlcpy(rule_num_str, rn_start, (rn_end - rn_start)+1);
rule_num = strtol_wrapper(rule_num_str, rn_offset, RCHK_MAX_IPT_RULE_NUM,
NO_EXIT_UPON_ERR, &is_err);
if(is_err != FKO_SUCCESS)
{
log_msg(LOG_ERR,
"Rule parse error while finding rule number in chain %i",
cpos);
if (ch[cpos].active_rules > 0)
ch[cpos].active_rules--;
break;
}
zero_cmd_buffers();
snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_DEL_RULE_ARGS,
opts->fw_config->fw_command,
ch[cpos].table,
ch[cpos].to_chain,
rule_num - rn_offset /* account for position of previously
deleted rule with rn_offset */
);
res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE,
WANT_STDERR, NO_TIMEOUT, &pid_status, opts);
chop_newline(err_buf);
log_msg(LOG_DEBUG, "check_firewall_rules() CMD: '%s' (res: %d, err: %s)",
cmd_buf, res, err_buf);
if(EXTCMD_IS_SUCCESS(res))
{
log_msg(LOG_INFO, "Removed rule %s from %s with expire time of %u",
rule_num_str, ch[cpos].to_chain, rule_exp
);
rn_offset++;
if (ch[cpos].active_rules > 0)
ch[cpos].active_rules--;
}
else
log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
}
else
{
/* Track the minimum future rule expire time.
*/
if(rule_exp > now)
min_exp = (min_exp < rule_exp) ? min_exp : rule_exp;
}
/* Push our tracking index forward beyond (just processed) _exp_
* string so we can continue to the next rule in the list.
*/
ndx = strstr(tmp_mark, EXPIRE_COMMENT_PREFIX);
}
return;
}
/* Iterate over the configure firewall access chains and purge expired
* firewall rules.
*/
void
check_firewall_rules(const fko_srv_options_t * const opts)
check_firewall_rules(const fko_srv_options_t * const opts,
const int chk_rm_all)
{
char exp_str[12] = {0};
char rule_num_str[6] = {0};
char *ndx, *rn_start, *rn_end, *tmp_mark;
char *ndx;
char ipt_output_buf[STANDARD_CMD_OUT_BUFSIZE] = {0};
int i, res, rn_offset, rule_num, is_err;
time_t now, rule_exp, min_exp = 0;
int i, res;
time_t now, min_exp = 0;
struct fw_chain *ch = opts->fw_config->chain;
@ -1459,16 +1590,21 @@ check_firewall_rules(const fko_srv_options_t * const opts)
/* If there are no active rules or we have not yet
* reached our expected next expire time, continue.
*/
if(ch[i].active_rules == 0 || ch[i].next_expire > now)
if(!chk_rm_all && (ch[i].active_rules == 0 || ch[i].next_expire > now))
continue;
if(ch[i].table[0] == '\0' || ch[i].to_chain[i] == '\0')
continue;
zero_cmd_buffers();
memset(ipt_output_buf, 0x0, STANDARD_CMD_OUT_BUFSIZE);
rn_offset = 0;
/* There should be a rule to delete. Get the current list of
* rules for this chain and delete the ones that are expired.
/* Get the current list of rules for this chain and delete
* any that have expired. Note that chk_rm_all puts us in
* garbage collection mode, and allows any rules that have
* been manually added (potentially by a program separate
* from fwknopd) to take advantage of fwknopd's timeout
* mechanism.
*/
snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_LIST_RULES_ARGS,
opts->fw_config->fw_command,
@ -1480,12 +1616,14 @@ check_firewall_rules(const fko_srv_options_t * const opts)
WANT_STDERR, NO_TIMEOUT, &pid_status, opts);
chop_newline(ipt_output_buf);
log_msg(LOG_DEBUG, "check_firewall_rules() CMD: '%s' (res: %d, ipt_output_buf: %s)",
log_msg(LOG_DEBUG,
"check_firewall_rules() CMD: '%s' (res: %d, ipt_output_buf: %s)",
cmd_buf, res, ipt_output_buf);
if(!EXTCMD_IS_SUCCESS(res))
{
log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, ipt_output_buf);
log_msg(LOG_ERR,
"Error %i from cmd:'%s': %s", res, cmd_buf, ipt_output_buf);
continue;
}
@ -1495,9 +1633,9 @@ check_firewall_rules(const fko_srv_options_t * const opts)
ndx = strstr(ipt_output_buf, EXPIRE_COMMENT_PREFIX);
if(ndx == NULL)
{
/* we did not find an expected rule.
/* we did not find a candidate rule to expire
*/
log_msg(LOG_ERR,
log_msg(LOG_DEBUG,
"Did not find expire comment in rules list %i", i);
if (ch[i].active_rules > 0)
@ -1506,121 +1644,7 @@ check_firewall_rules(const fko_srv_options_t * const opts)
continue;
}
/* walk the list and process rules as needed.
*/
while (ndx != NULL) {
/* Jump forward and extract the timestamp
*/
ndx += strlen(EXPIRE_COMMENT_PREFIX);
/* remember this spot for when we look for the next
* rule.
*/
tmp_mark = ndx;
strlcpy(exp_str, ndx, sizeof(exp_str));
rule_exp = (time_t)atoll(exp_str);
if(rule_exp <= now)
{
/* Backtrack and get the rule number and delete it.
*/
rn_start = ndx;
while(--rn_start > ipt_output_buf)
{
if(*rn_start == '\n')
break;
}
if(*rn_start != '\n')
{
/* This should not happen. But if it does, complain,
* decrement the active rule value, and go on.
*/
log_msg(LOG_ERR,
"Rule parse error while finding rule line start in chain %i", i);
if (ch[i].active_rules > 0)
ch[i].active_rules--;
break;
}
rn_start++;
rn_end = strchr(rn_start, ' ');
if(rn_end == NULL)
{
/* This should not happen. But if it does, complain,
* decrement the active rule value, and go on.
*/
log_msg(LOG_ERR,
"Rule parse error while finding rule number in chain %i", i);
if (ch[i].active_rules > 0)
ch[i].active_rules--;
break;
}
strlcpy(rule_num_str, rn_start, (rn_end - rn_start)+1);
rule_num = strtol_wrapper(rule_num_str, rn_offset, RCHK_MAX_IPT_RULE_NUM,
NO_EXIT_UPON_ERR, &is_err);
if(is_err != FKO_SUCCESS)
{
log_msg(LOG_ERR,
"Rule parse error while finding rule number in chain %i", i);
if (ch[i].active_rules > 0)
ch[i].active_rules--;
break;
}
zero_cmd_buffers();
snprintf(cmd_buf, CMD_BUFSIZE-1, "%s " IPT_DEL_RULE_ARGS,
opts->fw_config->fw_command,
ch[i].table,
ch[i].to_chain,
rule_num - rn_offset /* account for previously deleted rule */
);
res = run_extcmd(cmd_buf, err_buf, CMD_BUFSIZE,
WANT_STDERR, NO_TIMEOUT, &pid_status, opts);
chop_newline(err_buf);
log_msg(LOG_DEBUG, "check_firewall_rules() CMD: '%s' (res: %d, err: %s)",
cmd_buf, res, err_buf);
if(EXTCMD_IS_SUCCESS(res))
{
log_msg(LOG_INFO, "Removed rule %s from %s with expire time of %u",
rule_num_str, ch[i].to_chain, rule_exp
);
rn_offset++;
if (ch[i].active_rules > 0)
ch[i].active_rules--;
}
else
log_msg(LOG_ERR, "Error %i from cmd:'%s': %s", res, cmd_buf, err_buf);
}
else
{
/* Track the minimum future rule expire time.
*/
if(rule_exp > now)
min_exp = (min_exp < rule_exp) ? min_exp : rule_exp;
}
/* Push our tracking index forward beyond (just processed) _exp_
* string so we can continue to the next rule in the list.
*/
ndx = strstr(tmp_mark, EXPIRE_COMMENT_PREFIX);
}
rm_expired_rules(opts, ipt_output_buf, ndx, ch, i, now);
/* Set the next pending expire time accordingly. 0 if there are no
* more rules, or whatever the next expected (min_exp) time will be.
@ -1630,6 +1654,7 @@ check_firewall_rules(const fko_srv_options_t * const opts)
else if(min_exp)
ch[i].next_expire = min_exp;
}
return;
}
int

View File

@ -91,6 +91,7 @@
#define DEF_ENABLE_SPA_PACKET_AGING "Y"
#define DEF_MAX_SPA_PACKET_AGE "120"
#define DEF_ENABLE_DIGEST_PERSISTENCE "Y"
#define DEF_RULES_CHECK_CTR 10
#define DEF_MAX_SNIFF_BYTES "1500"
#define DEF_GPG_HOME_DIR "/root/.gnupg"
#ifdef GPG_EXE
@ -646,6 +647,12 @@ typedef struct fko_srv_options
*/
struct fw_config *fw_config;
/* Rule checking counter - this is for garbage cleanup mode to remove
* any rules with an expired timer (even those that may have been
* added by a third-party program).
*/
unsigned int check_rules_ctr;
/* Set to 1 when messages have to go through syslog, 0 otherwise */
unsigned char syslog_enable;

View File

@ -67,6 +67,7 @@ pcap_capture(fko_srv_options_t *opts)
int pcap_dispatch_count;
int max_sniff_bytes;
int is_err;
int chk_rm_all = 0;
pid_t child_pid;
#if FIREWALL_IPFW
@ -312,7 +313,16 @@ pcap_capture(fko_srv_options_t *opts)
/* Check for any expired firewall rules and deal with them.
*/
if(!opts->test)
check_firewall_rules(opts);
{
opts->check_rules_ctr++;
if(opts->check_rules_ctr % DEF_RULES_CHECK_CTR == 0)
{
chk_rm_all = 1;
opts->check_rules_ctr = 0;
}
check_firewall_rules(opts, chk_rm_all);
chk_rm_all = 0;
}
#if FIREWALL_IPFW
/* Purge expired rules that no longer have any corresponding

View File

@ -53,7 +53,7 @@ int
run_udp_server(fko_srv_options_t *opts)
{
int s_sock, sfd_flags, selval, pkt_len;
int is_err, s_timeout, rv=1;
int is_err, s_timeout, rv=1, chk_rm_all=0;
fd_set sfd_set;
struct sockaddr_in saddr, caddr;
struct timeval tv;
@ -148,7 +148,16 @@ run_udp_server(fko_srv_options_t *opts)
/* Check for any expired firewall rules and deal with them.
*/
if(!opts->test)
check_firewall_rules(opts);
{
opts->check_rules_ctr++;
if(opts->check_rules_ctr % DEF_RULES_CHECK_CTR == 0)
{
chk_rm_all = 1;
opts->check_rules_ctr = 0;
}
check_firewall_rules(opts, chk_rm_all);
chk_rm_all = 0;
}
/* Initialize and setup the socket for select.
*/