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
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.
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 */
};
/*
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 */
};
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;
}
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;
}
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
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));
/* 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)
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);
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]) {
} 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;
}