[MINOR] generic auth support with groups and encrypted passwords

Add generic authentication & authorization support.

Groups are implemented as bitmaps so the count is limited to
sizeof(int)*8 == 32.

Encrypted passwords are supported with libcrypt and crypt(3), so it is
possible to use any method supported by your system. For example modern
Linux/glibc instalations support MD5/SHA-256/SHA-512 and of course classic,
DES-based encryption.
This commit is contained in:
Krzysztof Piotr Oledzki 2010-01-29 17:50:44 +01:00 committed by Willy Tarreau
parent fccbdc8421
commit 961050465e
8 changed files with 633 additions and 3 deletions

View File

@ -108,7 +108,8 @@ SMALL_OPTS =
#### Debug settings
# You can enable debugging on specific code parts by setting DEBUG=-DDEBUG_xxx.
# Currently defined DEBUG macros include DEBUG_FULL, DEBUG_MEMORY, DEBUG_FSM,
# and DEBUG_HASH. Please check sources for exact meaning or do not use at all.
# DEBUG_HASH and DEBUG_AUTH. Please check sources for exact meaning or do not
# use at all.
DEBUG =
#### Additional include and library dirs
@ -170,6 +171,7 @@ ifeq ($(TARGET),linux22)
USE_GETSOCKNAME = implicit
USE_POLL = implicit
USE_TPROXY = implicit
USE_LIBCRYPT = implicit
else
ifeq ($(TARGET),linux24)
# This is for standard Linux 2.4 with netfilter but without epoll()
@ -177,6 +179,7 @@ ifeq ($(TARGET),linux24)
USE_NETFILTER = implicit
USE_POLL = implicit
USE_TPROXY = implicit
USE_LIBCRYPT = implicit
else
ifeq ($(TARGET),linux24e)
# This is for enhanced Linux 2.4 with netfilter and epoll() patch > 0.21
@ -187,6 +190,7 @@ ifeq ($(TARGET),linux24e)
USE_SEPOLL = implicit
USE_MY_EPOLL = implicit
USE_TPROXY = implicit
USE_LIBCRYPT = implicit
else
ifeq ($(TARGET),linux26)
# This is for standard Linux 2.6 with netfilter and standard epoll()
@ -196,6 +200,7 @@ ifeq ($(TARGET),linux26)
USE_EPOLL = implicit
USE_SEPOLL = implicit
USE_TPROXY = implicit
USE_LIBCRYPT = implicit
else
ifeq ($(TARGET),solaris)
# This is for Solaris 8
@ -209,6 +214,7 @@ ifeq ($(TARGET),freebsd)
USE_POLL = implicit
USE_KQUEUE = implicit
USE_TPROXY = implicit
USE_LIBCRYPT = implicit
else
ifeq ($(TARGET),openbsd)
# This is for OpenBSD >= 3.0
@ -324,6 +330,12 @@ OPTIONS_CFLAGS += -DCONFIG_HAP_LINUX_TPROXY
BUILD_OPTIONS += $(call ignore_implicit,USE_LINUX_TPROXY)
endif
ifneq ($(USE_LIBCRYPT),)
OPTIONS_CFLAGS += -DCONFIG_HAP_CRYPT
BUILD_OPTIONS += $(call ignore_implicit,USE_LIBCRYPT)
OPTIONS_LDFLAGS += -lcrypt
endif
ifneq ($(USE_POLL),)
OPTIONS_CFLAGS += -DENABLE_POLL
OPTIONS_OBJS += src/ev_poll.o
@ -464,7 +476,7 @@ OBJS = src/haproxy.o src/sessionhash.o src/base64.o src/protocols.o \
src/lb_chash.o src/lb_fwlc.o src/lb_fwrr.o src/lb_map.o \
src/stream_interface.o src/dumpstats.o src/proto_tcp.o \
src/session.o src/hdr_idx.o src/ev_select.o src/signal.o \
src/acl.o src/pattern.o src/memory.o src/freq_ctr.o
src/acl.o src/pattern.o src/memory.o src/freq_ctr.o src/auth.o
EBTREE_OBJS = $(EBTREE_DIR)/ebtree.o \
$(EBTREE_DIR)/eb32tree.o $(EBTREE_DIR)/eb64tree.o \

View File

@ -32,6 +32,7 @@
#define CFG_NONE 0
#define CFG_GLOBAL 1
#define CFG_LISTEN 2
#define CFG_USERLIST 3
struct cfg_keyword {
int section; /* section type for this keyword */

View File

@ -15,6 +15,8 @@
#include <common/config.h>
#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 */
@ -46,6 +48,7 @@ struct uri_auth {
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 uri_auth *next; /* Used at deinit() to build a list of unique elements */
};

36
include/proto/auth.h Normal file
View File

@ -0,0 +1,36 @@
/*
* User authentication & authorization.
*
* Copyright 2010 Krzysztof Piotr Oledzki <ole@ans.pl>
*
* 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 _PROTO_AUTH_H
#define _PROTO_AUTH_H
#include <common/config.h>
#include <types/auth.h>
extern struct userlist *userlist;
struct userlist *auth_find_userlist(char *name);
unsigned int auth_resolve_groups(struct userlist *l, char *groups);
struct req_acl_rule *parse_auth_cond(const char **args, const char *file, int linenum, struct list *known_acl, int *acl_requires);
void userlist_free(struct userlist *ul);
void req_acl_free(struct list *r);
int acl_match_auth(struct acl_test *test, struct acl_pattern *pattern);
#endif /* _PROTO_AUTH_H */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*/

73
include/types/auth.h Normal file
View File

@ -0,0 +1,73 @@
/*
* User authentication & authorization.
*
* Copyright 2010 Krzysztof Piotr Oledzki <ole@ans.pl>
*
* 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 _TYPES_AUTH_H
#define _TYPES_AUTH_H
#include <common/config.h>
#include <common/mini-clist.h>
#include <types/auth.h>
#define MAX_AUTH_GROUPS (unsigned int)(sizeof(int)*8)
#define AU_O_INSECURE 0x00000001 /* insecure, unencrypted password */
enum {
PR_REQ_ACL_ACT_UNKNOWN = 0,
PR_REQ_ACL_ACT_ALLOW,
PR_REQ_ACL_ACT_DENY,
PR_REQ_ACL_ACT_HTTP_AUTH,
PR_REQ_ACL_ACT_MAX
};
struct req_acl_rule {
struct list list;
struct acl_cond *cond; /* acl condition to meet */
unsigned int action;
union {
struct {
char *realm;
} http_auth;
};
};
struct auth_users {
struct auth_users *next;
unsigned int flags;
char *user, *pass;
union {
char *groups;
unsigned int group_mask;
};
};
struct userlist {
struct userlist *next;
char *name;
struct auth_users *users;
int grpcnt;
char *groups[MAX_AUTH_GROUPS];
char **groupusers;
};
#endif /* _TYPES_AUTH_H */
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*/

241
src/auth.c Normal file
View File

@ -0,0 +1,241 @@
/*
* User authentication & authorization
*
* Copyright 2010 Krzysztof Piotr Oledzki <ole@ans.pl>
*
* 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.
*
*/
#define _XOPEN_SOURCE 500
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <common/config.h>
#include <proto/acl.h>
#include <proto/log.h>
#include <types/auth.h>
struct userlist *userlist = NULL; /* list of all existing userlists */
/* find targets for selected gropus. The function returns pointer to
* the userlist struct ot NULL if name is NULL/empty or unresolvable.
*/
struct userlist *
auth_find_userlist(char *name)
{
struct userlist *l;
if (!name || !*name)
return NULL;
for (l = userlist; l; l = l->next)
if (!strcmp(l->name, name))
return l;
return NULL;
}
/* find group_mask for selected gropus. The function returns 1 if OK or nothing to do,
* 0 if case of unresolved groupname.
* WARING: the function destroys the list (strtok), so it can only be used once.
*/
unsigned int
auth_resolve_groups(struct userlist *l, char *groups)
{
char *group = NULL;
unsigned int g, group_mask = 0;
if (!groups || !*groups)
return 0;
while ((group = strtok(group?NULL:groups," "))) {
for (g = 0; g < l->grpcnt; g++)
if (!strcmp(l->groups[g], group))
break;
if (g == l->grpcnt) {
Alert("No such group '%s' in userlist '%s'.\n",
group, l->name);
return 0;
}
group_mask |= (1 << g);
}
return group_mask;
}
struct req_acl_rule *
parse_auth_cond(const char **args, const char *file, int linenum, struct list *known_acl, int *acl_requires)
{
struct req_acl_rule *req_acl;
int cur_arg;
req_acl = (struct req_acl_rule*)calloc(1, sizeof(struct req_acl_rule));
if (!req_acl) {
Alert("parsing [%s:%d]: out of memory.\n", file, linenum);
return NULL;
}
if (!*args[0]) {
goto req_error_parsing;
} else if (!strcmp(args[0], "allow")) {
req_acl->action = PR_REQ_ACL_ACT_ALLOW;
cur_arg = 1;
} else if (!strcmp(args[0], "deny")) {
req_acl->action = PR_REQ_ACL_ACT_DENY;
cur_arg = 1;
} else if (!strcmp(args[0], "auth")) {
req_acl->action = PR_REQ_ACL_ACT_HTTP_AUTH;
cur_arg = 1;
while(*args[cur_arg]) {
if (!strcmp(args[cur_arg], "realm")) {
req_acl->http_auth.realm = strdup(args[cur_arg + 1]);
cur_arg+=2;
continue;
} else
break;
}
} else {
req_error_parsing:
Alert("parsing [%s:%d]: %s '%s', expects 'allow', 'deny', 'auth'.\n",
file, linenum, *args[1]?"unknown parameter":"missing keyword in", args[*args[1]?1:0]);
return NULL;
}
if (*args[cur_arg]) {
int pol = ACL_COND_NONE;
struct acl_cond *cond;
if (!strcmp(args[cur_arg], "if"))
pol = ACL_COND_IF;
else if (!strcmp(args[cur_arg], "unless"))
pol = ACL_COND_UNLESS;
else {
Alert("parsing [%s:%d]: '%s' expects 'realm' for 'auth' or"
" either 'if' or 'unless' followed by a condition but found '%s'.\n",
file, linenum, args[0], args[cur_arg]);
return NULL;
}
if ((cond = parse_acl_cond((const char **)args + cur_arg + 1, known_acl, pol)) == NULL) {
Alert("parsing [%s:%d]: error detected while parsing 'req' condition.\n",
file, linenum);
return NULL;
}
cond->file = file;
cond->line = linenum;
*acl_requires |= cond->requires;
req_acl->cond = cond;
}
return req_acl;
}
void
userlist_free(struct userlist *ul)
{
struct userlist *tul;
struct auth_users *au, *tau;
int i;
while (ul) {
au = ul->users;
while (au) {
tau = au;
au = au->next;
free(tau->user);
free(tau->pass);
free(tau);
}
tul = ul;
ul = ul->next;
for (i = 0; i < tul->grpcnt; i++)
free(tul->groups[i]);
free(tul->name);
free(tul);
};
}
void
req_acl_free(struct list *r) {
struct req_acl_rule *tr, *pr;
list_for_each_entry_safe(pr, tr, r, list) {
LIST_DEL(&pr->list);
if (pr->action == PR_REQ_ACL_ACT_HTTP_AUTH)
free(pr->http_auth.realm);
free(pr);
}
}
/*
* Authenticate and authorize user; return 1 if OK, 0 if case of error.
*/
int
check_user(struct userlist *ul, unsigned int group_mask, const char *user, const char *pass)
{
struct auth_users *u;
const char *ep;
#ifdef DEBUG_AUTH
fprintf(stderr, "req: userlist=%s, user=%s, pass=%s, group_mask=%u\n",
ul->name, user, pass, group_mask);
#endif
for (u = ul->users; u; u = u->next)
if (!strcmp(user, u->user))
break;
if (!u)
return 0;
#ifdef DEBUG_AUTH
fprintf(stderr, "cfg: user=%s, pass=%s, group_mask=%u, flags=%X",
u->user, u->pass, u->group_mask, u->flags);
#endif
/*
* if user matches but group does not,
* it makes no sens to check passwords
*/
if (group_mask && !(group_mask & u->group_mask))
return 0;
if (!(u->flags & AU_O_INSECURE)) {
#ifdef CONFIG_HAP_CRYPT
ep = crypt(pass, u->pass);
#else
return 0;
#endif
} else
ep = pass;
#ifdef DEBUG_AUTH
fprintf(stderr, ", crypt=%s\n", ep);
#endif
if (!strcmp(ep, u->pass))
return 1;
else
return 0;
}

View File

@ -37,6 +37,7 @@
#include <types/global.h>
#include <proto/acl.h>
#include <proto/auth.h>
#include <proto/backend.h>
#include <proto/buffers.h>
#include <proto/checks.h>
@ -4008,6 +4009,175 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
return err_code;
}
int
cfg_parse_users(const char *file, int linenum, char **args, int kwm)
{
int err_code = 0;
const char *err;
if (!strcmp(args[0], "userlist")) { /* new userlist */
struct userlist *newul;
if (!*args[1]) {
Alert("parsing [%s:%d]: '%s' expects <name> as arguments.\n",
file, linenum, args[0]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
err = invalid_char(args[1]);
if (err) {
Alert("parsing [%s:%d]: character '%c' is not permitted in '%s' name '%s'.\n",
file, linenum, *err, args[0], args[1]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
for (newul = userlist; newul; newul = newul->next)
if (!strcmp(newul->name, args[1])) {
Warning("parsing [%s:%d]: ignoring duplicated userlist '%s'.\n",
file, linenum, args[1]);
err_code |= ERR_WARN;
goto out;
}
newul = (struct userlist *)calloc(1, sizeof(struct userlist));
if (!newul) {
Alert("parsing [%s:%d]: out of memory.\n", file, linenum);
err_code |= ERR_ALERT | ERR_ABORT;
goto out;
}
newul->groupusers = calloc(MAX_AUTH_GROUPS, sizeof(char *));
newul->name = strdup(args[1]);
if (!newul->groupusers | !newul->name) {
Alert("parsing [%s:%d]: out of memory.\n", file, linenum);
err_code |= ERR_ALERT | ERR_ABORT;
goto out;
}
newul->next = userlist;
userlist = newul;
} else if (!strcmp(args[0], "group")) { /* new group */
int cur_arg, i;
const char *err;
if (!*args[1]) {
Alert("parsing [%s:%d]: '%s' expects <name> as arguments.\n",
file, linenum, args[0]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
err = invalid_char(args[1]);
if (err) {
Alert("parsing [%s:%d]: character '%c' is not permitted in '%s' name '%s'.\n",
file, linenum, *err, args[0], args[1]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
for(i = 0; i < userlist->grpcnt; i++)
if (!strcmp(userlist->groups[i], args[1])) {
Warning("parsing [%s:%d]: ignoring duplicated group '%s' in userlist '%s'.\n",
file, linenum, args[1], userlist->name);
err_code |= ERR_ALERT;
goto out;
}
if (userlist->grpcnt >= MAX_AUTH_GROUPS) {
Alert("parsing [%s:%d]: too many groups (%u) in in userlist '%s' while adding group '%s'.\n",
file, linenum, MAX_AUTH_GROUPS, userlist->name, args[1]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
cur_arg = 2;
while (*args[cur_arg]) {
if (!strcmp(args[cur_arg], "users")) {
userlist->groupusers[userlist->grpcnt] = strdup(args[cur_arg + 1]);
cur_arg += 2;
continue;
} else {
Alert("parsing [%s:%d]: '%s' only supports 'users' option.\n",
file, linenum, args[0]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
}
userlist->groups[userlist->grpcnt++] = strdup(args[1]);
} else if (!strcmp(args[0], "user")) { /* new user */
struct auth_users *newuser;
int cur_arg;
if (!*args[1]) {
Alert("parsing [%s:%d]: '%s' expects <name> as arguments.\n",
file, linenum, args[0]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
for (newuser = userlist->users; newuser; newuser = newuser->next)
if (!strcmp(newuser->user, args[1])) {
Warning("parsing [%s:%d]: ignoring duplicated user '%s' in userlist '%s'.\n",
file, linenum, args[1], userlist->name);
err_code |= ERR_ALERT;
goto out;
}
newuser = (struct auth_users *)calloc(1, sizeof(struct auth_users));
if (!newuser) {
Alert("parsing [%s:%d]: out of memory.\n", file, linenum);
err_code |= ERR_ALERT | ERR_ABORT;
goto out;
}
newuser->user = strdup(args[1]);
newuser->next = userlist->users;
userlist->users = newuser;
cur_arg = 2;
while (*args[cur_arg]) {
if (!strcmp(args[cur_arg], "password")) {
#ifndef CONFIG_HAP_CRYPT
Warning("parsing [%s:%d]: no crypt(3) support compiled, encrypted passwords will not work.\n",
file, linenum);
err_code |= ERR_ALERT;
#endif
newuser->pass = strdup(args[cur_arg + 1]);
cur_arg += 2;
continue;
} else if (!strcmp(args[cur_arg], "insecure-password")) {
newuser->pass = strdup(args[cur_arg + 1]);
newuser->flags |= AU_O_INSECURE;
cur_arg += 2;
continue;
} else if (!strcmp(args[cur_arg], "groups")) {
newuser->groups = strdup(args[cur_arg + 1]);
cur_arg += 2;
continue;
} else {
Alert("parsing [%s:%d]: '%s' only supports 'password', 'insecure-password' and 'groups' options.\n",
file, linenum, args[0]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
}
} else {
Alert("parsing [%s:%d]: unknown keyword '%s' in '%s' section\n", file, linenum, args[0], "users");
err_code |= ERR_ALERT | ERR_FATAL;
}
out:
return err_code;
}
/*
* This function reads and parses the configuration file given in the argument.
@ -4172,6 +4342,10 @@ int readcfgfile(const char *file)
confsect = CFG_GLOBAL;
free(cursection);
cursection = strdup(args[0]);
} else if (!strcmp(args[0], "userlist")) {
confsect = CFG_USERLIST;
free(cursection);
cursection = strdup(args[0]);
}
/* else it's a section keyword */
@ -4182,8 +4356,11 @@ int readcfgfile(const char *file)
case CFG_GLOBAL:
err_code |= cfg_parse_global(file, linenum, args, kwm);
break;
case CFG_USERLIST:
err_code |= cfg_parse_users(file, linenum, args, kwm);
break;
default:
Alert("parsing [%s:%d] : unknown keyword '%s' out of section.\n", file, linenum, args[0]);
Alert("parsing [%s:%d]: unknown keyword '%s' out of section.\n", file, linenum, args[0]);
err_code |= ERR_ALERT | ERR_FATAL;
}
@ -4210,6 +4387,7 @@ int check_config_validity()
int cfgerr = 0;
struct proxy *curproxy = NULL;
struct server *newsrv = NULL;
struct userlist *curuserlist = NULL;
int err_code = 0;
unsigned int next_pxid = 1;
@ -4817,6 +4995,78 @@ int check_config_validity()
curproxy = curproxy->next;
}
for (curuserlist = userlist; curuserlist; curuserlist = curuserlist->next) {
struct auth_users *curuser;
int g;
for (curuser = curuserlist->users; curuser; curuser = curuser->next) {
unsigned int group_mask = 0;
char *group = NULL;
if (!curuser->groups)
continue;
while ((group = strtok(group?NULL:curuser->groups, ","))) {
for (g = 0; g < curuserlist->grpcnt; g++)
if (!strcmp(curuserlist->groups[g], group))
break;
if (g == curuserlist->grpcnt) {
Alert("userlist '%s': no such group '%s' specified in user '%s'\n",
curuserlist->name, group, curuser->user);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
group_mask |= (1 << g);
}
free(curuser->groups);
curuser->group_mask = group_mask;
}
for (g = 0; g < curuserlist->grpcnt; g++) {
char *user = NULL;
if (!curuserlist->groupusers[g])
continue;
while ((user = strtok(user?NULL:curuserlist->groupusers[g], ","))) {
for (curuser = curuserlist->users; curuser; curuser = curuser->next)
if (!strcmp(curuser->user, user))
break;
if (!curuser) {
Alert("userlist '%s': no such user '%s' specified in group '%s'\n",
curuserlist->name, user, curuserlist->groups[g]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
curuser->group_mask |= (1 << g);
}
free(curuserlist->groupusers[g]);
}
free(curuserlist->groupusers);
#ifdef DEBUG_AUTH
for (g = 0; g < curuserlist->grpcnt; g++) {
fprintf(stderr, "group %s, id %d, mask %08X, users:", curuserlist->groups[g], g , 1 << g);
for (curuser = curuserlist->users; curuser; curuser = curuser->next) {
if (curuser->group_mask & (1 << g))
fprintf(stderr, " %s", curuser->user);
}
fprintf(stderr, "\n");
}
#endif
}
/*
* Recount currently required checks.
*/

View File

@ -67,6 +67,7 @@
#include <types/capture.h>
#include <types/global.h>
#include <proto/auth.h>
#include <proto/acl.h>
#include <proto/backend.h>
#include <proto/buffers.h>
@ -177,6 +178,16 @@ void display_build_opts()
"\n\n",
DEFAULT_MAXCONN, BUFSIZE, MAXREWRITE, MAX_POLL_EVENTS);
printf("Encrypted password support via crypt(3): "
#ifdef CONFIG_HAP_CRYPT
"yes"
#else
"no"
#endif
"\n");
putchar('\n');
list_pollers(stdout);
putchar('\n');
}
@ -851,6 +862,7 @@ void deinit(void)
pool_destroy2(p->req_cap_pool);
pool_destroy2(p->rsp_cap_pool);
pool_destroy2(p->hdr_idx_pool);
p0 = p;
p = p->next;
free(p0);
@ -874,6 +886,8 @@ void deinit(void)
free(uap);
}
userlist_free(userlist);
protocol_unbind_all();
free(global.chroot); global.chroot = NULL;