diff --git a/include/common/uri_auth.h b/include/common/uri_auth.h index b4c297c77..e66988048 100644 --- a/include/common/uri_auth.h +++ b/include/common/uri_auth.h @@ -17,13 +17,6 @@ #include -/* 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 */ }; diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h index 68a771ede..8213e3b7b 100644 --- a/include/proto/proto_http.h +++ b/include/proto/proto_http.h @@ -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, diff --git a/include/types/proto_http.h b/include/types/proto_http.h index 18fbc2665..f5410fac4 100644 --- a/include/types/proto_http.h +++ b/include/types/proto_http.h @@ -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 */ }; diff --git a/src/acl.c b/src/acl.c index 66050943b..007751de6 100644 --- a/src/acl.c +++ b/src/acl.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -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", diff --git a/src/cfgparse.c b/src/cfgparse.c index 7f218db1f..17bd5eaea 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -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) && diff --git a/src/haproxy.c b/src/haproxy.c index 905febfae..cb043140e 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -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); } diff --git a/src/proto_http.c b/src/proto_http.c index 37af53954..4ac53b38d 100644 --- a/src/proto_http.c +++ b/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) diff --git a/src/uri_auth.c b/src/uri_auth.c index 3cfc44526..6b2ca2aa3 100644 --- a/src/uri_auth.c +++ b/src/uri_auth.c @@ -17,6 +17,7 @@ #include #include +#include /* * 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; } /*