Skip to content

Commit

Permalink
ovn: add rbac tables to ovn southbound schema
Browse files Browse the repository at this point in the history
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 <[email protected]>
Signed-off-by: Ben Pfaff <[email protected]>
  • Loading branch information
hlrichardson authored and blp committed Jun 8, 2017
1 parent d6db7b3 commit 75ddb5f
Show file tree
Hide file tree
Showing 5 changed files with 418 additions and 2 deletions.
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Post-v2.7.0
abbreviated to 4 hex digits.
* "ovn-sbctl lflow-list" can now print OpenFlow flows that correspond
to logical flows.
* Now uses OVSDB RBAC support to reduce impact of compromised hypervisors.
- OVSDB:
* New support for role-based access control (see ovsdb-server(1)).
- Add the command 'ovs-appctl stp/show' (see ovs-vswitchd(8)).
Expand Down
197 changes: 197 additions & 0 deletions ovn/northd/ovn-northd.c
Original file line number Diff line number Diff line change
Expand Up @@ -5805,6 +5805,189 @@ 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[] = {
{
.table = "Chassis",
.auth = rbac_chassis_auth,
.n_auth = ARRAY_SIZE(rbac_chassis_auth),
.insdel = true,
.update = rbac_chassis_update,
.n_update = ARRAY_SIZE(rbac_chassis_update),
.row = NULL
},{
.table = "Encap",
.auth = rbac_encap_auth,
.n_auth = ARRAY_SIZE(rbac_encap_auth),
.insdel = true,
.update = rbac_encap_update,
.n_update = ARRAY_SIZE(rbac_encap_update),
.row = NULL
},{
.table = "Port_Binding",
.auth = rbac_port_binding_auth,
.n_auth = ARRAY_SIZE(rbac_port_binding_auth),
.insdel = false,
.update = rbac_port_binding_update,
.n_update = ARRAY_SIZE(rbac_port_binding_update),
.row = NULL
},{
.table = "MAC_Binding",
.auth = rbac_mac_binding_auth,
.n_auth = ARRAY_SIZE(rbac_mac_binding_auth),
.insdel = true,
.update = rbac_mac_binding_update,
.n_update = ARRAY_SIZE(rbac_mac_binding_update),
.row = NULL
},{
.table = NULL,
.auth = NULL,
.n_auth = 0,
.insdel = false,
.update = NULL,
.n_update = 0,
.row = 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,
Expand Down Expand Up @@ -6025,6 +6208,19 @@ main(int argc, char *argv[])
add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dns_col_records);
add_column_noalert(ovnsb_idl_loop.idl, &sbrec_dns_col_external_ids);

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);

Expand All @@ -6043,6 +6239,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);
Expand Down
156 changes: 156 additions & 0 deletions ovn/ovn-architecture.7.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1436,6 +1436,162 @@
</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 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 (see
<code>ovsdb-server(1)</code> for additional details) are provided for the
southbound database.
</p>

<p>
The implementation of role-based access controls (RBAC) requires the
addition of two tables to an OVSDB schema: the <code>RBAC_Role</code>
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:
</p>
<dl>
<dt><code>Table Name</code></dt>
<dd>
The name of the associated table. This column exists primarily as an
aid for humans reading the contents of this table.
</dd>

<dt><code>Auth Criteria</code></dt>
<dd>
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 a row to be modified,
inserted, or deleted must be equal to the ID of the client attempting
to act on 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.
</dd>

<dt><code>Insert/Delete</code></dt>
<dd>
Row insertion/deletion permission; boolean value indicating whether
insertion and deletion of rows is allowed for the associated table.
If true, insertion and deletion of rows is allowed for authorized
clients.
</dd>

<dt><code>Updatable Columns</code></dt>
<dd>
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.
</dd>
</dl>

<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>
<p>
<code>Authorization</code>: client ID must match the chassis name.
</p>
<p>
<code>Insert/Delete</code>: authorized row insertion and deletion
are permitted.
</p>
<p>
<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.
</p>
</dd>

<dt><code>Encap</code></dt>
<dd>
<p>
<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.
</p>
<p>
<code>Insert/Delete</code>: row insertion and row deletion
are permitted.
</p>
<p>
<code>Update</code>: The columns <code>type</code>,
<code>options</code>, and <code>ip</code> can be modified.
</p>
</dd>

<dt><code>Port_Binding</code></dt>
<dd>
<p>
<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.
</p>
<p>
<code>Insert/Delete</code>: row insertion/deletion are not permitted
(ovn-northd maintains rows in this table.
</p>
<p>
<code>Update</code>: Only modifications to the <code>chassis</code>
column are permitted.
</p>
</dd>

<dt><code>MAC_Binding</code></dt>
<dd>
<p>
<code>Authorization</code>: disabled (all clients are considered
to be authorized).
</p>
<p>
<code>Insert/Delete</code>: row insertion/deletion are permitted.
</p>
<p>
<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.
</p>
</dd>
</dl>

<p>
Enabling RBAC for ovn-controller connections to the southbound database
requires the following steps:
</p>

<ol>
<li>
Creating SSL certificates for each chassis with the certificate CN field
set to the chassis name (e.g. for a chassis with
<code>external-ids:system-id=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>
</ol>

<h1>Design Decisions</h1>

<h2>Tunnel Encapsulations</h2>
Expand Down
Loading

0 comments on commit 75ddb5f

Please sign in to comment.