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
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
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
*/
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.
*/
#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 */
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 */
#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 */
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);
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);
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;
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;
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.
*/
#include <ctype.h>
#include <errno.h>
+#include <import/xxhash.h>
+
#include <common/cfgparse.h>
#include <common/config.h>
#include <common/errors.h>
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.
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")) {
memcpy(((struct sockaddr_in6 *)&s->addr)->sin6_addr.s6_addr, ip, 16);
break;
};
+ srv_set_dyncookie(s);
return 0;
}
char current_addr[INET6_ADDRSTRLEN];
uint16_t current_port, new_port;
struct chunk *msg;
+ int changed = 0;
msg = get_trash_chunk();
chunk_reset(msg);
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)) {
if (port_change_required) {
/* apply new port */
s->svc_port = new_port;
+ changed = 1;
/* prepare message */
chunk_appendf(msg, "port changed from '");
}
out:
+ if (changed)
+ srv_set_dyncookie(s);
if (updater)
chunk_appendf(msg, " by '%s'", updater);
chunk_appendf(msg, "\n");
if (!srv->lastaddr)
continue;
if (srv_apply_lastaddr(srv, &err_code) == 0)
- return return_code;
+ goto out;
return_code |= err_code;
break;
if (!srv->hostname)
continue;
if (srv_set_addr_via_libc(srv, &err_code) == 0)
- return return_code;
+ goto out;
return_code |= err_code;
break;
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;
return_code |= ERR_ALERT | ERR_FATAL;
return return_code;
+out:
+ srv_set_dyncookie(srv);
+ return return_code;
}
/*