]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: http: add http-request 'add-header' and 'set-header' to build headers
authorWilly Tarreau <w@1wt.eu>
Mon, 24 Dec 2012 14:45:22 +0000 (15:45 +0100)
committerWilly Tarreau <w@1wt.eu>
Mon, 24 Dec 2012 14:56:20 +0000 (15:56 +0100)
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.

doc/configuration.txt
include/types/proto_http.h
src/cfgparse.c
src/proto_http.c

index f1a388e5f3c661648c1cce1bae90ecc386d5aab7..3c46299ff5e3c9436827be18fb7857034a47a14f 100644 (file)
@@ -2606,22 +2606,54 @@ http-check send-state
 
   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.
-
-  There is no fixed limit to the number of http-request statements per
-  instance.
+  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.
+
+  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 @@ http-request { allow | deny | auth [realm <realm>] }
 
   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.
 
index c6efc55733fc30731806e1d193b97aa150b1b7b9..7c5a42a3fff20b37f99f65a01f6eed764a166cb7 100644 (file)
@@ -240,8 +240,10 @@ enum {
        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 http_req_rule {
                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 */
 };
 
index f7dcf061b5ac64dff38236e15c5cbf83150cb899..afcf60e6f658f105ed402e6c33ae3be253eda493 100644 (file)
@@ -2585,8 +2585,12 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
                        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;
                }
index b1039d60d185b64f9ef0a69cc03df2b46c835fc4..c715828e7bc19e1581b20ce066ccf40b071c8be8 100644 (file)
@@ -3061,13 +3061,16 @@ int http_handle_stats(struct session *s, struct channel *req)
        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 @@ http_check_access_rule(struct proxy *px, struct list *rules, struct session *s,
                }
 
                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 @@ int http_process_req_common(struct session *s, struct channel *req, int an_bit,
                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 @@ int http_process_req_common(struct session *s, struct channel *req, int an_bit,
        /* 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 @@ void free_http_req_rules(struct list *r) {
 
        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 @@ struct http_req_rule *parse_http_req_cond(const char **args, const char *file, i
                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 @@ struct http_req_rule *parse_http_req_cond(const char **args, const char *file, i
                        } 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;
        }