From patchwork Mon May 1 14:13:27 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lance Richardson X-Patchwork-Id: 757196 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from mail.linuxfoundation.org (mail.linuxfoundation.org [140.211.169.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3wGmh25TMdz9sDB for ; Tue, 2 May 2017 00:16:50 +1000 (AEST) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id 40CDAB55; Mon, 1 May 2017 14:13:32 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@mail.linuxfoundation.org Received: from smtp1.linuxfoundation.org (smtp1.linux-foundation.org [172.17.192.35]) by mail.linuxfoundation.org (Postfix) with ESMTPS id 938BB7A4 for ; Mon, 1 May 2017 14:13:30 +0000 (UTC) X-Greylist: domain auto-whitelisted by SQLgrey-1.7.6 Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by smtp1.linuxfoundation.org (Postfix) with ESMTPS id 86023142 for ; Mon, 1 May 2017 14:13:29 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.phx2.redhat.com [10.5.11.15]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id E9052804F0 for ; Mon, 1 May 2017 14:13:28 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mx1.redhat.com E9052804F0 Authentication-Results: ext-mx03.extmail.prod.ext.phx2.redhat.com; dmarc=none (p=none dis=none) header.from=redhat.com Authentication-Results: ext-mx03.extmail.prod.ext.phx2.redhat.com; spf=pass smtp.mailfrom=lrichard@redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 mx1.redhat.com E9052804F0 Received: from thinkcentre.localdomain.com (ovpn-124-242.rdu2.redhat.com [10.10.124.242]) by smtp.corp.redhat.com (Postfix) with ESMTP id 9D77F1F8 for ; Mon, 1 May 2017 14:13:28 +0000 (UTC) From: Lance Richardson To: dev@openvswitch.org Date: Mon, 1 May 2017 10:13:27 -0400 Message-Id: <20170501141327.9945-1-lrichard@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.15 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.27]); Mon, 01 May 2017 14:13:29 +0000 (UTC) X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on smtp1.linux-foundation.org Subject: [ovs-dev] [PATCH 5/6] ovn: add rbac tables to ovn southbound schema X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: ovs-dev-bounces@openvswitch.org Errors-To: ovs-dev-bounces@openvswitch.org Add rbac "roles" and "permissions" tables to ovn southbound database schema, add support to ovn-northd for managing these tables. Signed-off-by: Lance Richardson --- ovn/northd/ovn-northd.c | 190 +++++++++++++++++++++++++++++++++++++++++++++ ovn/ovn-architecture.7.xml | 155 ++++++++++++++++++++++++++++++++++++ ovn/ovn-sb.ovsschema | 28 ++++++- ovn/ovn-sb.xml | 39 ++++++++++ 4 files changed, 409 insertions(+), 3 deletions(-) diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index d0a5ba2..2d4bc81 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -5608,6 +5608,182 @@ check_and_add_supported_dhcpv6_opts_to_sb_db(struct northd_context *ctx) hmap_destroy(&dhcpv6_opts_to_add); } +static const char *rbac_chassis_auth[] = + {"name"}; +static const char *rbac_chassis_update[] = + {"nb_cfg", "external_ids", "encaps", "vtep_logical_switches"}; + +static const char *rbac_encap_auth[] = + {""}; +static const char *rbac_encap_update[] = + {"type", "options", "ip"}; + +static const char *rbac_port_binding_auth[] = + {""}; +static const char *rbac_port_binding_update[] = + {"chassis"}; + +static const char *rbac_mac_binding_auth[] = + {""}; +static const char *rbac_mac_binding_update[] = + {"logical_port", "ip", "mac", "datapath"}; + +static struct rbac_perm_cfg { + const char *table; + const char **auth; + int n_auth; + bool insdel; + const char **update; + int n_update; + const struct sbrec_rbac_permission *row; +} rbac_perm_cfg[] = { + { + "Chassis", + rbac_chassis_auth, + ARRAY_SIZE(rbac_chassis_auth), + true, + rbac_chassis_update, + ARRAY_SIZE(rbac_chassis_update), + NULL + },{ + "Encap", + rbac_encap_auth, + ARRAY_SIZE(rbac_encap_auth), + true, + rbac_encap_update, + ARRAY_SIZE(rbac_encap_update), + NULL + },{ + "Port_Binding", + rbac_port_binding_auth, + ARRAY_SIZE(rbac_port_binding_auth), + false, + rbac_port_binding_update, + ARRAY_SIZE(rbac_port_binding_update), + NULL + },{ + "MAC_Binding", + rbac_mac_binding_auth, + ARRAY_SIZE(rbac_mac_binding_auth), + true, + rbac_mac_binding_update, + ARRAY_SIZE(rbac_mac_binding_update), + NULL + }, + {} +}; + +static bool +ovn_rbac_validate_perm(const struct sbrec_rbac_permission *perm) +{ + struct rbac_perm_cfg *pcfg; + int i, j, n_found; + + for (pcfg = rbac_perm_cfg; pcfg->table; pcfg++) { + if (!strcmp(perm->table, pcfg->table)) { + break; + } + } + if (!pcfg->table) { + return false; + } + if (perm->n_authorization != pcfg->n_auth || + perm->n_update != pcfg->n_update) { + return false; + } + if (perm->insert_delete != pcfg->insdel) { + return false; + } + /* verify perm->authorization vs. pcfg->auth */ + n_found = 0; + for (i = 0; i < pcfg->n_auth; i++) { + for (j = 0; j < perm->n_authorization; j++) { + if (!strcmp(pcfg->auth[i], perm->authorization[j])) { + n_found++; + break; + } + } + } + if (n_found != pcfg->n_auth) { + return false; + } + + /* verify perm->update vs. pcfg->update */ + n_found = 0; + for (i = 0; i < pcfg->n_update; i++) { + for (j = 0; j < perm->n_update; j++) { + if (!strcmp(pcfg->update[i], perm->update[j])) { + n_found++; + break; + } + } + } + if (n_found != pcfg->n_update) { + return false; + } + + /* Success, db state matches expected state */ + pcfg->row = perm; + return true; +} + +static void +ovn_rbac_create_perm(struct rbac_perm_cfg *pcfg, + struct northd_context *ctx, + const struct sbrec_rbac_role *rbac_role) +{ + struct sbrec_rbac_permission *rbac_perm; + + rbac_perm = sbrec_rbac_permission_insert(ctx->ovnsb_txn); + sbrec_rbac_permission_set_table(rbac_perm, pcfg->table); + sbrec_rbac_permission_set_authorization(rbac_perm, + pcfg->auth, + pcfg->n_auth); + sbrec_rbac_permission_set_insert_delete(rbac_perm, pcfg->insdel); + sbrec_rbac_permission_set_update(rbac_perm, + pcfg->update, + pcfg->n_update); + sbrec_rbac_role_update_permissions_setkey(rbac_role, pcfg->table, + rbac_perm); +} + +static void +check_and_update_rbac(struct northd_context *ctx) +{ + const struct sbrec_rbac_role *rbac_role = NULL; + const struct sbrec_rbac_permission *perm_row, *perm_next; + const struct sbrec_rbac_role *role_row, *role_row_next; + struct rbac_perm_cfg *pcfg; + + for (pcfg = rbac_perm_cfg; pcfg->table; pcfg++) { + pcfg->row = NULL; + } + + SBREC_RBAC_PERMISSION_FOR_EACH_SAFE(perm_row, perm_next, ctx->ovnsb_idl) { + if (!ovn_rbac_validate_perm(perm_row)) { + sbrec_rbac_permission_delete(perm_row); + } + } + SBREC_RBAC_ROLE_FOR_EACH_SAFE(role_row, role_row_next, ctx->ovnsb_idl) { + if (strcmp(role_row->name, "ovn-controller")) { + sbrec_rbac_role_delete(role_row); + } else { + rbac_role = role_row; + } + } + + if (!rbac_role) { + rbac_role = sbrec_rbac_role_insert(ctx->ovnsb_txn); + sbrec_rbac_role_set_name(rbac_role, "ovn-controller"); + } + + for (pcfg = rbac_perm_cfg; pcfg->table; pcfg++) { + if (!pcfg->row) { + ovn_rbac_create_perm(pcfg, ctx, rbac_role); + } + } +} + /* Updates the sb_cfg and hv_cfg columns in the northbound NB_Global table. */ static void update_northbound_cfg(struct northd_context *ctx, @@ -5821,6 +5997,19 @@ main(int argc, char *argv[]) add_column_noalert(ovnsb_idl_loop.idl, &sbrec_address_set_col_name); add_column_noalert(ovnsb_idl_loop.idl, &sbrec_address_set_col_addresses); + ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_rbac_role); + add_column_noalert(ovnsb_idl_loop.idl, &sbrec_rbac_role_col_name); + add_column_noalert(ovnsb_idl_loop.idl, &sbrec_rbac_role_col_permissions); + + ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_rbac_permission); + add_column_noalert(ovnsb_idl_loop.idl, + &sbrec_rbac_permission_col_table); + add_column_noalert(ovnsb_idl_loop.idl, + &sbrec_rbac_permission_col_authorization); + add_column_noalert(ovnsb_idl_loop.idl, + &sbrec_rbac_permission_col_insert_delete); + add_column_noalert(ovnsb_idl_loop.idl, &sbrec_rbac_permission_col_update); + ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_chassis); ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_chassis_col_nb_cfg); @@ -5839,6 +6028,7 @@ main(int argc, char *argv[]) if (ctx.ovnsb_txn) { check_and_add_supported_dhcp_opts_to_sb_db(&ctx); check_and_add_supported_dhcpv6_opts_to_sb_db(&ctx); + check_and_update_rbac(&ctx); } unixctl_server_run(unixctl); diff --git a/ovn/ovn-architecture.7.xml b/ovn/ovn-architecture.7.xml index d8114f1..eb3d827 100644 --- a/ovn/ovn-architecture.7.xml +++ b/ovn/ovn-architecture.7.xml @@ -1404,6 +1404,161 @@ +

Security

+ +

Role-Based Access Controls for the Soutbound DB

+

+ In order to provide additional security against the possibility of an OVN + chassis becoming compromised in such a way as to allow rogue software to + make arbitrary modifications to the southbound database state and thus + disrupt the OVN network, role-based access controls are provided for the + southbound database. +

+ +

+ The implementation of role-based access controls (RBAC) requires the + addition of two tables to an OVSDB schema: the RBAC_Role + table, which is indexed by role name and maps the the names of the various + tables that may be modifiable for a given role to individual rows in a + permissions table containing detailed permission information for that role, + and the permission table itself which consists of rows containing the + following information: +

+
+
Table Name
+
+ The name of the associated table. This column exists primarily as an + aid for humans reading the contents of this table. +
+ +
Auth Criteria
+
+ A set of strings containing the names of columns (or column:key pairs + for columns containing string:string maps). The contents of at least + one of the columns or column:key values in the row to be modified must + be equal to the ID of the client attempting to modify the row in order + for the authorization check to pass. If the authorization criteria is + empty, authorization checking is disabled and all clients for the role + will be treated as authorized. +
+ +
Insert/Delete
+
+ Row insertion/deletion permission; boolean value indicating whether + insertion and deletion of rows is allowed for the associated table. + If true, insertion is allowed for any client and deletion is allowed + for authorized clients. +
+ +
Updatable Columns
+
+ A set of strings containing the names of columns or column:key pairs + that may be updated or mutated by authorized clients. Modifications to + columns within a row are only permitted when the authorization check + for the client passes and all columns to be modified are included in + this set of modifiable columns. +
+
+ +

+ RBAC configuration for the OVN southbound database is maintained by + ovn-northd. With RBAC enabled, modifications are only permitted for the + Chassis, Encap, Port_Binding, and + MAC_Binding tables, and are resstricted as follows: +

+
+
Chassis
+
+

+ Authorization: client ID must match the chassis name. +

+

+ Insert/Delete: row insertion and authorized row deletion + are permitted. +

+

+ Update: The columns nb_cfg, + external_ids, encaps, and + vtep_logical_switches may be modified when authorized. +

+
+ +
Encap
+
+

+ Authorization: disabled (all clients are considered + to be authorized. Future: add a "creating chassis name" column to + this table and use it for authorization checking. +

+

+ Insert/Delete: row insertion and authorized row deletion + are permitted. +

+

+ Update: The columns type, + options, and ip can be modified. +

+
+ +
Port_Binding
+
+

+ Authorization: disabled (all clients are considered + authorized. A future enhancement may add columns (or keys to + external_ids) in order to control which chassis are + allowed to bind each port. +

+

+ Insert/Delete: row insertion/deletion are not permitted + (ovn-northd maintains rows in this table. +

+

+ Update: Only modifications to the chassis + column are permitted. +

+
+ +
MAC_Binding
+
+

+ Authorization: disabled (all clients are considered + to be authorized). +

+

+ Insert/Delete: row insertion/deletion are permitted. +

+

+ Update: The columns logical_port, + ip, mac, and datapath may be + modified by ovn-controller. +

+
+
+ +

+ Enabling RBAC for ovn-controller connections to the southbound database + requires the following steps: +

+ +
    +
  1. + Creating SSL certificates for each chassis with the certificate CN field + set to the chassis name (e.g. for a chassis with + external-ids:system-id=chassis-1, via the command + "ovs-pki -B 1024 -u req+sign chassis-1 switch"). +
  2. +
  3. + Configuring each ovn-controller to use SSL when connecting to the + southbound database (e.g. via "ovs-vsctl set open . + external-ids:ovn-remote=ssl:x.x.x.x:6642"). +
  4. +
  5. + Configuring a southbound database SSL remote with "ovn-controller" role + (e.g. via "ovn-sbctl set-connection role=ovn-controller + pssl:6642"). +
  6. +
+

Design Decisions

Tunnel Encapsulations

diff --git a/ovn/ovn-sb.ovsschema b/ovn/ovn-sb.ovsschema index a576dc4..29b6196 100644 --- a/ovn/ovn-sb.ovsschema +++ b/ovn/ovn-sb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Southbound", - "version": "1.10.0", - "cksum": "860871483 9898", + "version": "1.12.0", + "cksum": "3249454395 10927", "tables": { "SB_Global": { "columns": { @@ -176,6 +176,7 @@ "min": 0, "max": 1}}, "read_only": {"type": "boolean"}, + "role": {"type": "string"}, "other_config": {"type": {"key": "string", "value": "string", "min": 0, @@ -201,4 +202,25 @@ "value": "string", "min": 0, "max": "unlimited"}}}, - "maxRows": 1}}} + "maxRows": 1}, + "RBAC_Role": { + "columns": { + "name": {"type": "string"}, + "permissions": { + "type": {"key": {"type": "string"}, + "value": {"type": "uuid", + "refTable": "RBAC_Permission", + "refType": "weak"}, + "min": 0, "max": "unlimited"}}}, + "isRoot": true}, + "RBAC_Permission": { + "columns": { + "table": {"type": "string"}, + "authorization": {"type": {"key": "string", + "min": 0, + "max": "unlimited"}}, + "insert_delete": {"type": "boolean"}, + "update" : {"type": {"key": "string", + "min": 0, + "max": "unlimited"}}}, + "isRoot": true}}} diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml index 5542f7e..2a2e32a 100644 --- a/ovn/ovn-sb.xml +++ b/ovn/ovn-sb.xml @@ -2512,6 +2512,9 @@ tcp.flags = RST; true to restrict these connections to read-only transactions, false to allow them to modify the database. + + String containing name of role for this connection entry. + @@ -2686,4 +2689,40 @@ tcp.flags = RST; + + Roles table for role-based access controls. + + + String containing the role name, corresponding to the role + column in the Connection table. + + + + A string:uuid map mapping table names to rows in the + RBAC_Permission table. + +
+ + Permissions table for role-based access controls. + + + Name of table to which this row applies. + + + + Set of strings identifying columns and column:key pairs to be compared + with client ID. At least one match is required in order to be + authorized. A zero-length string is treated as a special value + indicating all clients should be considered authorized. + + + + Boolean value, if "true" then row insertions and authorized row + deletions are allowed. + + + Set of strings identifying columns and column:key pairs that authorized + clients are allowed to modify. + +