MEDIUM: http: add http-request 'add-header' and 'set-header' to build headers
These two new statements allow to pass information extracted from the request
to the server. It's particularly useful for passing SSL information to the
server, but may be used for various other purposes such as combining headers
together to emulate internal variables.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index f1a388e..3c46299 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -2606,22 +2606,54 @@
See also : "option httpchk", "http-check disable-on-404"
-http-request { allow | deny | auth [realm <realm>] }
+http-request { allow | deny | auth [realm <realm>] |
+ add-header <name> <fmt> | set-header <name> <fmt> }
[ { if | unless } <condition> ]
Access control for Layer 7 requests
May be used in sections: defaults | frontend | listen | backend
no | yes | yes | yes
- These set of options allow to fine control access to a
- frontend/listen/backend. Each option may be followed by if/unless and acl.
- First option with matched condition (or option without condition) is final.
- For "deny" a 403 error will be returned, for "allow" normal processing is
- performed, for "auth" a 401/407 error code is returned so the client
- should be asked to enter a username and password.
+ The http-request statement defines a set of rules which apply to layer 7
+ processing. The rules are evaluated in their declaration order when they are
+ met in a frontend, listen or backend section. Any rule may optionally be
+ followed by an ACL-based condition, in which case it will only be evaluated
+ if the condition is true.
- There is no fixed limit to the number of http-request statements per
- instance.
+ The first keyword is the rule's action. Currently supported actions include :
+ - "allow" : this stops the evaluation of the rules and lets the request
+ pass the check. No further "http-request" rules are evaluated.
+
+ - "deny" : this stops the evaluation of the rules and immediately rejects
+ the request and emits an HTTP 403 error. No further "http-request" rules
+ are evaluated.
+
+ - "auth" : this stops the evaluation of the rules and immediately responds
+ with an HTTP 401 or 407 error code to invite the user to present a valid
+ user name and password. No further "http-request" rules are evaluated. An
+ optional "realm" parameter is supported, it sets the authentication realm
+ that is returned with the response (typically the application's name).
+
+ - "add-header" appends an HTTP header field whose name is specified in
+ <name> and whose value is defined by <fmt> which follows the log-format
+ rules (see Custom Log Format in section 8.2.4). This is particularly
+ useful to pass connection-specific information to the server (eg: the
+ client's SSL certificate), or to combine several headers into one. This
+ rule is not final, so it is possible to add other similar rules. Note
+ that header addition is performed immediately, so one rule might reuse
+ the resulting header from a previous rule.
+
+ - "set-header" does the same as "add-header" except that the header name
+ is first removed if it existed. This is useful when passing security
+ information to the server, where the header must not be manipulated by
+ external users.
+
+ There is no limit to the number of http-request statements per instance.
+
+ It is important to know that http-request rules are processed very early in
+ the HTTP processing, just after "block" rules and before "reqdel" or "reqrep"
+ rules. That way, headers added by "add-header"/"set-header" are visible by
+ almost all further ACL rules.
Example:
acl nagios src 192.168.129.3
@@ -2635,9 +2667,19 @@
Example:
acl auth_ok http_auth_group(L1) G1
-
http-request auth unless auth_ok
+ Example:
+ http-request set-header X-Haproxy-Current-Date %T
+ http-request set-header X-SSL %[ssl_fc]
+ http-request set-header X-SSL-Session_ID %[ssl_fc_session_id]
+ http-request set-header X-SSL-Client-Verify %[ssl_c_verify]
+ http-request set-header X-SSL-Client-DN %{+Q}[ssl_c_s_dn]
+ http-request set-header X-SSL-Client-CN %{+Q}[ssl_c_s_dn(cn)]
+ http-request set-header X-SSL-Issuer %{+Q}[ssl_c_i_dn]
+ http-request set-header X-SSL-Client-NotBefore %{+Q}[ssl_c_notbefore]
+ http-request set-header X-SSL-Client-NotAfter %{+Q}[ssl_c_notafter]
+
See also : "stats http-request", section 3.4 about userlists and section 7
about ACL usage.
diff --git a/include/types/proto_http.h b/include/types/proto_http.h
index c6efc55..7c5a42a 100644
--- a/include/types/proto_http.h
+++ b/include/types/proto_http.h
@@ -240,8 +240,10 @@
HTTP_REQ_ACT_UNKNOWN = 0,
HTTP_REQ_ACT_ALLOW,
HTTP_REQ_ACT_DENY,
- HTTP_REQ_ACT_HTTP_AUTH,
- HTTP_REQ_ACT_MAX
+ HTTP_REQ_ACT_AUTH,
+ HTTP_REQ_ACT_ADD_HDR,
+ HTTP_REQ_ACT_SET_HDR,
+ HTTP_REQ_ACT_MAX /* must always be last */
};
/*
@@ -347,6 +349,11 @@
struct {
char *realm;
} auth; /* arg used by "auth" */
+ struct {
+ char *name; /* header name */
+ int name_len; /* header name's length */
+ struct list fmt; /* log-format compatible expression */
+ } hdr_add; /* args used by "add-header" and "set-header" */
} arg; /* arguments used by some actions */
};
diff --git a/src/cfgparse.c b/src/cfgparse.c
index f7dcf06..afcf60e 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -2585,8 +2585,12 @@
goto out;
}
- if (!LIST_ISEMPTY(&curproxy->http_req_rules) && !LIST_PREV(&curproxy->http_req_rules, struct http_req_rule *, list)->cond) {
- Warning("parsing [%s:%d]: previous '%s' action has no condition attached, further entries are NOOP.\n",
+ if (!LIST_ISEMPTY(&curproxy->http_req_rules) &&
+ !LIST_PREV(&curproxy->http_req_rules, struct http_req_rule *, list)->cond &&
+ (LIST_PREV(&curproxy->http_req_rules, struct http_req_rule *, list)->action == HTTP_REQ_ACT_ALLOW ||
+ LIST_PREV(&curproxy->http_req_rules, struct http_req_rule *, list)->action == HTTP_REQ_ACT_DENY ||
+ LIST_PREV(&curproxy->http_req_rules, struct http_req_rule *, list)->action == HTTP_REQ_ACT_AUTH)) {
+ Warning("parsing [%s:%d]: previous '%s' action is final and has no condition attached, further entries are NOOP.\n",
file, linenum, args[0]);
err_code |= ERR_WARN;
}
diff --git a/src/proto_http.c b/src/proto_http.c
index b1039d6..c715828 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -3061,13 +3061,16 @@
return 1;
}
-/* returns a pointer to the first rule which forbids access (deny or http_auth),
- * or NULL if everything's OK.
+/* Executes the http-request rules <rules> for session <s>, proxy <px> and
+ * transaction <txn>. Returns NULL if it executed all rules, or a pointer to
+ * the last rule if it had to stop before the end (auth, deny, allow). It may
+ * set the TX_CLDENY on txn->flags if it encounters a deny rule.
*/
-static inline struct http_req_rule *
+static struct http_req_rule *
http_check_access_rule(struct proxy *px, struct list *rules, struct session *s, struct http_txn *txn)
{
struct http_req_rule *rule;
+ struct hdr_ctx ctx;
list_for_each_entry(rule, rules, list) {
int ret = 1;
@@ -3085,13 +3088,36 @@
}
if (ret) {
- if (rule->action == HTTP_REQ_ACT_ALLOW)
- return NULL; /* no problem */
- else
- return rule; /* most likely a deny or auth rule */
+ switch (rule->action) {
+ case HTTP_REQ_ACT_ALLOW:
+ return rule;
+ case HTTP_REQ_ACT_DENY:
+ txn->flags |= TX_CLDENY;
+ return rule;
+ case HTTP_REQ_ACT_AUTH:
+ return rule;
+ case HTTP_REQ_ACT_SET_HDR:
+ ctx.idx = 0;
+ /* remove all occurrences of the header */
+ while (http_find_header2(rule->arg.hdr_add.name, rule->arg.hdr_add.name_len,
+ txn->req.chn->buf->p, &txn->hdr_idx, &ctx)) {
+ http_remove_header2(&txn->req, &txn->hdr_idx, &ctx);
+ }
+ /* now fall through to header addition */
+
+ case HTTP_REQ_ACT_ADD_HDR:
+ chunk_printf(&trash, "%s: ", rule->arg.hdr_add.name);
+ memcpy(trash.str, rule->arg.hdr_add.name, rule->arg.hdr_add.name_len);
+ trash.len = rule->arg.hdr_add.name_len;
+ trash.str[trash.len++] = ':';
+ trash.str[trash.len++] = ' ';
+ trash.len += build_logline(s, trash.str + trash.len, trash.size - trash.len, &rule->arg.hdr_add.fmt);
+ http_header_add_tail2(&txn->req, &txn->hdr_idx, trash.str, trash.len);
+ break;
+ }
}
}
- return NULL;
+ return rule;
}
/* This stream analyser runs all HTTP request processing which is common to
@@ -3163,7 +3189,7 @@
do_stats = 0;
/* return a 403 if either rule has blocked */
- if (http_req_last_rule && http_req_last_rule->action == HTTP_REQ_ACT_DENY) {
+ if (txn->flags & TX_CLDENY) {
txn->status = 403;
s->logs.tv_request = now;
stream_int_retnclose(req->prod, http_error_message(s, HTTP_ERR_403));
@@ -3267,7 +3293,7 @@
/* we can be blocked here because the request needs to be authenticated,
* either to pass or to access stats.
*/
- if (http_req_last_rule && http_req_last_rule->action == HTTP_REQ_ACT_HTTP_AUTH) {
+ if (http_req_last_rule && http_req_last_rule->action == HTTP_REQ_ACT_AUTH) {
char *realm = http_req_last_rule->arg.auth.realm;
if (!realm)
@@ -7970,7 +7996,7 @@
list_for_each_entry_safe(pr, tr, r, list) {
LIST_DEL(&pr->list);
- if (pr->action == HTTP_REQ_ACT_HTTP_AUTH)
+ if (pr->action == HTTP_REQ_ACT_AUTH)
free(pr->arg.auth.realm);
free(pr);
@@ -7995,7 +8021,7 @@
rule->action = HTTP_REQ_ACT_DENY;
cur_arg = 1;
} else if (!strcmp(args[0], "auth")) {
- rule->action = HTTP_REQ_ACT_HTTP_AUTH;
+ rule->action = HTTP_REQ_ACT_AUTH;
cur_arg = 1;
while(*args[cur_arg]) {
@@ -8006,8 +8032,23 @@
} else
break;
}
+ } else if (strcmp(args[0], "add-header") == 0 || strcmp(args[0], "set-header") == 0) {
+ rule->action = *args[0] == 'a' ? HTTP_REQ_ACT_ADD_HDR : HTTP_REQ_ACT_SET_HDR;
+ cur_arg = 1;
+
+ if (!*args[cur_arg] || !*args[cur_arg+1] || *args[cur_arg+2]) {
+ Alert("parsing [%s:%d]: 'http-request %s' expects exactly 2 arguments.\n",
+ file, linenum, args[0]);
+ return NULL;
+ }
+
+ rule->arg.hdr_add.name = strdup(args[cur_arg]);
+ rule->arg.hdr_add.name_len = strlen(rule->arg.hdr_add.name);
+ LIST_INIT(&rule->arg.hdr_add.fmt);
+ parse_logformat_string(args[cur_arg + 1], proxy, &rule->arg.hdr_add.fmt, PR_MODE_HTTP);
+ cur_arg += 2;
} else {
- Alert("parsing [%s:%d]: 'http-request' expects 'allow', 'deny', 'auth', but got '%s'%s.\n",
+ Alert("parsing [%s:%d]: 'http-request' expects 'allow', 'deny', 'auth', 'add-header', 'set-header', but got '%s'%s.\n",
file, linenum, args[0], *args[0] ? "" : " (missing argument)");
return NULL;
}