[ovs-dev,RFC,v3,5/6] ovn: add rbac tables to ovn southbound schema

Submitted by Lance Richardson on April 19, 2017, 7:07 p.m.

Details

Message ID 20170419190706.4320-1-lrichard@redhat.com
State Superseded
Headers show

Commit Message

Lance Richardson April 19, 2017, 7:07 p.m.
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 <lrichard@redhat.com>
---
v2:
  - Corrected authorization setup for Chassis and Encap tables.

v3:
  - Added description of RBAC implementation to ovn-architecture.7.xml
  - Bumped sb schema version.

 ovn/northd/ovn-northd.c    | 190 +++++++++++++++++++++++++++++++++++++++++++++
 ovn/ovn-architecture.7.xml | 159 +++++++++++++++++++++++++++++++++++++
 ovn/ovn-sb.ovsschema       |  28 ++++++-
 ovn/ovn-sb.xml             |  39 ++++++++++
 4 files changed, 413 insertions(+), 3 deletions(-)

Patch hide | download patch | download mbox

diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index 027e5a1..e2393d5 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -5569,6 +5569,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,
@@ -5782,6 +5958,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);
 
@@ -5800,6 +5989,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..935cb86 100644
--- a/ovn/ovn-architecture.7.xml
+++ b/ovn/ovn-architecture.7.xml
@@ -1404,6 +1404,165 @@ 
     </li>
   </ol>
 
+  <h1>Security</h1>
+
+  <h2>Role-Based Access Controls for the Soutbound DB</h2>
+  <p>
+    In order to provide additional security against the possibility that an OVN
+    chassis could become compromised in a way that would 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.
+  </p>
+
+  <p>
+    The role-based access control (RBAC) implementation involves the addition
+    of two tables to an OVSDB schema: the <code>RBAC_Role</code> table, which
+    is indexed by role name and provides a map from the names of the various
+    tables that are modifiable for a given role to individual rows in a
+    permission table containing detailed permission information, and the
+    permission table itself which has rows containing the following
+    information:
+  </p>
+  <ul>
+    <li>
+      Table name.
+    </li>
+
+    <li>
+      Authorization 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 will be considered to be
+      authorized.
+    </li>
+
+    <li>
+       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.
+    </li>
+
+    <li>
+      Column update permissions; 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.
+    </li>
+  </ul>
+
+  <p>
+    RBAC configuration for the OVN southbound database is maintained by
+    ovn-northd. With RBAC enabled, modifications are only permitted for the
+    <code>Chassis</code>, <code>Encap</code>, <code>Port_Binding</code>, and
+    <code>MAC_Binding</code> tables, and are resstricted as follows:
+  </p>
+  <dl>
+    <dt><code>Chassis</code></dt>
+    <dd>
+      <ul>
+        <li>
+         <code>Authorization</code>: client ID must match the chassis name.
+        </li>
+        <li>
+          <code>Insert/Delete</code>: row insertion and authorized row deletion
+          are permitted.
+        </li>
+        <li>
+          <code>Update</code>: The columns <code>nb_cfg</code>,
+          <code>external_ids</code>, <code>encaps</code>, and
+          <code>vtep_logical_switches</code> may be modified when authorized.
+        </li>
+      </ul>
+    </dd>
+
+    <dt><code>Encap</code></dt>
+    <dd>
+      <ul>
+        <li>
+          <code>Authorization</code>: disabled (all clients are considered
+          to be authorized. Future: add a "creating chassis name" column to
+          this table and use it for authorization checking.
+        </li>
+        <li>
+          <code>Insert/Delete</code>: row insertion and authorized row deletion
+          are permitted.
+        </li>
+        <li>
+          <code>Update</code>: The columns <code>type</code>,
+          <code>options</code>, and <code>ip</code> can be modified.
+        </li>
+      </ul>
+    </dd>
+
+    <dt><code>Port_Binding</code></dt>
+    <dd>
+      <ul>
+        <li>
+          <code>Authorization</code>: disabled (all clients are considered
+          authorized. A future enhancement may add columns (or keys to
+          <code>external_ids</code>) in order to control which chassis are
+          allowed to bind each port.
+        </li>
+        <li>
+          <code>Insert/Delete</code>: row insertion/deletion are not permitted
+          (ovn-northd maintains rows in this table.
+        </li>
+        <li>
+          <code>Update</code>: Only modifications to the <code>chassis</code>
+          column are permitted.
+        </li>
+      </ul>
+    </dd>
+
+    <dt><code>MAC_Binding</code></dt>
+    <dd>
+      <ul>
+        <li>
+          <code>Authorization</code>: disabled (all clients are considered
+          to be authorized).
+        </li>
+        <li>
+          <code>Insert/Delete</code>: row insertion/deletion are permitted.
+        </li>
+        <li>
+          <code>Update</code>: The columns <code>logical_port</code>,
+          <code>ip</code>, <code>mac</code>, and <code>datapath</code> may be
+          modified by ovn-controller.
+        </li>
+      </ul>
+    </dd>
+  </dl>
+
+  <p>
+    Enabling RBAC for ovn-controller connections to the southbound database
+    requires the following steps:
+  </p>
+
+  <ul>
+    <li>
+      Creating SSL certificates for each chassis with the certificate CN field
+      set to the chassis name (e.g. for a chassis named <code>chassis-1</code>,
+      via the command "<code>ovs-pki -B 1024 -u req+sign chassis-1
+      switch</code>").
+    </li>
+    <li>
+      Configuring each ovn-controller to use SSL when connecting to the
+      southbound database (e.g. via "<code>ovs-vsctl set open .
+      external-ids:ovn-remote=ssl:x.x.x.x:6642</code>").
+    </li>
+    <li>
+      Configuring a southbound database SSL remote with "ovn-controller" role
+      (e.g. via "<code>ovn-sbctl set-connection role=ovn-controller
+      pssl:6642</code>").
+    </li>
+  </ul>
+
   <h1>Design Decisions</h1>
 
   <h2>Tunnel Encapsulations</h2>
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 8605c98..7ffae74 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -2504,6 +2504,9 @@  tcp.flags = RST;
         <code>true</code> to restrict these connections to read-only
         transactions, <code>false</code> to allow them to modify the database.
       </column>
+      <column name="role">
+        String containing name of role for this connection entry.
+      </column>
     </group>
 
     <group title="Client Failure Detection and Handling">
@@ -2678,4 +2681,40 @@  tcp.flags = RST;
       <column name="external_ids"/>
     </group>
   </table>
+  <table name="RBAC_Role">
+    Roles table for role-based access controls.
+
+    <column name="name">
+        String containing the role name, corresponding to the <code>role</code>
+        column in the <code>Connection</code> table.
+    </column>
+
+    <column name="permissions">
+        A string:uuid map mapping table names to rows in the
+        <code>RBAC_Permission</code> table.
+    </column>
+  </table>
+  <table name="RBAC_Permission">
+    Permissions table for role-based access controls.
+
+    <column name="table">
+      Name of table to which this row applies.
+    </column>
+
+    <column name="authorization">
+        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.
+    </column>
+
+    <column name="insert_delete">
+        Boolean value, if "true" then row insertions and authorized row
+        deletions are allowed.
+    </column>
+    <column name="update">
+        Set of strings identifying columns and column:key pairs that authorized
+        clients are allowed to modify.
+    </column>
+  </table>
 </database>