[MEDIUM] added the new 'stats' keyword with user authentication subsystem.
Right now it only validates the user/passwd according to a specified list, and lets the user pass through the proxy if the authentication is OK, and it refuses any invalid access with a 401 Unauthorized response.
This commit is contained in:
parent
4404b7ebcc
commit
9e1388671a
2
Makefile
2
Makefile
|
@ -109,7 +109,7 @@ LDFLAGS = -g
|
|||
|
||||
all: haproxy
|
||||
|
||||
haproxy: src/list.o src/chtbl.o src/hashpjw.o haproxy.o
|
||||
haproxy: src/list.o src/chtbl.o src/hashpjw.o haproxy.o src/base64.o src/uri_auth.o
|
||||
$(LD) $(LDFLAGS) -o $@ $^ $(LIBS)
|
||||
|
||||
%.o: %.c
|
||||
|
|
146
haproxy.c
146
haproxy.c
|
@ -85,6 +85,8 @@
|
|||
#include <assert.h>
|
||||
#endif
|
||||
|
||||
#include <include/base64.h>
|
||||
#include <include/uri_auth.h>
|
||||
#include "include/appsession.h"
|
||||
#include "include/mini-clist.h"
|
||||
|
||||
|
@ -493,6 +495,12 @@ int strlcpy2(char *dst, const char *src, int size) {
|
|||
|
||||
/*********************************************************************/
|
||||
|
||||
/* describes a chunk of string */
|
||||
struct chunk {
|
||||
char *str; /* beginning of the string itself. Might not be 0-terminated */
|
||||
int len; /* size of the string from first to last char. <0 = uninit. */
|
||||
};
|
||||
|
||||
struct cap_hdr {
|
||||
struct cap_hdr *next;
|
||||
char *name; /* header name, case insensitive */
|
||||
|
@ -584,6 +592,8 @@ struct session {
|
|||
struct pendconn *pend_pos; /* if not NULL, points to the position in the pending queue */
|
||||
char **req_cap; /* array of captured request headers (may be NULL) */
|
||||
char **rsp_cap; /* array of captured response headers (may be NULL) */
|
||||
struct chunk req_line; /* points to first line */
|
||||
struct chunk auth_hdr; /* points to 'Authorization:' header */
|
||||
struct {
|
||||
int logwait; /* log fields waiting to be collected : LW_* */
|
||||
struct timeval tv_accept; /* date of the accept() (beginning of the session) */
|
||||
|
@ -630,6 +640,7 @@ struct proxy {
|
|||
char *capture_name; /* beginning of the name of the cookie to capture */
|
||||
int capture_namelen; /* length of the cookie name to match */
|
||||
int capture_len; /* length of the string to be captured */
|
||||
struct uri_auth *uri_auth; /* if non-NULL, the (list of) per-URI authentications */
|
||||
int clitimeout; /* client I/O timeout (in milliseconds) */
|
||||
int srvtimeout; /* server I/O timeout (in milliseconds) */
|
||||
int contimeout; /* connect timeout (in milliseconds) */
|
||||
|
@ -839,6 +850,15 @@ const char *HTTP_400 =
|
|||
"\r\n"
|
||||
"<html><body><h1>400 Bad request</h1>\nYour browser sent an invalid request.\n</body></html>\n";
|
||||
|
||||
/* Warning: this one is an sprintf() fmt string, with <realm> as its only argument */
|
||||
const char *HTTP_401_fmt =
|
||||
"HTTP/1.0 401 Unauthorized\r\n"
|
||||
"Cache-Control: no-cache\r\n"
|
||||
"Connection: close\r\n"
|
||||
"WWW-Authenticate: Basic realm=\"%s\"\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";
|
||||
|
||||
const char *HTTP_403 =
|
||||
"HTTP/1.0 403 Forbidden\r\n"
|
||||
"Cache-Control: no-cache\r\n"
|
||||
|
@ -3139,6 +3159,8 @@ int event_accept(int fd) {
|
|||
s->res_cr = s->res_cw = s->res_sr = s->res_sw = RES_SILENT;
|
||||
s->cli_fd = cfd;
|
||||
s->srv_fd = -1;
|
||||
s->req_line.len = -1;
|
||||
s->auth_hdr.len = -1;
|
||||
s->srv = NULL;
|
||||
s->pend_pos = NULL;
|
||||
s->conn_retries = p->conn_retries;
|
||||
|
@ -3683,6 +3705,71 @@ int process_cli(struct session *t) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
/* Right now, we know that we have processed the entire headers
|
||||
* and that unwanted requests have been filtered out. We can do
|
||||
* whatever we want.
|
||||
*/
|
||||
|
||||
/* FIXME debugging code !!! */
|
||||
if (t->req_line.len >= 0) {
|
||||
write(2, t->req_line.str, t->req_line.len);
|
||||
}
|
||||
|
||||
if (t->proxy->uri_auth != NULL
|
||||
&& t->req_line.len >= t->proxy->uri_auth->uri_len + 4) { /* +4 for "GET /" */
|
||||
if (!memcmp(t->req_line.str + 4,
|
||||
t->proxy->uri_auth->uri_prefix, t->proxy->uri_auth->uri_len)
|
||||
&& !memcmp(t->req_line.str, "GET ", 4)) {
|
||||
struct user_auth *user;
|
||||
int authenticated;
|
||||
|
||||
/* we are in front of a interceptable URI. Let's check
|
||||
* if there's an authentication and if it's valid.
|
||||
*/
|
||||
user = t->proxy->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 ".
|
||||
*/
|
||||
if (t->auth_hdr.len < 21 || memcmp(t->auth_hdr.str + 14, " Basic ", 7))
|
||||
user = NULL;
|
||||
|
||||
while (user) {
|
||||
if ((t->auth_hdr.len == user->user_len + 21)
|
||||
&& !memcmp(t->auth_hdr.str+21, user->user_pwd, user->user_len)) {
|
||||
authenticated = 1;
|
||||
break;
|
||||
}
|
||||
user = user->next;
|
||||
write(2, t->auth_hdr.str, t->auth_hdr.len);
|
||||
}
|
||||
}
|
||||
|
||||
if (!authenticated) {
|
||||
int msglen;
|
||||
|
||||
/* no need to go further */
|
||||
|
||||
msglen = sprintf(trash, HTTP_401_fmt, t->proxy->uri_auth->auth_realm);
|
||||
t->logs.status = 401;
|
||||
client_retnclose(t, msglen, trash);
|
||||
if (!(t->flags & SN_ERR_MASK))
|
||||
t->flags |= SN_ERR_PRXCOND;
|
||||
if (!(t->flags & SN_FINST_MASK))
|
||||
t->flags |= SN_FINST_R;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Hey, we have passed the authentication ! */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (line = 0; line < t->proxy->nb_reqadd; line++) {
|
||||
len = sprintf(trash, "%s\r\n", t->proxy->req_add[line]);
|
||||
buffer_replace2(req, req->h, req->h, trash, len);
|
||||
|
@ -4228,8 +4315,8 @@ int process_cli(struct session *t) {
|
|||
/* WARNING! <ptr> becomes invalid for now. If some code
|
||||
* below needs to rely on it before the end of the global
|
||||
* header loop, we need to correct it with this code :
|
||||
* ptr = del_colon;
|
||||
*/
|
||||
ptr = del_colon;
|
||||
}
|
||||
else
|
||||
delete_header = 1;
|
||||
|
@ -4239,8 +4326,24 @@ int process_cli(struct session *t) {
|
|||
/* let's look if we have to delete this header */
|
||||
if (delete_header && !(t->flags & SN_CLDENY)) {
|
||||
buffer_replace2(req, req->h, req->lr, NULL, 0);
|
||||
/* WARNING: ptr is not valid anymore, since the header may have
|
||||
* been deleted or truncated ! */
|
||||
} else {
|
||||
/* try to catch the first line as the request */
|
||||
if (t->req_line.len < 0) {
|
||||
t->req_line.str = req->h;
|
||||
t->req_line.len = ptr - req->h;
|
||||
}
|
||||
|
||||
/* We might also need the 'Authorization: ' header */
|
||||
if (t->auth_hdr.len < 0 &&
|
||||
t->proxy->uri_auth != NULL &&
|
||||
ptr > req->h + 15 &&
|
||||
!strncasecmp("Authorization: ", req->h, 15)) {
|
||||
t->auth_hdr.str = req->h;
|
||||
t->auth_hdr.len = ptr - req->h;
|
||||
}
|
||||
}
|
||||
/* WARNING: ptr is not valid anymore, since the header may have been deleted or truncated ! */
|
||||
|
||||
req->h = req->lr;
|
||||
} /* while (req->lr < req->r) */
|
||||
|
@ -7545,6 +7648,45 @@ int cfg_parse_listen(char *file, int linenum, char **args) {
|
|||
}
|
||||
curproxy->conn_retries = atol(args[1]);
|
||||
}
|
||||
else if (!strcmp(args[0], "stats")) {
|
||||
if (*(args[1]) == 0) {
|
||||
Alert("parsing [%s:%d] : '%s' expects 'uri', 'realm', 'auth' or 'enable'.\n", file, linenum, args[0]);
|
||||
return -1;
|
||||
} else if (!strcmp(args[1], "uri")) {
|
||||
if (*(args[2]) == 0) {
|
||||
Alert("parsing [%s:%d] : 'uri' needs an URI prefix.\n", file, linenum);
|
||||
return -1;
|
||||
} else if (!stats_set_uri(&curproxy->uri_auth, args[2])) {
|
||||
Alert("parsing [%s:%d] : out of memory.\n", file, linenum);
|
||||
return -1;
|
||||
}
|
||||
} else if (!strcmp(args[1], "realm")) {
|
||||
if (*(args[2]) == 0) {
|
||||
Alert("parsing [%s:%d] : 'realm' needs an realm name.\n", file, linenum);
|
||||
return -1;
|
||||
} else if (!stats_set_realm(&curproxy->uri_auth, args[2])) {
|
||||
Alert("parsing [%s:%d] : out of memory.\n", file, linenum);
|
||||
return -1;
|
||||
}
|
||||
} else if (!strcmp(args[1], "auth")) {
|
||||
if (*(args[2]) == 0) {
|
||||
Alert("parsing [%s:%d] : 'auth' needs a user:password account.\n", file, linenum);
|
||||
return -1;
|
||||
} else if (!stats_add_auth(&curproxy->uri_auth, args[2])) {
|
||||
Alert("parsing [%s:%d] : out of memory.\n", file, linenum);
|
||||
return -1;
|
||||
}
|
||||
} else if (!strcmp(args[1], "enable")) {
|
||||
if (!stats_check_init_uri_auth(&curproxy->uri_auth)) {
|
||||
Alert("parsing [%s:%d] : out of memory.\n", file, linenum);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
Alert("parsing [%s:%d] : unknown stats parameter '%s' (expects 'uri', 'realm', 'auth' or 'enable').\n",
|
||||
file, linenum, args[0]);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else if (!strcmp(args[0], "option")) {
|
||||
if (*(args[1]) == 0) {
|
||||
Alert("parsing [%s:%d] : '%s' expects an option name.\n", file, linenum, args[0]);
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Ascii to Base64 conversion as described in RFC1421.
|
||||
* Copyright 2006 Willy Tarreau <willy@w.ods.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BASE64_H
|
||||
#define BASE64_H
|
||||
|
||||
int a2base64(char *in, int ilen, char *out, int olen);
|
||||
|
||||
#endif /* BASE64_H */
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* URI-based user authentication using the HTTP basic method.
|
||||
*
|
||||
* Copyright 2006 Willy Tarreau <willy@w.ods.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef URI_AUTH_H
|
||||
#define URI_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) */
|
||||
};
|
||||
|
||||
/* later we may link them to support multiple URI matching */
|
||||
struct uri_auth {
|
||||
int uri_len; /* the prefix length */
|
||||
char *uri_prefix; /* the prefix we want to match */
|
||||
char *auth_realm; /* the realm reported to the client */
|
||||
struct user_auth *users; /* linked list of valid user:passwd couples */
|
||||
};
|
||||
|
||||
/* This is the default statistics URI */
|
||||
#ifdef CONFIG_STATS_DEFAULT_URI
|
||||
#define STATS_DEFAULT_URI CONFIG_STATS_DEFAULT_URI
|
||||
#else
|
||||
#define STATS_DEFAULT_URI "/haproxy?stats"
|
||||
#endif
|
||||
|
||||
/* This is the default statistics realm */
|
||||
#ifdef CONFIG_STATS_DEFAULT_REALM
|
||||
#define STATS_DEFAULT_REALM CONFIG_STATS_DEFAULT_REALM
|
||||
#else
|
||||
#define STATS_DEFAULT_REALM "HAProxy Statistics"
|
||||
#endif
|
||||
|
||||
|
||||
/* Various functions used to set the fields during the configuration parsing.
|
||||
* Please that all those function can initialize the root entry in order not to
|
||||
* force the user to respect a certain order in the configuration file.
|
||||
*
|
||||
* Default values are used during initialization. Check STATS_DEFAULT_* for
|
||||
* more information.
|
||||
*/
|
||||
struct uri_auth *stats_check_init_uri_auth(struct uri_auth **root);
|
||||
struct uri_auth *stats_set_uri(struct uri_auth **root, char *uri);
|
||||
struct uri_auth *stats_set_realm(struct uri_auth **root, char *realm);
|
||||
struct uri_auth *stats_add_auth(struct uri_auth **root, char *user);
|
||||
|
||||
#endif /* URI_AUTH_H */
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Ascii to Base64 conversion as described in RFC1421.
|
||||
* Copyright 2006 Willy Tarreau <willy@w.ods.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <include/base64.h>
|
||||
|
||||
|
||||
/* Encodes <ilen> bytes from <in> to <out> for at most <olen> chars (including
|
||||
* the trailing zero). Returns the number of bytes written. No check is made
|
||||
* for <in> or <out> to be NULL. Returns negative value if <olen> is too short
|
||||
* to accept <ilen>. 4 output bytes are produced for 1 to 3 input bytes.
|
||||
*/
|
||||
int a2base64(char *in, int ilen, char *out, int olen)
|
||||
{
|
||||
char base64[64]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
int convlen;
|
||||
|
||||
convlen = ((ilen + 2) / 3) * 4;
|
||||
|
||||
if (convlen >= olen)
|
||||
return -1;
|
||||
|
||||
/* we don't need to check olen anymore */
|
||||
while (ilen >= 3) {
|
||||
out[0] = base64[(((unsigned char)in[0]) >> 2)];
|
||||
out[1] = base64[(((unsigned char)in[0] & 0x03) << 4) | (((unsigned char)in[1]) >> 4)];
|
||||
out[2] = base64[(((unsigned char)in[1] & 0x0F) << 2) | (((unsigned char)in[2]) >> 6)];
|
||||
out[3] = base64[(((unsigned char)in[2] & 0x3F))];
|
||||
out += 4;
|
||||
in += 3; ilen -= 3;
|
||||
}
|
||||
|
||||
if (!ilen) {
|
||||
out[0] = '\0';
|
||||
} else {
|
||||
out[0] = base64[((unsigned char)in[0]) >> 2];
|
||||
if (ilen == 1) {
|
||||
out[1] = base64[((unsigned char)in[0] & 0x03) << 4];
|
||||
out[2] = '=';
|
||||
} else {
|
||||
out[1] = base64[(((unsigned char)in[0] & 0x03) << 4) |
|
||||
(((unsigned char)in[1]) >> 4)];
|
||||
out[2] = base64[((unsigned char)in[1] & 0x0F) << 2];
|
||||
}
|
||||
out[3] = '=';
|
||||
out[4] = '\0';
|
||||
}
|
||||
|
||||
return convlen;
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* URI-based user authentication using the HTTP basic method.
|
||||
*
|
||||
* Copyright 2006 Willy Tarreau <willy@w.ods.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version
|
||||
* 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <include/uri_auth.h>
|
||||
#include <include/base64.h>
|
||||
|
||||
|
||||
/*
|
||||
* Initializes a basic uri_auth structure header and returns a pointer to it.
|
||||
* Uses the pointer provided if not NULL and not initialized.
|
||||
*/
|
||||
struct uri_auth *stats_check_init_uri_auth(struct uri_auth **root)
|
||||
{
|
||||
struct uri_auth *u;
|
||||
|
||||
if (!root || !*root) {
|
||||
if ((u = (struct uri_auth *)calloc(1, sizeof (*u))) == NULL)
|
||||
goto out_u;
|
||||
} else
|
||||
u = *root;
|
||||
|
||||
if (!u->uri_prefix) {
|
||||
u->uri_len = strlen(STATS_DEFAULT_URI);
|
||||
if ((u->uri_prefix = strdup(STATS_DEFAULT_URI)) == NULL)
|
||||
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);
|
||||
out_u:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a default uri_auth with <uri> set as the uri_prefix.
|
||||
* Uses the pointer provided if not NULL and not initialized.
|
||||
*/
|
||||
struct uri_auth *stats_set_uri(struct uri_auth **root, char *uri)
|
||||
{
|
||||
struct uri_auth *u;
|
||||
char *uri_copy;
|
||||
int uri_len;
|
||||
|
||||
uri_len = strlen(uri);
|
||||
if ((uri_copy = strdup(uri)) == NULL)
|
||||
goto out_uri;
|
||||
|
||||
if ((u = stats_check_init_uri_auth(root)) == NULL)
|
||||
goto out_u;
|
||||
|
||||
if (u->uri_prefix)
|
||||
free(u->uri_prefix);
|
||||
|
||||
u->uri_len = uri_len;
|
||||
u->uri_prefix = uri_copy;
|
||||
return u;
|
||||
|
||||
out_u:
|
||||
free(uri_copy);
|
||||
out_uri:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a default uri_auth with <realm> set as the realm.
|
||||
* Uses the pointer provided if not NULL and not initialized.
|
||||
*/
|
||||
struct uri_auth *stats_set_realm(struct uri_auth **root, char *realm)
|
||||
{
|
||||
struct uri_auth *u;
|
||||
char *realm_copy;
|
||||
|
||||
if ((realm_copy = strdup(realm)) == NULL)
|
||||
goto out_realm;
|
||||
|
||||
if ((u = stats_check_init_uri_auth(root)) == NULL)
|
||||
goto out_u;
|
||||
|
||||
if (u->auth_realm)
|
||||
free(u->auth_realm);
|
||||
|
||||
u->auth_realm = realm_copy;
|
||||
return u;
|
||||
|
||||
out_u:
|
||||
free(realm_copy);
|
||||
out_realm:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a default uri_auth with a <user:passwd> entry added to the list of
|
||||
* 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 *u;
|
||||
char *auth_base64;
|
||||
int alen, blen;
|
||||
struct user_auth *users, **ulist;
|
||||
|
||||
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;
|
||||
|
||||
if ((u = stats_check_init_uri_auth(root)) == NULL)
|
||||
goto out_base64;
|
||||
|
||||
ulist = &u->users;
|
||||
while ((users = *ulist)) {
|
||||
if (!strcmp(users->user_pwd, auth_base64))
|
||||
break;
|
||||
ulist = &users->next;
|
||||
}
|
||||
|
||||
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