MINOR: http-ana: Use proxy's error replies to emit 401/407 responses

There is no reason to not use proxy's error replies to emit 401/407
responses. The function http_reply_40x_unauthorized(), responsible to emit those
responses, is not really complex. It only adds a
WWW-Authenticate/Proxy-Authenticate header to a generic message.

So now, error replies can be defined for 401 and 407 status codes, using
errorfile or http-error directives. When an http-request auth rule is evaluated,
the corresponding error reply is used. For 401 responses, all occurrences of the
WWW-Authenticate header are removed and replaced by a new one with a basic
authentication challenge for the configured realm. For 407 responses, the same
is done on the Proxy-Authenticate header. If the error reply must not be
altered, "http-request return" rule must be used instead.
This commit is contained in:
Christopher Faulet 2020-05-27 09:57:28 +02:00
parent ae43b6c446
commit 612f2eafe9
4 changed files with 76 additions and 51 deletions

View File

@ -2522,8 +2522,8 @@ errorfile <code> <file>
Arguments :
<code> is the HTTP status code. Currently, HAProxy is capable of
generating codes 200, 400, 403, 404, 405, 408, 410, 425, 429,
500, 502, 503, and 504.
generating codes 200, 400, 401, 403, 404, 405, 407, 408, 410,
425, 429, 500, 502, 503, and 504.
<file> designates a file containing the full HTTP response. It is
recommended to follow the common practice of appending ".http" to
@ -3859,8 +3859,8 @@ errorfile <code> <file>
yes | yes | yes | yes
Arguments :
<code> is the HTTP status code. Currently, HAProxy is capable of
generating codes 200, 400, 403, 404, 405, 408, 410, 425, 429, 500,
502, 503, and 504.
generating codes 200, 400, 401, 403, 404, 405, 407, 408, 410,
425, 429, 500, 502, 503, and 504.
<file> designates a file containing the full HTTP response. It is
recommended to follow the common practice of appending ".http" to
@ -3908,8 +3908,8 @@ errorfiles <name> [<code> ...]
<name> is the name of an existing http-errors section.
<code> is a HTTP status code. Several status code may be listed.
Currently, HAProxy is capable of generating codes 200, 400, 403,
404, 405, 408, 410, 425, 429, 500, 502, 503, and 504.
Currently, HAProxy is capable of generating codes 200, 400, 401,
403, 404, 405, 407, 408, 410, 425, 429, 500, 502, 503, and 504.
Errors defined in the http-errors section with the name <name> are imported
in the current proxy. If no status code is specified, all error files of the
@ -3934,8 +3934,8 @@ errorloc302 <code> <url>
yes | yes | yes | yes
Arguments :
<code> is the HTTP status code. Currently, HAProxy is capable of
generating codes 200, 400, 403, 404, 405, 408, 410, 425, 429, 500,
502, 503, and 504.
generating codes 200, 400, 401, 403, 404, 405, 407, 408, 410,
425, 429, 500, 502, 503, and 504.
<url> it is the exact contents of the "Location" header. It may contain
either a relative URI to an error page hosted on the same site,
@ -3966,8 +3966,8 @@ errorloc303 <code> <url>
yes | yes | yes | yes
Arguments :
<code> is the HTTP status code. Currently, HAProxy is capable of
generating codes 200, 400, 403, 404, 405, 408, 410, 425, 429, 500,
502, 503, and 504.
generating codes 200, 400, 401, 403, 404, 405, 407, 408, 410,
425, 429, 500, 502, 503, and 504.
<url> it is the exact contents of the "Location" header. It may contain
either a relative URI to an error page hosted on the same site,
@ -4942,8 +4942,8 @@ http-error status <code> [content-type <type>]
Arguments :
staus <code> is the HTTP status code. It must be specified.
Currently, HAProxy is capable of generating codes
200, 400, 403, 404, 405, 408, 410, 425, 429, 500,
502, 503, and 504.
200, 400, 401, 403, 404, 405, 407, 408, 410, 425, 429,
500, 502, 503, and 504.
content-type <type> is the response content type, for instance
"text/plain". This parameter is ignored and should be
@ -5095,6 +5095,14 @@ http-request auth [realm <realm>] [ { if | unless } <condition> ]
"realm" parameter is supported, it sets the authentication realm that is
returned with the response (typically the application's name).
The corresponding proxy's error message is used. It may be customized using
an "errorfile" or an "http-error" directive. For 401 responses, all
occurrences of the WWW-Authenticate header are removed and replaced by a new
one with a basic authentication challenge for realm "<realm>". For 407
responses, the same is done on the Proxy-Authenticate header. If the error
message must not be altered, consider to use "http-request return" rule
instead.
Example:
acl auth_ok http_auth_group(L1) G1
http-request auth unless auth_ok

View File

@ -82,9 +82,11 @@ enum ht_auth_m {
enum {
HTTP_ERR_200 = 0,
HTTP_ERR_400,
HTTP_ERR_401,
HTTP_ERR_403,
HTTP_ERR_404,
HTTP_ERR_405,
HTTP_ERR_407,
HTTP_ERR_408,
HTTP_ERR_410,
HTTP_ERR_421,

View File

@ -215,9 +215,11 @@ const char *HTTP_407_fmt =
const int http_err_codes[HTTP_ERR_SIZE] = {
[HTTP_ERR_200] = 200, /* used by "monitor-uri" */
[HTTP_ERR_400] = 400,
[HTTP_ERR_401] = 401,
[HTTP_ERR_403] = 403,
[HTTP_ERR_404] = 404,
[HTTP_ERR_405] = 405,
[HTTP_ERR_407] = 407,
[HTTP_ERR_408] = 408,
[HTTP_ERR_410] = 410,
[HTTP_ERR_421] = 421,
@ -248,6 +250,15 @@ const char *http_err_msgs[HTTP_ERR_SIZE] = {
"\r\n"
"<html><body><h1>400 Bad request</h1>\nYour browser sent an invalid request.\n</body></html>\n",
[HTTP_ERR_401] =
"HTTP/1.1 401 Unauthorized\r\n"
"Content-length: 112\r\n"
"Cache-Control: no-cache\r\n"
"Connection: close\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"<html><body><h1>401 Unauthorized</h1>\nYou need a valid user and password to access this content.\n</body></html>\n",
[HTTP_ERR_403] =
"HTTP/1.1 403 Forbidden\r\n"
"Content-length: 93\r\n"
@ -275,6 +286,15 @@ const char *http_err_msgs[HTTP_ERR_SIZE] = {
"\r\n"
"<html><body><h1>405 Method Not Allowed</h1>\nA request was made of a resource using a request method not supported by that resource\n</body></html>\n",
[HTTP_ERR_407] =
"HTTP/1.1 407 Unauthorized\r\n"
"Content-length: 112\r\n"
"Cache-Control: no-cache\r\n"
"Connection: close\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"<html><body><h1>407 Unauthorized</h1>\nYou need a valid user and password to access this content.\n</body></html>\n",
[HTTP_ERR_408] =
"HTTP/1.1 408 Request Time-out\r\n"
"Content-length: 110\r\n"
@ -396,9 +416,11 @@ int http_get_status_idx(unsigned int status)
switch (status) {
case 200: return HTTP_ERR_200;
case 400: return HTTP_ERR_400;
case 401: return HTTP_ERR_401;
case 403: return HTTP_ERR_403;
case 404: return HTTP_ERR_404;
case 405: return HTTP_ERR_405;
case 407: return HTTP_ERR_407;
case 408: return HTTP_ERR_408;
case 410: return HTTP_ERR_410;
case 421: return HTTP_ERR_421;

View File

@ -4910,61 +4910,54 @@ static int http_reply_40x_unauthorized(struct stream *s, const char *auth_realm)
{
struct channel *res = &s->res;
struct htx *htx = htx_from_buf(&res->buf);
struct htx_sl *sl;
struct ist code, body;
int status;
unsigned int flags = (HTX_SL_F_IS_RESP|HTX_SL_F_VER_11);
struct http_reply *reply;
struct http_hdr_ctx ctx;
struct ist hdr;
if (!(s->txn->flags & TX_USE_PX_CONN)) {
status = 401;
code = ist("401");
body = ist("<html><body><h1>401 Unauthorized</h1>\n"
"You need a valid user and password to access this content.\n"
"</body></html>\n");
s->txn->status = 401;
hdr = ist("WWW-Authenticate");
}
else {
status = 407;
code = ist("407");
body = ist("<html><body><h1>407 Unauthorized</h1>\n"
"You need a valid user and password to access this content.\n"
"</body></html>\n");
s->txn->status = 407;
hdr = ist("Proxy-Authenticate");
}
sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags,
ist("HTTP/1.1"), code, ist("Unauthorized"));
if (!sl)
goto fail;
sl->info.res.status = status;
s->txn->status = status;
reply = http_error_message(s);
channel_htx_truncate(res, htx);
if (chunk_printf(&trash, "Basic realm=\"%s\"", auth_realm) == -1)
goto fail;
if (!htx_add_header(htx, ist("Content-length"), ist("112")) ||
!htx_add_header(htx, ist("Cache-Control"), ist("no-cache")) ||
!htx_add_header(htx, ist("Connection"), ist("close")) ||
!htx_add_header(htx, ist("Content-Type"), ist("text/html")))
goto fail;
if (status == 401 && !htx_add_header(htx, ist("WWW-Authenticate"), ist2(trash.area, trash.data)))
goto fail;
if (status == 407 && !htx_add_header(htx, ist("Proxy-Authenticate"), ist2(trash.area, trash.data)))
goto fail;
if (!htx_add_endof(htx, HTX_BLK_EOH))
/* Write the generic 40x message */
if (http_reply_to_htx(s, htx, reply) == -1)
goto fail;
while (body.len) {
size_t sent = htx_add_data(htx, body);
if (!sent)
goto fail;
body.ptr += sent;
body.len -= sent;
}
/* Remove all existing occurrences of the XXX-Authenticate header */
ctx.blk = NULL;
while (http_find_header(htx, hdr, &ctx, 1))
http_remove_header(htx, &ctx);
if (!htx_add_endof(htx, HTX_BLK_EOM))
/* Now a the right XXX-Authenticate header */
if (!http_add_header(htx, hdr, ist2(b_orig(&trash), b_data(&trash))))
goto fail;
/* Finally forward the reply */
htx_to_buf(htx, &res->buf);
if (!http_forward_proxy_resp(s, 1))
goto fail;
/* Note: Only eval on the request */
s->logs.tv_request = now;
s->req.analysers &= AN_REQ_FLT_END;
if (s->sess->fe == s->be) /* report it if the request was intercepted by the frontend */
_HA_ATOMIC_ADD(&s->sess->fe->fe_counters.intercepted_req, 1);
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_LOCAL;
if (!(s->flags & SF_FINST_MASK))
s->flags |= SF_FINST_R;
return 0;
fail: