From patchwork Mon Dec 19 13:11:56 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Corey Bryant X-Patchwork-Id: 132223 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [140.186.70.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 1EA03B6FDD for ; Tue, 20 Dec 2011 00:12:34 +1100 (EST) Received: from localhost ([::1]:39943 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Rcd15-0000gs-2c for incoming@patchwork.ozlabs.org; Mon, 19 Dec 2011 08:12:31 -0500 Received: from eggs.gnu.org ([140.186.70.92]:44867) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Rcd0s-0000b9-Eb for qemu-devel@nongnu.org; Mon, 19 Dec 2011 08:12:23 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Rcd0l-0006B7-UT for qemu-devel@nongnu.org; Mon, 19 Dec 2011 08:12:18 -0500 Received: from e39.co.us.ibm.com ([32.97.110.160]:35151) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Rcd0l-0006B2-MH for qemu-devel@nongnu.org; Mon, 19 Dec 2011 08:12:11 -0500 Received: from /spool/local by e39.co.us.ibm.com with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted for from ; Mon, 19 Dec 2011 06:12:11 -0700 Received: from d03relay03.boulder.ibm.com (9.17.195.228) by e39.co.us.ibm.com (192.168.1.139) with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted; Mon, 19 Dec 2011 06:12:08 -0700 Received: from d03av05.boulder.ibm.com (d03av05.boulder.ibm.com [9.17.195.85]) by d03relay03.boulder.ibm.com (8.13.8/8.13.8/NCO v10.0) with ESMTP id pBJDC7Yl158792 for ; Mon, 19 Dec 2011 06:12:07 -0700 Received: from d03av05.boulder.ibm.com (loopback [127.0.0.1]) by d03av05.boulder.ibm.com (8.14.4/8.13.1/NCO v10.0 AVout) with ESMTP id pBJDC6e3005924 for ; Mon, 19 Dec 2011 06:12:07 -0700 Received: from localhost (sig-9-65-126-99.mts.ibm.com [9.65.126.99]) by d03av05.boulder.ibm.com (8.14.4/8.13.1/NCO v10.0 AVin) with ESMTP id pBJDC6v6005877; Mon, 19 Dec 2011 06:12:06 -0700 From: Corey Bryant To: qemu-devel@nongnu.org Date: Mon, 19 Dec 2011 08:11:56 -0500 Message-Id: <1324300318-3419-3-git-send-email-coreyb@linux.vnet.ibm.com> X-Mailer: git-send-email 1.7.3.4 In-Reply-To: <1324300318-3419-1-git-send-email-coreyb@linux.vnet.ibm.com> References: <1324300318-3419-1-git-send-email-coreyb@linux.vnet.ibm.com> x-cbid: 11121913-4242-0000-0000-0000005B9FC2 X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 32.97.110.160 Cc: aliguori@us.ibm.com, rmarwah@linux.vnet.ibm.com Subject: [Qemu-devel] [PATCH v6 2/4] Add access control support to qemu bridge helper X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org We go to great lengths to restrict ourselves to just cap_net_admin as an OS enforced security mechanism. However, we further restrict what we allow users to do to simply adding a tap device to a bridge interface by virtue of the fact that this is the only functionality we expose. This is not good enough though. An administrator is likely to want to restrict the bridges that an unprivileged user can access, in particular, to restrict an unprivileged user from putting a guest on what should be isolated networks. This patch implements an ACL mechanism that is enforced by qemu-bridge-helper. The ACLs are fairly simple whitelist/blacklist mechanisms with a wildcard of 'all'. All users are blacklisted by default, and deny takes precedence over allow. An interesting feature of this ACL mechanism is that you can include external ACL files. The main reason to support this is so that you can set different file system permissions on those external ACL files. This allows an administrator to implement rather sophisticated ACL policies based on user/group policies via the file system. As an example: /etc/qemu/bridge.conf root:qemu 0640 allow br0 include /etc/qemu/alice.conf include /etc/qemu/bob.conf include /etc/qemu/charlie.conf /etc/qemu/alice.conf root:alice 0640 allow br1 /etc/qemu/bob.conf root:bob 0640 allow br2 /etc/qemu/charlie.conf root:charlie 0640 deny all This ACL pattern allows any user in the qemu group to get a tap device connected to br0 (which is bridged to the physical network). Users in the alice group can additionally get a tap device connected to br1. This allows br1 to act as a private bridge for the alice group. Users in the bob group can additionally get a tap device connected to br2. This allows br2 to act as a private bridge for the bob group. Users in the charlie group cannot get a tap device connected to any bridge. Under no circumstance can the bob group get access to br1 or can the alice group get access to br2. And under no cicumstance can the charlie group get access to any bridge. Signed-off-by: Anthony Liguori Signed-off-by: Richa Marwaha Signed-off-by: Corey Bryant --- qemu-bridge-helper.c | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 153 insertions(+), 0 deletions(-) diff --git a/qemu-bridge-helper.c b/qemu-bridge-helper.c index e0ba917..2f137c2 100644 --- a/qemu-bridge-helper.c +++ b/qemu-bridge-helper.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -32,8 +33,110 @@ #include +#include "qemu-queue.h" + #include "net/tap-linux.h" +#define DEFAULT_ACL_FILE CONFIG_QEMU_CONFDIR "/bridge.conf" + +enum { + ACL_ALLOW = 0, + ACL_ALLOW_ALL, + ACL_DENY, + ACL_DENY_ALL, +}; + +typedef struct ACLRule { + int type; + char iface[IFNAMSIZ]; + QSIMPLEQ_ENTRY(ACLRule) entry; +} ACLRule; + +typedef QSIMPLEQ_HEAD(ACLList, ACLRule) ACLList; + +static int parse_acl_file(const char *filename, ACLList *acl_list) +{ + FILE *f; + char line[4096]; + ACLRule *acl_rule; + + f = fopen(filename, "r"); + if (f == NULL) { + return -1; + } + + while (fgets(line, sizeof(line), f) != NULL) { + char *ptr = line; + char *cmd, *arg, *argend; + + while (isspace(*ptr)) { + ptr++; + } + + /* skip comments and empty lines */ + if (*ptr == '#' || *ptr == 0) { + continue; + } + + cmd = ptr; + arg = strchr(cmd, ' '); + if (arg == NULL) { + arg = strchr(cmd, '\t'); + } + + if (arg == NULL) { + fprintf(stderr, "Invalid config line:\n %s\n", line); + fclose(f); + errno = EINVAL; + return -1; + } + + *arg = 0; + arg++; + while (isspace(*arg)) { + arg++; + } + + argend = arg + strlen(arg); + while (arg != argend && isspace(*(argend - 1))) { + argend--; + } + *argend = 0; + + if (strcmp(cmd, "deny") == 0) { + acl_rule = g_malloc(sizeof(*acl_rule)); + if (strcmp(arg, "all") == 0) { + acl_rule->type = ACL_DENY_ALL; + } else { + acl_rule->type = ACL_DENY; + snprintf(acl_rule->iface, IFNAMSIZ, "%s", arg); + } + QSIMPLEQ_INSERT_TAIL(acl_list, acl_rule, entry); + } else if (strcmp(cmd, "allow") == 0) { + acl_rule = g_malloc(sizeof(*acl_rule)); + if (strcmp(arg, "all") == 0) { + acl_rule->type = ACL_ALLOW_ALL; + } else { + acl_rule->type = ACL_ALLOW; + snprintf(acl_rule->iface, IFNAMSIZ, "%s", arg); + } + QSIMPLEQ_INSERT_TAIL(acl_list, acl_rule, entry); + } else if (strcmp(cmd, "include") == 0) { + /* ignore errors */ + parse_acl_file(arg, acl_list); + } else { + fprintf(stderr, "Unknown command `%s'\n", cmd); + fclose(f); + errno = EINVAL; + return -1; + } + } + + fclose(f); + + return 0; +} + static bool has_vnet_hdr(int fd) { unsigned int features = 0; @@ -91,6 +194,9 @@ int main(int argc, char **argv) const char *bridge; char iface[IFNAMSIZ]; int index; + ACLRule *acl_rule; + ACLList acl_list; + int access_allowed, access_denied; int ret = EXIT_SUCCESS; /* parse arguments */ @@ -112,6 +218,48 @@ int main(int argc, char **argv) bridge = argv[index++]; unixfd = atoi(argv[index++]); + /* parse default acl file */ + QSIMPLEQ_INIT(&acl_list); + if (parse_acl_file(DEFAULT_ACL_FILE, &acl_list) == -1) { + fprintf(stderr, "failed to parse default acl file `%s'\n", + DEFAULT_ACL_FILE); + ret = EXIT_FAILURE; + goto cleanup; + } + + /* validate bridge against acl -- default policy is to deny + * according acl policy if we have a deny and allow both + * then deny should always win over allow + */ + access_allowed = 0; + access_denied = 0; + QSIMPLEQ_FOREACH(acl_rule, &acl_list, entry) { + switch (acl_rule->type) { + case ACL_ALLOW_ALL: + access_allowed = 1; + break; + case ACL_ALLOW: + if (strcmp(bridge, acl_rule->iface) == 0) { + access_allowed = 1; + } + break; + case ACL_DENY_ALL: + access_denied = 1; + break; + case ACL_DENY: + if (strcmp(bridge, acl_rule->iface) == 0) { + access_denied = 1; + } + break; + } + } + + if ((access_allowed == 0) || (access_denied == 1)) { + fprintf(stderr, "access denied by acl file\n"); + ret = EXIT_FAILURE; + goto cleanup; + } + /* open a socket to use to control the network interfaces */ ctlfd = socket(AF_INET, SOCK_STREAM, 0); if (ctlfd == -1) { @@ -209,5 +357,10 @@ int main(int argc, char **argv) cleanup: + while ((acl_rule = QSIMPLEQ_FIRST(&acl_list)) != NULL) { + QSIMPLEQ_REMOVE_HEAD(&acl_list, entry); + g_free(acl_rule); + } + return ret; }