]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: server: Add dynamic session cookies.
authorOlivier Houchard <ohouchard@haproxy.com>
Tue, 14 Mar 2017 19:01:29 +0000 (20:01 +0100)
committerWilly Tarreau <w@1wt.eu>
Wed, 15 Mar 2017 10:37:30 +0000 (11:37 +0100)
This adds a new "dynamic" keyword for the cookie option. If set, a cookie
will be generated for each server (assuming one isn't already provided on
the "server" line), from the IP of the server, the TCP port, and a secret
key provided. To provide the secret key, a new keyword as been added,
"dynamic-cookie-key", for backends.

Example :
backend bk_web
  balance roundrobin
  dynamic-cookie-key "bla"
  cookie WEBSRV insert dynamic
  server s1 127.0.0.1:80 check
  server s2 192.168.56.1:80 check

This is a first step to be able to dynamically add and remove servers,
without modifying the configuration file, and still have all the load
balancers redirect the traffic to the right server.

Provide a way to generate session cookies, based on the IP address of the
server, the TCP port, and a secret key provided.

doc/configuration.txt
include/proto/server.h
include/types/proxy.h
include/types/server.h
src/cfgparse.c
src/server.c

index a505f70adaad1ade4e0190125b632e37f86527af..228e8132e80926d87b3aaff5923dd0f74b3a7b1b 100644 (file)
@@ -2771,6 +2771,7 @@ contimeout <timeout> (deprecated)
 cookie <name> [ rewrite | insert | prefix ] [ indirect ] [ nocache ]
               [ postonly ] [ preserve ] [ httponly ] [ secure ]
               [ domain <domain> ]* [ maxidle <idle> ] [ maxlife <life> ]
+              [ dynamic ]
   Enable cookie-based persistence in a backend.
   May be used in sections :   defaults | frontend | listen | backend
                                  yes   |    no    |   yes  |   yes
@@ -2922,6 +2923,13 @@ cookie <name> [ rewrite | insert | prefix ] [ indirect ] [ nocache ]
               is stronger than the maxidle method in that it forces a
               redispatch after some absolute delay.
 
+    dynamic   Activate dynamic cookies. When used, a session cookie is
+              dynamically created for each server, based on the IP and port
+              of the server, and a secret key, specified in the
+              "dynamic-cookie-key" backend directive.
+              The cookie will be regenerated each time the IP address change,
+              and is only generated for IPv4/IPv6.
+
   There can be only one persistence cookie per HTTP backend, and it can be
   declared in a defaults section. The value of the cookie will be the value
   indicated after the "cookie" keyword in a "server" statement. If no cookie
@@ -3042,6 +3050,19 @@ dispatch <address>:<port>
   See also : "server"
 
 
+dynamic-cookie-key <string>
+  Set the dynamic cookie secret key for a backend.
+  May be used in sections :   defaults | frontend | listen | backend
+                                 yes   |    no    |   yes  |   yes
+  Arguments : The secret key to be used.
+
+  When dynamic cookies are enabled (see the "dynamic" directive for cookie),
+  a dynamic cookie is created for each server (unless one is explicitely
+  specified on the "server" line), using a hash of the IP address of the
+  server, the TCP port, and the secret key.
+  That way, we can ensure session persistence accross multiple load-balancers,
+  even if servers are dynamically added or removed.
+
 enabled
   Enable a proxy, frontend or backend.
   May be used in sections :   defaults | frontend | listen | backend
index 7c9574ee42897f6cf3160d7bc5368eca177329cd..6e3ccf39fe39d9799ba80ed4557a3509140691da 100644 (file)
@@ -204,6 +204,11 @@ void srv_set_admin_flag(struct server *s, enum srv_admin mode, const char *cause
  */
 void srv_clr_admin_flag(struct server *s, enum srv_admin mode);
 
+/* Calculates the dynamic persitent cookie for a server, if a secret key has
+ * been provided.
+ */
+void srv_set_dyncookie(struct server *s);
+
 /* Puts server <s> into maintenance mode, and propagate that status down to all
  * tracking servers.
  */
index 3f848a0dc7bce53aa78147e27a8f31f337c28c7c..5306a3b6c5552f5c97a8a914b82369d85f2b8cfa 100644 (file)
@@ -189,6 +189,7 @@ enum PR_SRV_STATE_FILE {
 #define PR_CK_PSV       0x00000040      /* cookie ... preserve */
 #define PR_CK_HTTPONLY  0x00000080      /* emit the "HttpOnly" attribute */
 #define PR_CK_SECURE    0x00000100      /* emit the "Secure" attribute */
+#define PR_CK_DYNAMIC   0x00000200     /* create dynamic cookies for each server */
 
 /* bits for sticking rules */
 #define STK_IS_MATCH   0x00000001      /* match on request fetch */
@@ -282,6 +283,7 @@ struct proxy {
        char *cookie_domain;                    /* domain used to insert the cookie */
        char *cookie_name;                      /* name of the cookie to look for */
        int  cookie_len;                        /* strlen(cookie_name), computed only once */
+       char *dyncookie_key;                    /* Secret key used to generate dynamic persistent cookies */
        unsigned int cookie_maxidle;            /* max idle time for this cookie */
        unsigned int cookie_maxlife;            /* max life time for this cookie */
        int  rdp_cookie_len;                    /* strlen(rdp_cookie_name), computed only once */
index 51b7e535c6aeee05cdd1c15c938bc50e9dc12f65..feede6d9757fe7e7d7e88adf5adb30f53b06a9a9 100644 (file)
@@ -117,6 +117,7 @@ enum srv_initaddr {
 #define SRV_F_CHECKADDR    0x0020        /* this server has a check addr configured */
 #define SRV_F_CHECKPORT    0x0040        /* this server has a check port configured */
 #define SRV_F_AGENTADDR    0x0080        /* this server has a agent addr configured */
+#define SRV_F_COOKIESET    0x0100        /* this server has a cookie configured, so don't generate dynamic cookies */
 
 /* configured server options for send-proxy (server->pp_opts) */
 #define SRV_PP_V1          0x0001        /* proxy protocol version 1 */
index 9d04f0284a42d5ef447e19f8f01b70412a7762ed..5a09589735ea3abf9f2e4893efb9cf87fa62ff89 100644 (file)
@@ -2632,6 +2632,9 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
                        if (defproxy.cookie_name)
                                curproxy->cookie_name = strdup(defproxy.cookie_name);
                        curproxy->cookie_len = defproxy.cookie_len;
+
+                       if (defproxy.dyncookie_key)
+                               curproxy->dyncookie_key = strdup(defproxy.dyncookie_key);
                        if (defproxy.cookie_domain)
                                curproxy->cookie_domain = strdup(defproxy.cookie_domain);
 
@@ -2793,6 +2796,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
                free(defproxy.check_path);
                free(defproxy.cookie_name);
                free(defproxy.rdp_cookie_name);
+               free(defproxy.dyncookie_key);
                free(defproxy.cookie_domain);
                free(defproxy.url_param_name);
                free(defproxy.hh_name);
@@ -3159,6 +3163,20 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
                        goto out;
                }
        }
+       else if (!strcmp(args[0], "dynamic-cookie-key")) { /* Dynamic cookies secret key */
+
+               if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL))
+                       err_code |= ERR_WARN;
+
+               if (*(args[1]) == 0) {
+                       Alert("parsing [%s:%d] : '%s' expects <secret_key> as argument.\n",
+                                       file, linenum, args[0]);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto out;
+               }
+               free(curproxy->dyncookie_key);
+               curproxy->dyncookie_key = strdup(args[1]);
+       }
        else if (!strcmp(args[0], "cookie")) {  /* cookie name */
                int cur_arg;
 
@@ -3292,8 +3310,15 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
                                curproxy->cookie_maxlife = maxlife;
                                cur_arg++;
                        }
+                       else if (!strcmp(args[cur_arg], "dynamic")) { /* Dynamic persitent cookies secret key */
+
+                               if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[cur_arg], NULL))
+                                       err_code |= ERR_WARN;
+                               curproxy->ck_opts |= PR_CK_DYNAMIC;
+                       }
+
                        else {
-                               Alert("parsing [%s:%d] : '%s' supports 'rewrite', 'insert', 'prefix', 'indirect', 'nocache', 'postonly', 'domain', 'maxidle, and 'maxlife' options.\n",
+                               Alert("parsing [%s:%d] : '%s' supports 'rewrite', 'insert', 'prefix', 'indirect', 'nocache', 'postonly', 'domain', 'maxidle', 'dynamic' and 'maxlife' options.\n",
                                      file, linenum, args[0]);
                                err_code |= ERR_ALERT | ERR_FATAL;
                                goto out;
@@ -8442,6 +8467,21 @@ out_uri_auth_compat:
                        newsrv = newsrv->next;
                }
 
+               /*
+                * Try to generate dynamic cookies for servers now.
+                * It couldn't be done earlier, since at the time we parsed
+                * the server line, we may not have known yet that we
+                * should use dynamic cookies, or the secret key may not
+                * have been provided yet.
+                */
+               if (curproxy->ck_opts & PR_CK_DYNAMIC) {
+                       newsrv = curproxy->srv;
+                       while (newsrv != NULL) {
+                               srv_set_dyncookie(newsrv);
+                               newsrv = newsrv->next;
+                       }
+
+               }
                /* We have to initialize the server lookup mechanism depending
                 * on what LB algorithm was choosen.
                 */
index 9cc02f74f1df73dd418a578436191a5b60e9515b..4e03e50a4695ecb3117551d5f4514222783f18c7 100644 (file)
@@ -14,6 +14,8 @@
 #include <ctype.h>
 #include <errno.h>
 
+#include <import/xxhash.h>
+
 #include <common/cfgparse.h>
 #include <common/config.h>
 #include <common/errors.h>
@@ -77,6 +79,73 @@ int srv_getinter(const struct check *check)
        return (check->fastinter)?(check->fastinter):(check->inter);
 }
 
+void srv_set_dyncookie(struct server *s)
+{
+       struct proxy *p = s->proxy;
+       struct server *tmpserv;
+       char *tmpbuf;
+       unsigned long long hash_value;
+       size_t key_len = strlen(p->dyncookie_key);
+       size_t buffer_len;
+       int addr_len;
+       int port;
+
+       if ((s->flags & SRV_F_COOKIESET) ||
+           !(s->proxy->ck_opts & PR_CK_DYNAMIC) ||
+           s->proxy->dyncookie_key == NULL)
+               return;
+
+       if (s->addr.ss_family != AF_INET &&
+           s->addr.ss_family != AF_INET6)
+               return;
+       /*
+        * Buffer to calculate the cookie value.
+        * The buffer contains the secret key + the server IP address
+        * + the TCP port.
+        */
+       addr_len = (s->addr.ss_family == AF_INET) ? 4 : 16;
+       /*
+        * The TCP port should use only 2 bytes, but is stored in
+        * an unsigned int in struct server, so let's use 4, to be
+        * on the safe side.
+        */
+       buffer_len = key_len + addr_len + 4;
+       tmpbuf = trash.str;
+       memcpy(tmpbuf, p->dyncookie_key, key_len);
+       memcpy(&(tmpbuf[key_len]),
+           s->addr.ss_family == AF_INET ?
+           (void *)&((struct sockaddr_in *)&s->addr)->sin_addr.s_addr :
+           (void *)&(((struct sockaddr_in6 *)&s->addr)->sin6_addr.s6_addr),
+           addr_len);
+       /*
+        * Make sure it's the same across all the load balancers,
+        * no matter their endianness.
+        */
+       port = htonl(s->svc_port);
+       memcpy(&tmpbuf[key_len + addr_len], &port, 4);
+       hash_value = XXH64(tmpbuf, buffer_len, 0);
+       memprintf(&s->cookie, "%016llx", hash_value);
+       if (!s->cookie)
+               return;
+       s->cklen = 16;
+       /*
+        * Check that we did not get a hash collision.
+        * Unlikely, but it can happen.
+        */
+       for (p = proxy; p != NULL; p = p->next)
+               for (tmpserv = proxy->srv; tmpserv != NULL;
+                   tmpserv = tmpserv->next) {
+                       if (tmpserv == s)
+                               continue;
+                       if (tmpserv->cookie &&
+                           strcmp(tmpserv->cookie, s->cookie) == 0) {
+                               Warning("We generated two equal cookies for two different servers.\n"
+                                   "Please change the secret key for '%s'.\n",
+                                   s->proxy->id);
+                       }
+               }
+}
+
 /*
  * Registers the server keyword list <kwl> as a list of valid keywords for next
  * parsing sessions.
@@ -1175,6 +1244,7 @@ int parse_server(const char *file, int linenum, char **args, struct proxy *curpr
                        else if (!defsrv && !strcmp(args[cur_arg], "cookie")) {
                                newsrv->cookie = strdup(args[cur_arg + 1]);
                                newsrv->cklen = strlen(args[cur_arg + 1]);
+                               newsrv->flags |= SRV_F_COOKIESET;
                                cur_arg += 2;
                        }
                        else if (!strcmp(args[cur_arg], "init-addr")) {
@@ -2730,6 +2800,7 @@ int update_server_addr(struct server *s, void *ip, int ip_sin_family, const char
                memcpy(((struct sockaddr_in6 *)&s->addr)->sin6_addr.s6_addr, ip, 16);
                break;
        };
+       srv_set_dyncookie(s);
 
        return 0;
 }
@@ -2758,6 +2829,7 @@ const char *update_server_addr_port(struct server *s, const char *addr, const ch
        char current_addr[INET6_ADDRSTRLEN];
        uint16_t current_port, new_port;
        struct chunk *msg;
+       int changed = 0;
 
        msg = get_trash_chunk();
        chunk_reset(msg);
@@ -2792,6 +2864,7 @@ const char *update_server_addr_port(struct server *s, const char *addr, const ch
                        goto port;
                }
                ipcpy(&sa, &s->addr);
+               changed = 1;
 
                /* we also need to update check's ADDR only if it uses the server's one */
                if ((s->check.state & CHK_ST_CONFIGURED) && (s->flags & SRV_F_CHECKADDR)) {
@@ -2859,6 +2932,7 @@ const char *update_server_addr_port(struct server *s, const char *addr, const ch
                if (port_change_required) {
                        /* apply new port */
                        s->svc_port = new_port;
+                       changed = 1;
 
                        /* prepare message */
                        chunk_appendf(msg, "port changed from '");
@@ -2893,6 +2967,8 @@ const char *update_server_addr_port(struct server *s, const char *addr, const ch
        }
 
 out:
+       if (changed)
+               srv_set_dyncookie(s);
        if (updater)
                chunk_appendf(msg, " by '%s'", updater);
        chunk_appendf(msg, "\n");
@@ -3279,7 +3355,7 @@ static int srv_iterate_initaddr(struct server *srv)
                        if (!srv->lastaddr)
                                continue;
                        if (srv_apply_lastaddr(srv, &err_code) == 0)
-                               return return_code;
+                               goto out;
                        return_code |= err_code;
                        break;
 
@@ -3287,7 +3363,7 @@ static int srv_iterate_initaddr(struct server *srv)
                        if (!srv->hostname)
                                continue;
                        if (srv_set_addr_via_libc(srv, &err_code) == 0)
-                               return return_code;
+                               goto out;
                        return_code |= err_code;
                        break;
 
@@ -3305,7 +3381,7 @@ static int srv_iterate_initaddr(struct server *srv)
                                Warning("parsing [%s:%d] : 'server %s' : could not resolve address '%s', falling back to configured address.\n",
                                        srv->conf.file, srv->conf.line, srv->id, srv->hostname);
                        }
-                       return return_code;
+                       goto out;
 
                default: /* unhandled method */
                        break;
@@ -3323,6 +3399,9 @@ static int srv_iterate_initaddr(struct server *srv)
 
        return_code |= ERR_ALERT | ERR_FATAL;
        return return_code;
+out:
+       srv_set_dyncookie(srv);
+       return return_code;
 }
 
 /*