[server] Added FORCE_SNAT to access.conf stanzas.
authorMichael Rash <mbr@cipherdyne.org>
Thu, 5 Dec 2013 02:52:07 +0000 (21:52 -0500)
committerMichael Rash <mbr@cipherdyne.org>
Thu, 5 Dec 2013 02:52:07 +0000 (21:52 -0500)
Added FORCE_SNAT to the access.conf file so that per-access stanza SNAT
criteria can be specified for SPA access.

ChangeLog
doc/fwknopd.man.asciidoc
server/access.c
server/fw_util_iptables.c
server/fwknopd_common.h
test/test-fwknop.pl
test/tests/rijndael_hmac.pl

index 34f9764..3915423 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -29,6 +29,8 @@ fwknop-2.5.2 (//2013):
     - [server] Bug fix for SPA NAT modes on iptables firewalls to ensure that
       custom fwknop chains are re-created if they get deleted out from under
       the running fwknopd instance.
+    - [server] Added FORCE_SNAT to the access.conf file so that per-access
+      stanza SNAT criteria can be specified for SPA access.
     - [test suite] added --gdb-test to allow a previously executed fwknop
       or fwknopd command to be sent through gdb with the same command line
       args as the test suite used.  This is for convenience to rapidly allow
index c39bf50..d1d9da1 100644 (file)
@@ -443,6 +443,16 @@ directive starts a new stanza.
     for each stanza in the access.conf file.  This way, multiple external
     users can each directly access only one internal system per SPA key.
 
+*FORCE_SNAT* '<IP>'::
+    For any valid SPA packet, add an SNAT rule in addition to any DNAT rule
+    created with a corresponding (required) FORCE_NAT variable.  This is
+    analogous to SNAT_TRANSLATE_IP from the '@sysconfdir@/fwknop/fwknopd.conf'' file
+    except that it is per access stanza and overrides any value set with
+    SNAT_TRANSLATE_IP.  This is useful for situations where an incoming NAT'd
+    connection may be otherwise unanswerable due to routing constraints (i.e.
+    the system receiving the SPA authenticated connection has a default route
+    to a different device than the SPA system itself).
+
 *GPG_HOME_DIR* '<path>'::
     Define the path to the GnuPG directory to be used by the *fwknopd*
     server.  If this keyword is not specified within '@sysconfdir@/fwknop/access.conf' then
index 203d2d8..88699f7 100644 (file)
@@ -165,7 +165,6 @@ add_acc_force_nat(fko_srv_options_t *opts, acc_stanza_t *curr_acc, const char *v
 
     if (sscanf(val, "%15s %5u", ip_str, &curr_acc->force_nat_port) != 2)
     {
-
         log_msg(LOG_ERR,
             "[*] Fatal: invalid FORCE_NAT arg '%s', need <IP> <PORT>",
             val
@@ -192,6 +191,32 @@ add_acc_force_nat(fko_srv_options_t *opts, acc_stanza_t *curr_acc, const char *v
 
     return;
 }
+
+static void
+add_acc_force_snat(fko_srv_options_t *opts, acc_stanza_t *curr_acc, const char *val)
+{
+    char      ip_str[MAX_IPV4_STR_LEN] = {0};
+
+    if (sscanf(val, "%15s", ip_str) != 1)
+    {
+        log_msg(LOG_ERR,
+                "[*] Fatal: invalid FORCE_SNAT arg '%s', need <IP>", val);
+        clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE);
+    }
+
+    if(! is_valid_ipv4_addr(ip_str))
+    {
+        log_msg(LOG_ERR,
+            "[*] Fatal: invalid FORCE_NAT IP '%s'", ip_str);
+        clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE);
+    }
+
+    curr_acc->force_snat = 1;
+    add_acc_string(&(curr_acc->force_snat_ip), ip_str);
+
+    return;
+}
+
 #endif
 
 /* Take an IP or Subnet/Mask and convert it to mask for later
@@ -680,6 +705,9 @@ free_acc_stanza_data(acc_stanza_t *acc)
     if(acc->force_nat_ip != NULL)
         free(acc->force_nat_ip);
 
+    if(acc->force_nat_ip != NULL)
+        free(acc->force_snat_ip);
+
     if(acc->key != NULL)
     {
         zero_buf_wrapper(acc->key, acc->key_len);
@@ -955,6 +983,15 @@ acc_data_is_valid(acc_stanza_t * const acc)
         }
     }
 
+    if(acc->force_snat == 1 && acc->force_nat == 0)
+    {
+        log_msg(LOG_ERR,
+                "[*] FORCE_SNAT implies FORCE_NAT must also be used for access stanza source: '%s'",
+                acc->source
+        );
+        return(0);
+    }
+
     if(acc->require_source_address == 0)
     {
         log_msg(LOG_INFO,
@@ -1340,6 +1377,24 @@ parse_access_file(fko_srv_options_t *opts)
             clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE);
 #endif
         }
+        else if(CONF_VAR_IS(var, "FORCE_SNAT"))
+        {
+#if FIREWALL_IPTABLES
+            if(strncasecmp(opts->config[CONF_ENABLE_IPT_FORWARDING], "Y", 1) !=0 )
+            {
+                log_msg(LOG_ERR,
+                    "[*] FORCE_SNAT_NAT requires ENABLE_IPT_FORWARDING to be enabled in fwknopd.conf");
+                fclose(file_ptr);
+                clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE);
+            }
+            add_acc_force_snat(opts, curr_acc, val);
+#else
+            log_msg(LOG_ERR,
+                "[*] FORCE_SNAT not supported.");
+            fclose(file_ptr);
+            clean_exit(opts, NO_FW_CLEANUP, EXIT_FAILURE);
+#endif
+        }
         else
         {
             log_msg(LOG_ERR,
index 8a10304..5460430 100644 (file)
@@ -961,7 +961,7 @@ process_spa_request(const fko_srv_options_t * const opts,
     struct fw_chain * const dnat_chain = &(opts->fw_config->chain[IPT_DNAT_ACCESS]);
     struct fw_chain *snat_chain; /* We assign this later (if we need to). */
 
-    int             res = 0, is_err;
+    int             res = 0, is_err, snat_chain_num = 0;
     time_t          now;
     unsigned int    exp_ts;
 
@@ -1260,29 +1260,27 @@ process_spa_request(const fko_srv_options_t * const opts,
 
         /* If SNAT (or MASQUERADE) is wanted, then we add those rules here as well.
         */
-        if(strncasecmp(opts->config[CONF_ENABLE_IPT_SNAT], "Y", 1) == 0)
+        if(acc->force_snat || strncasecmp(opts->config[CONF_ENABLE_IPT_SNAT], "Y", 1) == 0)
         {
-            /* Setup some parameter depending on whether we are using SNAT
-             * or MASQUERADE.
+            /* Add SNAT or MASQUERADE rules.
             */
-            if((opts->config[CONF_SNAT_TRANSLATE_IP] != NULL)
-                && strncasecmp(opts->config[CONF_SNAT_TRANSLATE_IP], "__CHANGEME__", 10)!=0)
+            if(acc->force_snat && is_valid_ipv4_addr(acc->force_snat_ip))
+            {
+                /* Using static SNAT */
+                snat_chain = &(opts->fw_config->chain[IPT_SNAT_ACCESS]);
+                snprintf(snat_target, SNAT_TARGET_BUFSIZE-1,
+                    "--to-source %s:%i", acc->force_snat_ip, fst_port);
+                snat_chain_num = IPT_SNAT_ACCESS;
+            }
+            else if((opts->config[CONF_SNAT_TRANSLATE_IP] != NULL)
+                && is_valid_ipv4_addr(opts->config[CONF_SNAT_TRANSLATE_IP]))
             {
                 /* Using static SNAT */
                 snat_chain = &(opts->fw_config->chain[IPT_SNAT_ACCESS]);
                 snprintf(snat_target, SNAT_TARGET_BUFSIZE-1,
                     "--to-source %s:%i", opts->config[CONF_SNAT_TRANSLATE_IP],
                     fst_port);
-
-                /* Check to make sure that the jump rules exist for each
-                 * required chain
-                */
-                if(chain_exists(opts, IPT_SNAT_ACCESS) == 0)
-                    create_chain(opts, IPT_SNAT_ACCESS);
-
-                if(jump_rule_exists(opts, IPT_SNAT_ACCESS) == 0)
-                    add_jump_rule(opts, IPT_SNAT_ACCESS);
-
+                snat_chain_num = IPT_SNAT_ACCESS;
             }
             else
             {
@@ -1290,16 +1288,14 @@ process_spa_request(const fko_srv_options_t * const opts,
                 snat_chain = &(opts->fw_config->chain[IPT_MASQUERADE_ACCESS]);
                 snprintf(snat_target, SNAT_TARGET_BUFSIZE-1,
                     "--to-ports %i", fst_port);
+                snat_chain_num = IPT_MASQUERADE_ACCESS;
+            }
 
-                /* Check to make sure that the jump rules exist for each
-                 * required chain
-                */
-                if(chain_exists(opts, IPT_MASQUERADE_ACCESS) == 0)
-                    create_chain(opts, IPT_MASQUERADE_ACCESS);
+            if(chain_exists(opts, snat_chain_num) == 0)
+                create_chain(opts, snat_chain_num);
 
-                if(jump_rule_exists(opts, IPT_MASQUERADE_ACCESS) == 0)
-                    add_jump_rule(opts, IPT_MASQUERADE_ACCESS);
-            }
+            if(jump_rule_exists(opts, snat_chain_num) == 0)
+                add_jump_rule(opts, snat_chain_num);
 
             memset(rule_buf, 0, CMD_BUFSIZE);
 
index 9c2a7d6..b06c849 100644 (file)
@@ -321,10 +321,19 @@ typedef struct acc_stanza
     time_t               access_expire_time;
     int                  expired;
     int                  encryption_mode;
+
+    /* DNAT parameters
+    */
     unsigned char        force_nat;
     char                *force_nat_ip;
     char                *force_nat_proto;
     unsigned int         force_nat_port;
+
+    /* SNAT parameters
+    */
+    unsigned char        force_snat;
+    char                *force_snat_ip;
+
     struct acc_stanza   *next;
 } acc_stanza_t;
 
index 42aef8f..0a6905c 100755 (executable)
@@ -79,6 +79,7 @@ our %cf = (
     'invalid_ipt_input_chain6'     => "$conf_dir/invalid_ipt_input_chain_6_fwknopd.conf",
     'force_nat_access'             => "$conf_dir/force_nat_access.conf",
     'hmac_force_nat_access'        => "$conf_dir/hmac_force_nat_access.conf",
+    'hmac_force_snat_access'       => "$conf_dir/hmac_force_snat_access.conf",
     'cmd_access'                   => "$conf_dir/cmd_access.conf",
     'local_nat'                    => "$conf_dir/local_nat_fwknopd.conf",
     'no_flush_init'                => "$conf_dir/no_flush_init_fwknopd.conf",
@@ -186,6 +187,8 @@ our $fake_ip           = '127.0.0.2';
 our $spoof_ip          = '1.2.3.4';
 our $internal_nat_host = '192.168.1.2';
 our $force_nat_host    = '192.168.1.123';
+our $force_nat_host2   = '123.4.4.4';
+our $force_snat_host   = '33.3.3.3';
 our $default_spa_port  = 62201;
 our $non_std_spa_port  = 12345;
 
index 64eeffa..3d65639 100644 (file)
             $cf{'rc_hmac_b64_key'},
         'fwknopd_cmdline' => "$fwknopdCmd -c $cf{'nat'} -a $cf{'hmac_force_nat_access'} " .
             "-d $default_digest_file -p $default_pid_file $intf_str",
-        'server_positive_output_matches' => [qr/\sto\:$force_nat_host\:22/i],
-        'server_negative_output_matches' => [qr/\sto\:$internal_nat_host\:22/i],
+        'server_positive_output_matches' => [qr/\*\/\sto\:$force_nat_host\:22/i],
+        'server_negative_output_matches' => [qr/\*\/\sto\:$internal_nat_host\:22/i],
         'fw_rule_created' => $NEW_RULE_REQUIRED,
         'fw_rule_removed' => $NEW_RULE_REMOVED,
         'server_conf' => $cf{'nat'},
     {
         'category' => 'Rijndael+HMAC',
         'subcategory' => 'client+server',
+        'detail'   => "force SNAT $force_snat_host (tcp/22)",
+        'function' => \&spa_cycle,
+        'cmdline'  => $default_client_args,
+        'cmdline'  => "$default_client_args_no_get_key --rc-file " .
+            $cf{'rc_hmac_b64_key'},
+        'fwknopd_cmdline' => "$fwknopdCmd -c $cf{'snat'} -a $cf{'hmac_force_snat_access'} " .
+            "-d $default_digest_file -p $default_pid_file $intf_str",
+        'server_positive_output_matches' => [qr/DNAT\s.*\*\/\sto\:$force_nat_host2\:22/i,
+            qr/SNAT\s.*\*\/\sto\:$force_snat_host\:22/],
+        'server_negative_output_matches' => [qr/\*\/\sto\:$internal_nat_host\:22/i,
+            qr/\*\/\sto\:$force_nat_host\:22/i],
+        'fw_rule_created' => $NEW_RULE_REQUIRED,
+        'fw_rule_removed' => $NEW_RULE_REMOVED,
+        'server_conf' => $cf{'snat'},
+        'key_file' => $cf{'rc_hmac_b64_key'},
+    },
+    {
+        'category' => 'Rijndael+HMAC',
+        'subcategory' => 'client+server',
+        'detail'   => "force SNAT $force_snat_host (ipt flush)",
+        'function' => \&spa_cycle,
+        'cmdline'  => $default_client_args,
+        'cmdline'  => "$default_client_args_no_get_key --rc-file " .
+            $cf{'rc_hmac_b64_key'},
+        'fwknopd_cmdline' => "$fwknopdCmd -c $cf{'snat'} -a $cf{'hmac_force_snat_access'} " .
+            "-d $default_digest_file -p $default_pid_file $intf_str",
+        'server_positive_output_matches' => [qr/DNAT\s.*\*\/\sto\:$force_nat_host2\:22/i,
+            qr/SNAT\s.*\*\/\sto\:$force_snat_host\:22/],
+        'server_negative_output_matches' => [qr/\*\/\sto\:$internal_nat_host\:22/i,
+            qr/\*\/\sto\:$force_nat_host\:22/i],
+        'fw_rule_created' => $NEW_RULE_REQUIRED,
+        'fw_rule_removed' => $NEW_RULE_REMOVED,
+        'server_conf' => $cf{'snat'},
+        'key_file' => $cf{'rc_hmac_b64_key'},
+        'iptables_rm_chains_after_server_start' => $YES,
+    },
+    {
+        'category' => 'Rijndael+HMAC',
+        'subcategory' => 'client+server',
         'detail'   => "force NAT (iptables flush)",
         'function' => \&spa_cycle,
         'cmdline'  => $default_client_args,
             $cf{'rc_hmac_b64_key'},
         'fwknopd_cmdline' => "$fwknopdCmd -c $cf{'nat'} -a $cf{'hmac_force_nat_access'} " .
             "-d $default_digest_file -p $default_pid_file $intf_str",
-        'server_positive_output_matches' => [qr/\sto\:$force_nat_host\:22/i],
-        'server_negative_output_matches' => [qr/\sto\:$internal_nat_host\:22/i],
+        'server_positive_output_matches' => [qr/\*\/\sto\:$force_nat_host\:22/i],
+        'server_negative_output_matches' => [qr/\*\/\sto\:$internal_nat_host\:22/i],
         'fw_rule_created' => $NEW_RULE_REQUIRED,
         'fw_rule_removed' => $NEW_RULE_REMOVED,
         'server_conf' => $cf{'nat'},