diff options
-rw-r--r-- | Makefile | 6 | ||||
-rw-r--r-- | acl.c | 185 | ||||
-rw-r--r-- | acl.h | 74 | ||||
-rwxr-xr-x | configure | 18 | ||||
-rw-r--r-- | monitor.c | 96 | ||||
-rw-r--r-- | qemu-doc.texi | 49 | ||||
-rw-r--r-- | vnc-auth-sasl.c | 16 | ||||
-rw-r--r-- | vnc-auth-sasl.h | 7 | ||||
-rw-r--r-- | vnc.c | 21 | ||||
-rw-r--r-- | vnc.h | 3 |
10 files changed, 470 insertions, 5 deletions
@@ -144,7 +144,7 @@ endif ifdef CONFIG_CURSES OBJS+=curses.o endif -OBJS+=vnc.o d3des.o +OBJS+=vnc.o acl.o d3des.o ifdef CONFIG_VNC_TLS OBJS+=vnc-tls.o vnc-auth-vencrypt.o endif @@ -174,9 +174,11 @@ sdl.o: sdl.c keymaps.h sdl_keysym.h sdl.o audio/sdlaudio.o: CFLAGS += $(SDL_CFLAGS) +acl.o: acl.h acl.c + vnc.h: vnc-tls.h vnc-auth-vencrypt.h vnc-auth-sasl.h keymaps.h -vnc.o: vnc.c vnc.h vnc_keysym.h vnchextile.h d3des.c d3des.h +vnc.o: vnc.c vnc.h vnc_keysym.h vnchextile.h d3des.c d3des.h acl.h vnc.o: CFLAGS += $(CONFIG_VNC_TLS_CFLAGS) @@ -0,0 +1,185 @@ +/* + * QEMU access control list management + * + * Copyright (C) 2009 Red Hat, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#include "qemu-common.h" +#include "sysemu.h" +#include "acl.h" + +#ifdef HAVE_FNMATCH_H +#include <fnmatch.h> +#endif + + +static unsigned int nacls = 0; +static qemu_acl **acls = NULL; + + + +qemu_acl *qemu_acl_find(const char *aclname) +{ + int i; + for (i = 0 ; i < nacls ; i++) { + if (strcmp(acls[i]->aclname, aclname) == 0) + return acls[i]; + } + + return NULL; +} + +qemu_acl *qemu_acl_init(const char *aclname) +{ + qemu_acl *acl; + + acl = qemu_acl_find(aclname); + if (acl) + return acl; + + acl = qemu_malloc(sizeof(*acl)); + acl->aclname = qemu_strdup(aclname); + /* Deny by default, so there is no window of "open + * access" between QEMU starting, and the user setting + * up ACLs in the monitor */ + acl->defaultDeny = 1; + + acl->nentries = 0; + TAILQ_INIT(&acl->entries); + + acls = qemu_realloc(acls, sizeof(*acls) * (nacls +1)); + acls[nacls] = acl; + nacls++; + + return acl; +} + +int qemu_acl_party_is_allowed(qemu_acl *acl, + const char *party) +{ + qemu_acl_entry *entry; + + TAILQ_FOREACH(entry, &acl->entries, next) { +#ifdef HAVE_FNMATCH_H + if (fnmatch(entry->match, party, 0) == 0) + return entry->deny ? 0 : 1; +#else + /* No fnmatch, so fallback to exact string matching + * instead of allowing wildcards */ + if (strcmp(entry->match, party) == 0) + return entry->deny ? 0 : 1; +#endif + } + + return acl->defaultDeny ? 0 : 1; +} + + +void qemu_acl_reset(qemu_acl *acl) +{ + qemu_acl_entry *entry; + + /* Put back to deny by default, so there is no window + * of "open access" while the user re-initializes the + * access control list */ + acl->defaultDeny = 1; + TAILQ_FOREACH(entry, &acl->entries, next) { + TAILQ_REMOVE(&acl->entries, entry, next); + free(entry->match); + free(entry); + } + acl->nentries = 0; +} + + +int qemu_acl_append(qemu_acl *acl, + int deny, + const char *match) +{ + qemu_acl_entry *entry; + + entry = qemu_malloc(sizeof(*entry)); + entry->match = qemu_strdup(match); + entry->deny = deny; + + TAILQ_INSERT_TAIL(&acl->entries, entry, next); + acl->nentries++; + + return acl->nentries; +} + + +int qemu_acl_insert(qemu_acl *acl, + int deny, + const char *match, + int index) +{ + qemu_acl_entry *entry; + qemu_acl_entry *tmp; + int i = 0; + + if (index <= 0) + return -1; + if (index >= acl->nentries) + return qemu_acl_append(acl, deny, match); + + + entry = qemu_malloc(sizeof(*entry)); + entry->match = qemu_strdup(match); + entry->deny = deny; + + TAILQ_FOREACH(tmp, &acl->entries, next) { + i++; + if (i == index) { + TAILQ_INSERT_BEFORE(tmp, entry, next); + acl->nentries++; + break; + } + } + + return i; +} + +int qemu_acl_remove(qemu_acl *acl, + const char *match) +{ + qemu_acl_entry *entry; + int i = 0; + + TAILQ_FOREACH(entry, &acl->entries, next) { + i++; + if (strcmp(entry->match, match) == 0) { + TAILQ_REMOVE(&acl->entries, entry, next); + return i; + } + } + return -1; +} + + +/* + * Local variables: + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 8 + * End: + */ @@ -0,0 +1,74 @@ +/* + * QEMU access control list management + * + * Copyright (C) 2009 Red Hat, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __QEMU_ACL_H__ +#define __QEMU_ACL_H__ + +#include "sys-queue.h" + +typedef struct qemu_acl_entry qemu_acl_entry; +typedef struct qemu_acl qemu_acl; + +struct qemu_acl_entry { + char *match; + int deny; + + TAILQ_ENTRY(qemu_acl_entry) next; +}; + +struct qemu_acl { + char *aclname; + unsigned int nentries; + TAILQ_HEAD(,qemu_acl_entry) entries; + int defaultDeny; +}; + +qemu_acl *qemu_acl_init(const char *aclname); + +qemu_acl *qemu_acl_find(const char *aclname); + +int qemu_acl_party_is_allowed(qemu_acl *acl, + const char *party); + +void qemu_acl_reset(qemu_acl *acl); + +int qemu_acl_append(qemu_acl *acl, + int deny, + const char *match); +int qemu_acl_insert(qemu_acl *acl, + int deny, + const char *match, + int index); +int qemu_acl_remove(qemu_acl *acl, + const char *match); + +#endif /* __QEMU_ACL_H__ */ + +/* + * Local variables: + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 8 + * End: + */ @@ -862,6 +862,21 @@ EOF fi ########################################## +# fnmatch() probe, used for ACL routines +fnmatch="no" +cat > $TMPC << EOF +#include <fnmatch.h> +int main(void) +{ + fnmatch("foo", "foo", 0); + return 0; +} +EOF +if $cc $ARCH_CFLAGS -o $TMPE $TMPC > /dev/null 2> /dev/null ; then + fnmatch="yes" +fi + +########################################## # vde libraries probe if test "$vde" = "yes" ; then cat > $TMPC << EOF @@ -1421,6 +1436,9 @@ if test "$vnc_sasl" = "yes" ; then echo "CONFIG_VNC_SASL_LIBS=$vnc_sasl_libs" >> $config_mak echo "#define CONFIG_VNC_SASL 1" >> $config_h fi +if test "$fnmatch" = "yes" ; then + echo "#define HAVE_FNMATCH_H 1" >> $config_h +fi qemu_version=`head $source_path/VERSION` echo "VERSION=$qemu_version" >>$config_mak echo "#define QEMU_VERSION \"$qemu_version\"" >> $config_h @@ -41,6 +41,7 @@ #include "qemu-timer.h" #include "migration.h" #include "kvm.h" +#include "acl.h" //#define DEBUG //#define DEBUG_COMPLETION @@ -1532,6 +1533,86 @@ static void do_info_balloon(Monitor *mon) monitor_printf(mon, "balloon: actual=%d\n", (int)(actual >> 20)); } +static void do_acl(Monitor *mon, + const char *command, + const char *aclname, + const char *match, + int has_index, + int index) +{ + qemu_acl *acl; + + acl = qemu_acl_find(aclname); + if (!acl) { + monitor_printf(mon, "acl: unknown list '%s'\n", aclname); + return; + } + + if (strcmp(command, "show") == 0) { + int i = 0; + qemu_acl_entry *entry; + monitor_printf(mon, "policy: %s\n", + acl->defaultDeny ? "deny" : "allow"); + TAILQ_FOREACH(entry, &acl->entries, next) { + i++; + monitor_printf(mon, "%d: %s %s\n", i, + entry->deny ? "deny" : "allow", + entry->match); + } + } else if (strcmp(command, "reset") == 0) { + qemu_acl_reset(acl); + monitor_printf(mon, "acl: removed all rules\n"); + } else if (strcmp(command, "policy") == 0) { + if (!match) { + monitor_printf(mon, "acl: missing policy parameter\n"); + return; + } + + if (strcmp(match, "allow") == 0) { + acl->defaultDeny = 0; + monitor_printf(mon, "acl: policy set to 'allow'\n"); + } else if (strcmp(match, "deny") == 0) { + acl->defaultDeny = 1; + monitor_printf(mon, "acl: policy set to 'deny'\n"); + } else { + monitor_printf(mon, "acl: unknown policy '%s', expected 'deny' or 'allow'\n", match); + } + } else if ((strcmp(command, "allow") == 0) || + (strcmp(command, "deny") == 0)) { + int deny = strcmp(command, "deny") == 0 ? 1 : 0; + int ret; + + if (!match) { + monitor_printf(mon, "acl: missing match parameter\n"); + return; + } + + if (has_index) + ret = qemu_acl_insert(acl, deny, match, index); + else + ret = qemu_acl_append(acl, deny, match); + if (ret < 0) + monitor_printf(mon, "acl: unable to add acl entry\n"); + else + monitor_printf(mon, "acl: added rule at position %d\n", ret); + } else if (strcmp(command, "remove") == 0) { + int ret; + + if (!match) { + monitor_printf(mon, "acl: missing match parameter\n"); + return; + } + + ret = qemu_acl_remove(acl, match); + if (ret < 0) + monitor_printf(mon, "acl: no matching acl entry\n"); + else + monitor_printf(mon, "acl: removed rule at position %d\n", ret); + } else { + monitor_printf(mon, "acl: unknown command '%s'\n", command); + } +} + /* Please update qemu-doc.texi when adding or changing commands */ static const mon_cmd_t mon_cmds[] = { { "help|?", "s?", help_cmd, @@ -1636,6 +1717,12 @@ static const mon_cmd_t mon_cmds[] = { "target", "request VM to change it's memory allocation (in MB)" }, { "set_link", "ss", do_set_link, "name [up|down]", "change the link status of a network adapter" }, + { "acl", "sss?i?", do_acl, "<command> <aclname> [<match>] [<index>]\n", + "acl show vnc.username\n" + "acl policy vnc.username deny\n" + "acl allow vnc.username fred\n" + "acl deny vnc.username bob\n" + "acl reset vnc.username\n" }, { NULL, NULL, }, }; @@ -2961,6 +3048,15 @@ static void monitor_event(void *opaque, int event) } } + +/* + * Local variables: + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 8 + * End: + */ + void monitor_init(CharDriverState *chr, int flags) { static int is_first_init = 1; diff --git a/qemu-doc.texi b/qemu-doc.texi index 1528f39..6201932 100644 --- a/qemu-doc.texi +++ b/qemu-doc.texi @@ -631,6 +631,19 @@ ensures a data encryption preventing compromise of authentication credentials. See the @ref{vnc_security} section for details on using SASL authentication. +@item acl + +Turn on access control lists for checking of the x509 client certificate +and SASL party. For x509 certs, the ACL check is made against the +certificate's distinguished name. This is something that looks like +@code{C=GB,O=ACME,L=Boston,CN=bob}. For SASL party, the ACL check is +made against the username, which depending on the SASL plugin, may +include a realm component, eg @code{bob} or @code{bob\@EXAMPLE.COM}. +When the @option{acl} flag is set, the initial access list will be +empty, with a @code{deny} policy. Thus no one will be allowed to +use the VNC server until the ACLs have been loaded. This can be +achieved using the @code{acl} monitor command. + @end table @end table @@ -1392,6 +1405,42 @@ Password: ******** @end table +@item acl @var{subcommand} @var{aclname} @var{match} @var{index} + +Manage access control lists for network services. There are currently +two named access control lists, @var{vnc.x509dname} and @var{vnc.username} +matching on the x509 client certificate distinguished name, and SASL +username respectively. + +@table @option +@item acl show <aclname> +list all the match rules in the access control list, and the default +policy +@item acl policy <aclname> @code{allow|deny} +set the default access control list policy, used in the event that +none of the explicit rules match. The default policy at startup is +always @code{deny} +@item acl allow <aclname> <match> [<index>] +add a match to the access control list, allowing access. The match will +normally be an exact username or x509 distinguished name, but can +optionally include wildcard globs. eg @code{*\@EXAMPLE.COM} to allow +all users in the @code{EXAMPLE.COM} kerberos realm. The match will +normally be appended to the end of the ACL, but can be inserted +earlier in the list if the optional @code{index} parameter is supplied. +@item acl deny <aclname> <match> [<index>] +add a match to the access control list, denying access. The match will +normally be an exact username or x509 distinguished name, but can +optionally include wildcard globs. eg @code{*\@EXAMPLE.COM} to allow +all users in the @code{EXAMPLE.COM} kerberos realm. The match will +normally be appended to the end of the ACL, but can be inserted +earlier in the list if the optional @code{index} parameter is supplied. +@item acl remove <aclname> <match> +remove the specified match rule from the access control list. +@item acl reset <aclname> +remove all matches from the access control list, and set the default +policy back to @code{deny}. +@end table + @item screendump @var{filename} Save screen into PPM image @var{filename}. diff --git a/vnc-auth-sasl.c b/vnc-auth-sasl.c index 2882a35..4b4aca9 100644 --- a/vnc-auth-sasl.c +++ b/vnc-auth-sasl.c @@ -120,22 +120,32 @@ static int vnc_auth_sasl_check_access(VncState *vs) { const void *val; int err; + int allow; err = sasl_getprop(vs->sasl.conn, SASL_USERNAME, &val); if (err != SASL_OK) { - VNC_DEBUG("cannot query SASL username on connection %d (%s)\n", + VNC_DEBUG("cannot query SASL username on connection %d (%s), denying access\n", err, sasl_errstring(err, NULL, NULL)); return -1; } if (val == NULL) { - VNC_DEBUG("no client username was found\n"); + VNC_DEBUG("no client username was found, denying access\n"); return -1; } VNC_DEBUG("SASL client username %s\n", (const char *)val); vs->sasl.username = qemu_strdup((const char*)val); - return 0; + if (vs->vd->sasl.acl == NULL) { + VNC_DEBUG("no ACL activated, allowing access\n"); + return 0; + } + + allow = qemu_acl_party_is_allowed(vs->vd->sasl.acl, vs->sasl.username); + + VNC_DEBUG("SASL client %s %s by ACL\n", vs->sasl.username, + allow ? "allowed" : "denied"); + return allow ? 0 : -1; } static int vnc_auth_sasl_check_ssf(VncState *vs) diff --git a/vnc-auth-sasl.h b/vnc-auth-sasl.h index a72973a..fd9b18a 100644 --- a/vnc-auth-sasl.h +++ b/vnc-auth-sasl.h @@ -30,6 +30,9 @@ #include <sasl/sasl.h> typedef struct VncStateSASL VncStateSASL; +typedef struct VncDisplaySASL VncDisplaySASL; + +#include "acl.h" struct VncStateSASL { sasl_conn_t *conn; @@ -56,6 +59,10 @@ struct VncStateSASL { char *mechlist; }; +struct VncDisplaySASL { + qemu_acl *acl; +}; + void vnc_sasl_client_cleanup(VncState *vs); long vnc_client_read_sasl(VncState *vs); @@ -28,6 +28,7 @@ #include "sysemu.h" #include "qemu_socket.h" #include "qemu-timer.h" +#include "acl.h" #define VNC_REFRESH_INTERVAL (1000 / 30) @@ -2083,6 +2084,7 @@ int vnc_display_open(DisplayState *ds, const char *display) int sasl = 0; int saslErr; #endif + int acl = 0; if (!vnc_display) return -1; @@ -2139,9 +2141,28 @@ int vnc_display_open(DisplayState *ds, const char *display) return -1; } #endif + } else if (strncmp(options, "acl", 3) == 0) { + acl = 1; } } +#ifdef CONFIG_VNC_TLS + if (acl && x509 && vs->tls.x509verify) { + if (!(vs->tls.acl = qemu_acl_init("vnc.x509dname"))) { + fprintf(stderr, "Failed to create x509 dname ACL\n"); + exit(1); + } + } +#endif +#ifdef CONFIG_VNC_SASL + if (acl && sasl) { + if (!(vs->sasl.acl = qemu_acl_init("vnc.username"))) { + fprintf(stderr, "Failed to create username ACL\n"); + exit(1); + } + } +#endif + /* * Combinations we support here: * @@ -99,6 +99,9 @@ struct VncDisplay int subauth; /* Used by VeNCrypt */ VncDisplayTLS tls; #endif +#ifdef CONFIG_VNC_SASL + VncDisplaySASL sasl; +#endif }; struct VncState |