mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2025-02-12 08:27:24 +00:00
[MAJOR] use the new auth framework for http stats
Support the new syntax (http-request allow/deny/auth) in http stats. Now it is possible to use the same syntax is the same like in the frontend/backend http-request access control: acl src_nagios src 192.168.66.66 acl stats_auth_ok http_auth(L1) stats http-request allow if src_nagios stats http-request allow if stats_auth_ok stats http-request auth realm LB The old syntax is still supported, but now it is emulated via private acls and an aditional userlist.
This commit is contained in:
parent
f9423ae43a
commit
8c8bd4593c
@ -17,13 +17,6 @@
|
||||
|
||||
#include <types/auth.h>
|
||||
|
||||
/* here we find a very basic list of base64-encoded 'user:passwd' strings */
|
||||
struct user_auth {
|
||||
struct user_auth *next; /* next entry, NULL if none */
|
||||
int user_len; /* user:passwd length */
|
||||
char *user_pwd; /* auth as base64("user":"passwd") (see RFC2617) */
|
||||
};
|
||||
|
||||
/* This is a list of proxies we are allowed to see. Later, it should go in the
|
||||
* user list, but before this we need to support de/re-authentication.
|
||||
*/
|
||||
@ -46,9 +39,9 @@ struct uri_auth {
|
||||
char *node, *desc; /* node name & description reported in this stats */
|
||||
int refresh; /* refresh interval for the browser (in seconds) */
|
||||
int flags; /* some flags describing the statistics page */
|
||||
struct user_auth *users; /* linked list of valid user:passwd couples */
|
||||
struct stat_scope *scope; /* linked list of authorized proxies */
|
||||
struct list req_acl; /* */
|
||||
struct userlist *userlist; /* private userlist to emulate legacy "stats auth user:password" */
|
||||
struct list req_acl; /* http stats ACL: allow/deny/auth */
|
||||
struct uri_auth *next; /* Used at deinit() to build a list of unique elements */
|
||||
};
|
||||
|
||||
|
@ -81,7 +81,7 @@ void manage_client_side_appsession(struct session *t, const char *buf, int len);
|
||||
void manage_client_side_cookies(struct session *t, struct buffer *req);
|
||||
void manage_server_side_cookies(struct session *t, struct buffer *rtr);
|
||||
void check_response_for_cacheability(struct session *t, struct buffer *rtr);
|
||||
int stats_check_uri_auth(struct session *t, struct proxy *backend);
|
||||
int stats_check_uri(struct session *s, struct proxy *backend);
|
||||
void init_proto_http();
|
||||
int http_find_header2(const char *name, int len,
|
||||
char *sol, struct hdr_idx *idx,
|
||||
|
@ -315,7 +315,6 @@ struct http_txn {
|
||||
char *srv_cookie; /* cookie presented by the server, in capture mode */
|
||||
char *sessid; /* the appsession id, if found in the request or in the response */
|
||||
|
||||
struct chunk auth_hdr; /* points to 'Authorization:' header */
|
||||
struct http_auth_data auth; /* HTTP auth data */
|
||||
};
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include <common/config.h>
|
||||
#include <common/mini-clist.h>
|
||||
#include <common/standard.h>
|
||||
#include <common/uri_auth.h>
|
||||
|
||||
#include <proto/acl.h>
|
||||
#include <proto/auth.h>
|
||||
@ -1218,7 +1219,11 @@ acl_find_targets(struct proxy *p)
|
||||
continue;
|
||||
}
|
||||
|
||||
ul = auth_find_userlist(expr->arg.str);
|
||||
if (p->uri_auth && p->uri_auth->userlist &&
|
||||
!strcmp(p->uri_auth->userlist->name, expr->arg.str))
|
||||
ul = p->uri_auth->userlist;
|
||||
else
|
||||
ul = auth_find_userlist(expr->arg.str);
|
||||
|
||||
if (!ul) {
|
||||
Alert("proxy %s: acl %s %s(%s): unable to find userlist.\n",
|
||||
|
@ -2414,6 +2414,37 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
|
||||
err_code |= ERR_ALERT | ERR_ABORT;
|
||||
goto out;
|
||||
}
|
||||
} else if (!strcmp(args[1], "http-request")) { /* request access control: allow/deny/auth */
|
||||
struct req_acl_rule *req_acl;
|
||||
|
||||
if (curproxy == &defproxy) {
|
||||
Alert("parsing [%s:%d]: '%s' not allowed in 'defaults' section.\n", file, linenum, args[0]);
|
||||
err_code |= ERR_ALERT | ERR_FATAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!stats_check_init_uri_auth(&curproxy->uri_auth)) {
|
||||
Alert("parsing [%s:%d]: out of memory.\n", file, linenum);
|
||||
err_code |= ERR_ALERT | ERR_ABORT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!LIST_ISEMPTY(&curproxy->uri_auth->req_acl) &&
|
||||
!LIST_PREV(&curproxy->uri_auth->req_acl, struct req_acl_rule *, list)->cond) {
|
||||
Warning("parsing [%s:%d]: previous '%s' action has no condition attached, further entries are NOOP.\n",
|
||||
file, linenum, args[0]);
|
||||
err_code |= ERR_WARN;
|
||||
}
|
||||
|
||||
req_acl = parse_auth_cond((const char **)args + 2, file, linenum, &curproxy->acl, &curproxy->acl_requires);
|
||||
|
||||
if (!req_acl) {
|
||||
err_code |= ERR_ALERT | ERR_ABORT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
LIST_ADDQ(&curproxy->uri_auth->req_acl, &req_acl->list);
|
||||
|
||||
} else if (!strcmp(args[1], "auth")) {
|
||||
if (*(args[2]) == 0) {
|
||||
Alert("parsing [%s:%d] : 'auth' needs a user:password account.\n", file, linenum);
|
||||
@ -4691,6 +4722,55 @@ int check_config_validity()
|
||||
}
|
||||
}
|
||||
|
||||
if (curproxy->uri_auth && !LIST_ISEMPTY(&curproxy->uri_auth->req_acl) &&
|
||||
(curproxy->uri_auth->userlist || curproxy->uri_auth->auth_realm )) {
|
||||
Alert("%s '%s': stats 'auth'/'realm' and 'http-request' can't be used at the same time.\n",
|
||||
"proxy", curproxy->id);
|
||||
cfgerr++;
|
||||
goto out_uri_auth_compat;
|
||||
}
|
||||
|
||||
if (curproxy->uri_auth && curproxy->uri_auth->userlist) {
|
||||
const char *uri_auth_compat_acl[3] = { ".internal-stats-auth-ok", "http_auth(.internal-stats-userlist)", ""};
|
||||
const char *uri_auth_compat_req[][4] = {
|
||||
{ "allow", "if", ".internal-stats-auth-ok", ""},
|
||||
{ "auth", "", "", ""},
|
||||
{ 0 },
|
||||
};
|
||||
struct req_acl_rule *req_acl;
|
||||
int i;
|
||||
|
||||
if (parse_acl(uri_auth_compat_acl, &curproxy->acl) == NULL) {
|
||||
Alert("Error compiling internal auth-compat acl.\n");
|
||||
cfgerr++;
|
||||
goto out_uri_auth_compat;
|
||||
}
|
||||
|
||||
if (curproxy->uri_auth->auth_realm) {
|
||||
uri_auth_compat_req[1][1] = "realm";
|
||||
uri_auth_compat_req[1][2] = curproxy->uri_auth->auth_realm;
|
||||
} else
|
||||
uri_auth_compat_req[1][1] = "";
|
||||
|
||||
for (i = 0; *uri_auth_compat_req[i]; i++) {
|
||||
req_acl = parse_auth_cond(uri_auth_compat_req[i], "internal-stats-auth-compat", i,
|
||||
&curproxy->acl, &curproxy->acl_requires);
|
||||
if (!req_acl) {
|
||||
cfgerr++;
|
||||
break;
|
||||
}
|
||||
|
||||
LIST_ADDQ(&curproxy->uri_auth->req_acl, &req_acl->list);
|
||||
}
|
||||
|
||||
if (curproxy->uri_auth->auth_realm) {
|
||||
free(curproxy->uri_auth->auth_realm);
|
||||
curproxy->uri_auth->auth_realm = NULL;
|
||||
}
|
||||
|
||||
}
|
||||
out_uri_auth_compat:
|
||||
|
||||
cfgerr += acl_find_targets(curproxy);
|
||||
|
||||
if ((curproxy->mode == PR_MODE_TCP || curproxy->mode == PR_MODE_HTTP) &&
|
||||
|
@ -713,7 +713,6 @@ void deinit(void)
|
||||
struct wordlist *wl, *wlb;
|
||||
struct cond_wordlist *cwl, *cwlb;
|
||||
struct uri_auth *uap, *ua = NULL;
|
||||
struct user_auth *user;
|
||||
int i;
|
||||
|
||||
while (p) {
|
||||
@ -879,12 +878,9 @@ void deinit(void)
|
||||
free(uap->node);
|
||||
free(uap->desc);
|
||||
|
||||
while (uap->users) {
|
||||
user = uap->users;
|
||||
uap->users = uap->users->next;
|
||||
free(user->user_pwd);
|
||||
free(user);
|
||||
}
|
||||
userlist_free(uap->userlist);
|
||||
req_acl_free(&uap->req_acl);
|
||||
|
||||
free(uap);
|
||||
}
|
||||
|
||||
|
121
src/proto_http.c
121
src/proto_http.c
@ -2798,7 +2798,7 @@ int http_process_req_common(struct session *s, struct buffer *req, int an_bit, s
|
||||
struct req_acl_rule *req_acl, *req_acl_final = NULL;
|
||||
struct redirect_rule *rule;
|
||||
struct cond_wordlist *wl;
|
||||
int del_ka, del_cl;
|
||||
int del_ka, del_cl, do_stats;
|
||||
|
||||
if (unlikely(msg->msg_state < HTTP_MSG_BODY)) {
|
||||
/* we need more data */
|
||||
@ -2835,7 +2835,9 @@ int http_process_req_common(struct session *s, struct buffer *req, int an_bit, s
|
||||
}
|
||||
}
|
||||
|
||||
list_for_each_entry(req_acl, &px->req_acl, list) {
|
||||
do_stats = stats_check_uri(s, px);
|
||||
|
||||
list_for_each_entry(req_acl, (do_stats?&px->uri_auth->req_acl:&px->req_acl), list) {
|
||||
int ret = 1;
|
||||
|
||||
if (req_acl->action >= PR_REQ_ACL_ACT_MAX)
|
||||
@ -2962,25 +2964,35 @@ int http_process_req_common(struct session *s, struct buffer *req, int an_bit, s
|
||||
|
||||
if (req_acl_final && req_acl_final->action == PR_REQ_ACL_ACT_HTTP_AUTH) {
|
||||
struct chunk msg;
|
||||
char *realm = req_acl->http_auth.realm;
|
||||
|
||||
sprintf(trash, HTTP_401_fmt, req_acl->http_auth.realm?req_acl->http_auth.realm:px->id);
|
||||
if (!realm)
|
||||
realm = do_stats?STATS_DEFAULT_REALM:px->id;
|
||||
|
||||
sprintf(trash, HTTP_401_fmt, realm);
|
||||
chunk_initlen(&msg, trash, sizeof(trash), strlen(trash));
|
||||
txn->status = 401;
|
||||
stream_int_retnclose(req->prod, &msg);
|
||||
goto return_prx_cond;
|
||||
}
|
||||
|
||||
/* check if stats URI was requested, and if an auth is needed */
|
||||
if (px->uri_auth != NULL &&
|
||||
(txn->meth == HTTP_METH_GET || txn->meth == HTTP_METH_HEAD)) {
|
||||
/* we have to check the URI and auth for this request.
|
||||
if (do_stats) {
|
||||
/* We need to provied stats for this request.
|
||||
* FIXME!!! that one is rather dangerous, we want to
|
||||
* make it follow standard rules (eg: clear req->analysers).
|
||||
*/
|
||||
if (stats_check_uri_auth(s, px)) {
|
||||
req->analysers = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
s->logs.tv_request = now;
|
||||
s->data_source = DATA_SRC_STATS;
|
||||
s->data_state = DATA_ST_INIT;
|
||||
s->task->nice = -32; /* small boost for HTTP statistics */
|
||||
stream_int_register_handler(s->rep->prod, http_stats_io_handler);
|
||||
s->rep->prod->private = s;
|
||||
s->rep->prod->st0 = s->rep->prod->st1 = 0;
|
||||
req->analysers = 0;
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/* check whether we have some ACLs set to redirect this request */
|
||||
@ -6362,25 +6374,27 @@ void get_srv_from_appsession(struct session *t, const char *begin, int len)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* In a GET or HEAD request, check if the requested URI matches the stats uri
|
||||
* for the current backend, and if an authorization has been passed and is valid.
|
||||
* for the current backend.
|
||||
*
|
||||
* It is assumed that the request is either a HEAD or GET and that the
|
||||
* t->be->uri_auth field is valid. An HTTP/401 response may be sent, or
|
||||
* the stats I/O handler will be registered to start sending data.
|
||||
* t->be->uri_auth field is valid.
|
||||
*
|
||||
* Returns 1 if the session's state changes, otherwise 0.
|
||||
* Returns 1 if stats should be provided, otherwise 0.
|
||||
*/
|
||||
int stats_check_uri_auth(struct session *t, struct proxy *backend)
|
||||
int stats_check_uri(struct session *t, struct proxy *backend)
|
||||
{
|
||||
struct http_txn *txn = &t->txn;
|
||||
struct uri_auth *uri_auth = backend->uri_auth;
|
||||
struct user_auth *user;
|
||||
int authenticated, cur_idx;
|
||||
char *h;
|
||||
|
||||
if (!uri_auth)
|
||||
return 0;
|
||||
|
||||
if (txn->meth != HTTP_METH_GET && txn->meth != HTTP_METH_HEAD)
|
||||
return 0;
|
||||
|
||||
memset(&t->data_ctx.stats, 0, sizeof(t->data_ctx.stats));
|
||||
|
||||
/* check URI size */
|
||||
@ -6424,74 +6438,6 @@ int stats_check_uri_auth(struct session *t, struct proxy *backend)
|
||||
|
||||
t->data_ctx.stats.flags |= STAT_SHOW_STAT | STAT_SHOW_INFO;
|
||||
|
||||
/* we are in front of a interceptable URI. Let's check
|
||||
* if there's an authentication and if it's valid.
|
||||
*/
|
||||
user = uri_auth->users;
|
||||
if (!user) {
|
||||
/* no user auth required, it's OK */
|
||||
authenticated = 1;
|
||||
} else {
|
||||
authenticated = 0;
|
||||
|
||||
/* a user list is defined, we have to check.
|
||||
* skip 21 chars for "Authorization: Basic ".
|
||||
*/
|
||||
|
||||
/* FIXME: this should move to an earlier place */
|
||||
cur_idx = 0;
|
||||
h = txn->req.sol + hdr_idx_first_pos(&txn->hdr_idx);
|
||||
while ((cur_idx = txn->hdr_idx.v[cur_idx].next)) {
|
||||
int len = txn->hdr_idx.v[cur_idx].len;
|
||||
if (len > 14 &&
|
||||
!strncasecmp("Authorization:", h, 14)) {
|
||||
chunk_initlen(&txn->auth_hdr, h, 0, len);
|
||||
break;
|
||||
}
|
||||
h += len + txn->hdr_idx.v[cur_idx].cr + 1;
|
||||
}
|
||||
|
||||
if (txn->auth_hdr.len < 21 ||
|
||||
memcmp(txn->auth_hdr.str + 14, " Basic ", 7))
|
||||
user = NULL;
|
||||
|
||||
while (user) {
|
||||
if ((txn->auth_hdr.len == user->user_len + 14 + 7)
|
||||
&& !memcmp(txn->auth_hdr.str + 14 + 7,
|
||||
user->user_pwd, user->user_len)) {
|
||||
authenticated = 1;
|
||||
break;
|
||||
}
|
||||
user = user->next;
|
||||
}
|
||||
}
|
||||
|
||||
if (!authenticated) {
|
||||
struct chunk msg;
|
||||
|
||||
/* no need to go further */
|
||||
sprintf(trash, HTTP_401_fmt, uri_auth->auth_realm);
|
||||
chunk_initlen(&msg, trash, sizeof(trash), strlen(trash));
|
||||
txn->status = 401;
|
||||
stream_int_retnclose(t->req->prod, &msg);
|
||||
t->req->analysers = 0;
|
||||
if (!(t->flags & SN_ERR_MASK))
|
||||
t->flags |= SN_ERR_PRXCOND;
|
||||
if (!(t->flags & SN_FINST_MASK))
|
||||
t->flags |= SN_FINST_R;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* The request is valid, the user is authenticated. Let's start sending
|
||||
* data.
|
||||
*/
|
||||
t->logs.tv_request = now;
|
||||
t->data_source = DATA_SRC_STATS;
|
||||
t->data_state = DATA_ST_INIT;
|
||||
t->task->nice = -32; /* small boost for HTTP statistics */
|
||||
stream_int_register_handler(t->rep->prod, http_stats_io_handler);
|
||||
t->rep->prod->private = t;
|
||||
t->rep->prod->st0 = t->rep->prod->st1 = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -6555,7 +6501,6 @@ void http_init_txn(struct session *s)
|
||||
txn->rsp.msg_state = HTTP_MSG_RPBEFORE; /* at the very beginning of the response */
|
||||
|
||||
txn->auth.method = HTTP_AUTH_UNKNOWN;
|
||||
chunk_reset(&txn->auth_hdr);
|
||||
|
||||
txn->req.err_pos = txn->rsp.err_pos = -2; /* block buggy requests/responses */
|
||||
if (fe->options2 & PR_O2_REQBUG_OK)
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include <common/config.h>
|
||||
#include <common/uri_auth.h>
|
||||
|
||||
#include <proto/log.h>
|
||||
|
||||
/*
|
||||
* Initializes a basic uri_auth structure header and returns a pointer to it.
|
||||
@ -29,6 +30,8 @@ struct uri_auth *stats_check_init_uri_auth(struct uri_auth **root)
|
||||
if (!root || !*root) {
|
||||
if ((u = (struct uri_auth *)calloc(1, sizeof (*u))) == NULL)
|
||||
goto out_u;
|
||||
|
||||
LIST_INIT(&u->req_acl);
|
||||
} else
|
||||
u = *root;
|
||||
|
||||
@ -38,17 +41,11 @@ struct uri_auth *stats_check_init_uri_auth(struct uri_auth **root)
|
||||
goto out_uri;
|
||||
}
|
||||
|
||||
if (!u->auth_realm)
|
||||
if ((u->auth_realm = strdup(STATS_DEFAULT_REALM)) == NULL)
|
||||
goto out_realm;
|
||||
|
||||
if (root && !*root)
|
||||
*root = u;
|
||||
|
||||
return u;
|
||||
|
||||
out_realm:
|
||||
free(u->uri_prefix);
|
||||
out_uri:
|
||||
if (!root || !*root)
|
||||
free(u);
|
||||
@ -210,48 +207,55 @@ struct uri_auth *stats_set_flag(struct uri_auth **root, int flag)
|
||||
* authorized users. If a matching entry is found, no update will be performed.
|
||||
* Uses the pointer provided if not NULL and not initialized.
|
||||
*/
|
||||
struct uri_auth *stats_add_auth(struct uri_auth **root, char *auth)
|
||||
struct uri_auth *stats_add_auth(struct uri_auth **root, char *user)
|
||||
{
|
||||
struct uri_auth *u;
|
||||
char *auth_base64;
|
||||
int alen, blen;
|
||||
struct user_auth *users, **ulist;
|
||||
struct auth_users *newuser;
|
||||
char *pass;
|
||||
|
||||
alen = strlen(auth);
|
||||
blen = ((alen + 2) / 3) * 4;
|
||||
|
||||
if ((auth_base64 = (char *)calloc(1, blen + 1)) == NULL)
|
||||
goto out_ubase64;
|
||||
|
||||
/* convert user:passwd to base64. It should return exactly blen */
|
||||
if (a2base64(auth, alen, auth_base64, blen + 1) != blen)
|
||||
goto out_base64;
|
||||
pass = strchr(user, ':');
|
||||
if (pass)
|
||||
*pass++ = '\0';
|
||||
else
|
||||
pass = "";
|
||||
|
||||
if ((u = stats_check_init_uri_auth(root)) == NULL)
|
||||
goto out_base64;
|
||||
return NULL;
|
||||
|
||||
ulist = &u->users;
|
||||
while ((users = *ulist)) {
|
||||
if (!strcmp(users->user_pwd, auth_base64))
|
||||
break;
|
||||
ulist = &users->next;
|
||||
}
|
||||
if (!u->userlist)
|
||||
u->userlist = (struct userlist *)calloc(1, sizeof(struct userlist));
|
||||
|
||||
if (!u->userlist)
|
||||
return NULL;
|
||||
|
||||
if (!u->userlist->name)
|
||||
u->userlist->name = strdup(".internal-stats-userlist");
|
||||
|
||||
if (!u->userlist->name)
|
||||
return NULL;
|
||||
|
||||
for (newuser = u->userlist->users; newuser; newuser = newuser->next)
|
||||
if (!strcmp(newuser->user, user)) {
|
||||
Warning("uri auth: ignoring duplicated user '%s'.\n",
|
||||
user);
|
||||
return u;
|
||||
}
|
||||
|
||||
newuser = (struct auth_users *)calloc(1, sizeof(struct auth_users));
|
||||
if (!newuser)
|
||||
return NULL;
|
||||
|
||||
newuser->user = strdup(user);
|
||||
newuser->pass = strdup(pass);
|
||||
newuser->flags |= AU_O_INSECURE;
|
||||
|
||||
if (!newuser->user || !newuser->user)
|
||||
return NULL;
|
||||
|
||||
newuser->next = u->userlist->users;
|
||||
u->userlist->users = newuser;
|
||||
|
||||
if (!users) {
|
||||
if ((users = (struct user_auth *)calloc(1, sizeof(*users))) == NULL)
|
||||
goto out_u;
|
||||
*ulist = users;
|
||||
users->user_pwd = auth_base64;
|
||||
users->user_len = blen;
|
||||
}
|
||||
return u;
|
||||
|
||||
out_u:
|
||||
free(u);
|
||||
out_base64:
|
||||
free(auth_base64);
|
||||
out_ubase64:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
|
Loading…
Reference in New Issue
Block a user