diff --git a/doc/configuration.txt b/doc/configuration.txt index f1a388e5f..3c46299ff 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -2606,22 +2606,54 @@ http-check send-state See also : "option httpchk", "http-check disable-on-404" -http-request { allow | deny | auth [realm ] } +http-request { allow | deny | auth [realm ] | + add-header | set-header } [ { if | unless } ] 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 + and whose value is defined by 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 ] } 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 c6efc5573..7c5a42a3f 100644 --- a/include/types/proto_http.h +++ b/include/types/proto_http.h @@ -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 */ }; diff --git a/src/cfgparse.c b/src/cfgparse.c index f7dcf061b..afcf60e6f 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -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; } diff --git a/src/proto_http.c b/src/proto_http.c index b1039d60d..c715828e7 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -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 for session , proxy and + * transaction . 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; }