]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MAJOR] use the new auth framework for http stats
authorKrzysztof Piotr Oledzki <ole@ans.pl>
Fri, 29 Jan 2010 18:29:32 +0000 (19:29 +0100)
committerWilly Tarreau <w@1wt.eu>
Sun, 31 Jan 2010 18:14:09 +0000 (19:14 +0100)
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.

include/common/uri_auth.h
include/proto/proto_http.h
include/types/proto_http.h
src/acl.c
src/cfgparse.c
src/haproxy.c
src/proto_http.c
src/uri_auth.c

index b4c297c77511be17330e6cd32d7c33aee6d9d78b..e669880486e7ba556a906e2d0ca58220ab4ffc02 100644 (file)
 
 #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 */
 };
 
index 68a771ede6f33c59a127fb700aad9669efea6d36..8213e3b7b8edd7940314363b317448acb89c5320 100644 (file)
@@ -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,
index 18fbc266519de60a3e9e02622ff93d0ac9cef529..f5410fac426f3ffda5a43d3854082a7ae5073b1d 100644 (file)
@@ -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 */
 };
 
index 66050943b7c61af5f472afee13824c8efe0f3ccb..007751de66182662fa15dc4664f141cad5a0dc63 100644 (file)
--- a/src/acl.c
+++ b/src/acl.c
@@ -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",
index 7f218db1ffa77bc09f7d7d1945b42fae0e038622..17bd5eaea1e0d1e79ce23af7a134d0c0c9929329 100644 (file)
@@ -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) &&
index 905febfaede5d8f0599a9fc278dbda0f3dc85cc3..cb043140ee79c22826e1079f6f2cdc148f189758 100644 (file)
@@ -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);
        }
 
index 37af5395460e69309647af202e1cd25b72257ec0..4ac53b38deea2eb4ee1bfa343f8d9c8c880acb3d 100644 (file)
@@ -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;
+
+               if (!realm)
+                       realm = do_stats?STATS_DEFAULT_REALM:px->id;
 
-               sprintf(trash, HTTP_401_fmt, req_acl->http_auth.realm?req_acl->http_auth.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)
index 3cfc445263c3f0a38be35f72eb4b7ec1bced81cf..6b2ca2aa39493dce0c11c5554c984adaba97c595 100644 (file)
@@ -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;
+       pass = strchr(user, ':');
+       if (pass)
+               *pass++ = '\0';
+       else
+               pass = "";
 
-       if ((auth_base64 = (char *)calloc(1, blen + 1)) == NULL)
-               goto out_ubase64;
+       if ((u = stats_check_init_uri_auth(root)) == NULL)
+               return NULL;
 
-       /* convert user:passwd to base64. It should return exactly blen */
-       if (a2base64(auth, alen, auth_base64, blen + 1) != blen)
-               goto out_base64;
+       if (!u->userlist)
+               u->userlist = (struct userlist *)calloc(1, sizeof(struct userlist));
 
-       if ((u = stats_check_init_uri_auth(root)) == NULL)
-               goto out_base64;
+       if (!u->userlist)
+               return NULL;
 
-       ulist = &u->users;
-       while ((users = *ulist)) {
-               if (!strcmp(users->user_pwd, auth_base64))
-                       break;
-               ulist = &users->next;
-       }
+       if (!u->userlist->name)
+               u->userlist->name = strdup(".internal-stats-userlist");
 
-       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;
+       if (!u->userlist->name)
+               return NULL;
 
- out_u:
-       free(u);
- out_base64:
-       free(auth_base64);
- out_ubase64:
-       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;
+
+       return u;
 }
 
 /*