diff --git a/NEWS b/NEWS index 82004c845ce..2517b906328 100644 --- a/NEWS +++ b/NEWS @@ -38,6 +38,8 @@ Post-v2.7.0 abbreviated to 4 hex digits. * "ovn-sbctl lflow-list" can now print OpenFlow flows that correspond to logical flows. + - OVSDB: + * New support for role-based access control (see ovsdb-server(1)). - Add the command 'ovs-appctl stp/show' (see ovs-vswitchd(8)). - OpenFlow: * All features required by OpenFlow 1.4 are now implemented, so diff --git a/lib/jsonrpc.c b/lib/jsonrpc.c index a0ade9c14e2..2fae057de18 100644 --- a/lib/jsonrpc.c +++ b/lib/jsonrpc.c @@ -1005,6 +1005,16 @@ jsonrpc_session_get_name(const struct jsonrpc_session *s) return reconnect_get_name(s->reconnect); } +const char * +jsonrpc_session_get_id(const struct jsonrpc_session *s) +{ + if (s->rpc && s->rpc->stream) { + return stream_get_peer_id(s->rpc->stream); + } else { + return NULL; + } +} + /* Always takes ownership of 'msg', regardless of success. */ int jsonrpc_session_send(struct jsonrpc_session *s, struct jsonrpc_msg *msg) diff --git a/lib/jsonrpc.h b/lib/jsonrpc.h index 982017a7b54..9b4fb0e5137 100644 --- a/lib/jsonrpc.h +++ b/lib/jsonrpc.h @@ -130,5 +130,6 @@ void jsonrpc_session_set_probe_interval(struct jsonrpc_session *, int probe_interval); void jsonrpc_session_set_dscp(struct jsonrpc_session *, uint8_t dscp); +const char *jsonrpc_session_get_id(const struct jsonrpc_session *); #endif /* jsonrpc.h */ diff --git a/lib/ovsdb-error.c b/lib/ovsdb-error.c index dfa424945a0..d8161e6d7b9 100644 --- a/lib/ovsdb-error.c +++ b/lib/ovsdb-error.c @@ -167,6 +167,19 @@ ovsdb_internal_error(struct ovsdb_error *inner_error, return error; } +struct ovsdb_error * +ovsdb_perm_error(const char *details, ...) +{ + struct ovsdb_error *error; + va_list args; + + va_start(args, details); + error = ovsdb_error_valist("permission error", details, args); + va_end(args); + + return error; +} + void ovsdb_error_destroy(struct ovsdb_error *error) { diff --git a/lib/ovsdb-error.h b/lib/ovsdb-error.h index 2bc259a548c..da91b74999d 100644 --- a/lib/ovsdb-error.h +++ b/lib/ovsdb-error.h @@ -41,6 +41,10 @@ struct ovsdb_error *ovsdb_internal_error(struct ovsdb_error *error, OVS_PRINTF_FORMAT(4, 5) OVS_WARN_UNUSED_RESULT; +struct ovsdb_error *ovsdb_perm_error(const char *details, ...) + OVS_PRINTF_FORMAT(1, 2) + OVS_WARN_UNUSED_RESULT; + /* Returns a pointer to an ovsdb_error that represents an internal error for * the current file name and line number with MSG as the associated message. * The caller is responsible for freeing the internal error. */ diff --git a/lib/ovsdb-idl.c b/lib/ovsdb-idl.c index 515541241be..893143c551f 100644 --- a/lib/ovsdb-idl.c +++ b/lib/ovsdb-idl.c @@ -160,6 +160,7 @@ static const char *row_update_names[] = {"row_update", "row_update2"}; static struct vlog_rate_limit syntax_rl = VLOG_RATE_LIMIT_INIT(1, 5); static struct vlog_rate_limit semantic_rl = VLOG_RATE_LIMIT_INIT(1, 5); +static struct vlog_rate_limit other_rl = VLOG_RATE_LIMIT_INIT(1, 5); static void ovsdb_idl_clear(struct ovsdb_idl *); static void ovsdb_idl_send_schema_request(struct ovsdb_idl *); @@ -3768,9 +3769,14 @@ ovsdb_idl_txn_process_reply(struct ovsdb_idl *idl, soft_errors++; } else if (!strcmp(error->u.string, "not owner")) { lock_errors++; + } else if (!strcmp(error->u.string, "not allowed")) { + hard_errors++; + ovsdb_idl_txn_set_error_json(txn, op); } else if (strcmp(error->u.string, "aborted")) { hard_errors++; ovsdb_idl_txn_set_error_json(txn, op); + VLOG_WARN_RL(&other_rl, + "transaction error: %s", txn->error); } } else { hard_errors++; diff --git a/ovsdb/automake.mk b/ovsdb/automake.mk index db4b9604df7..50e5ab367ef 100644 --- a/ovsdb/automake.mk +++ b/ovsdb/automake.mk @@ -24,6 +24,8 @@ ovsdb_libovsdb_la_SOURCES = \ ovsdb/monitor.h \ ovsdb/query.c \ ovsdb/query.h \ + ovsdb/rbac.c \ + ovsdb/rbac.h \ ovsdb/replication.c \ ovsdb/replication.h \ ovsdb/row.c \ diff --git a/ovsdb/execution.c b/ovsdb/execution.c index e2d320e2367..1ebe7a772e4 100644 --- a/ovsdb/execution.c +++ b/ovsdb/execution.c @@ -27,6 +27,7 @@ #include "ovsdb-parser.h" #include "ovsdb.h" #include "query.h" +#include "rbac.h" #include "row.h" #include "server.h" #include "table.h" @@ -39,6 +40,8 @@ struct ovsdb_execution { struct ovsdb_txn *txn; struct ovsdb_symbol_table *symtab; bool durable; + const char *role; + const char *id; /* Triggers. */ long long int elapsed_msec; @@ -97,6 +100,7 @@ lookup_executor(const char *name, bool *read_only) struct json * ovsdb_execute(struct ovsdb *db, const struct ovsdb_session *session, const struct json *params, bool read_only, + const char *role, const char *id, long long int elapsed_msec, long long int *timeout_msec) { struct ovsdb_execution x; @@ -126,6 +130,8 @@ ovsdb_execute(struct ovsdb *db, const struct ovsdb_session *session, x.txn = ovsdb_txn_create(db); x.symtab = ovsdb_symbol_table_create(); x.durable = false; + x.role = role; + x.id = id; x.elapsed_msec = elapsed_msec; x.timeout_msec = LLONG_MAX; results = NULL; @@ -348,6 +354,13 @@ ovsdb_execute_insert(struct ovsdb_execution *x, struct ovsdb_parser *parser, } } } + + if (!error && !ovsdb_rbac_insert(x->db, table, row, x->role, x->id)) { + error = ovsdb_perm_error("RBAC rules for client \"%s\" role \"%s\" " + "prohibit row insertion into table \"%s\".", + x->id, x->role, table->schema->name); + } + if (!error) { *ovsdb_row_get_uuid_rw(row) = row_uuid; ovsdb_txn_row_insert(x->txn, row); @@ -410,6 +423,8 @@ struct update_row_cbdata { struct ovsdb_txn *txn; const struct ovsdb_row *row; const struct ovsdb_column_set *columns; + const char *role; + const char *id; }; static bool @@ -470,7 +485,15 @@ ovsdb_execute_update(struct ovsdb_execution *x, struct ovsdb_parser *parser, ur.txn = x->txn; ur.row = row; ur.columns = &columns; - ovsdb_query(table, &condition, update_row_cb, &ur); + if (ovsdb_rbac_update(x->db, table, &columns, &condition, x->role, + x->id)) { + ovsdb_query(table, &condition, update_row_cb, &ur); + } else { + error = ovsdb_perm_error("RBAC rules for client \"%s\" role " + "\"%s\" prohibit modification of " + "table \"%s\".", + x->id, x->role, table->schema->name); + } json_object_put(result, "count", json_integer_create(ur.n_matches)); } @@ -529,7 +552,15 @@ ovsdb_execute_mutate(struct ovsdb_execution *x, struct ovsdb_parser *parser, mr.txn = x->txn; mr.mutations = &mutations; mr.error = &error; - ovsdb_query(table, &condition, mutate_row_cb, &mr); + if (ovsdb_rbac_mutate(x->db, table, &mutations, &condition, x->role, + x->id)) { + ovsdb_query(table, &condition, mutate_row_cb, &mr); + } else { + error = ovsdb_perm_error("RBAC rules for client \"%s\" role " + "\"%s\" prohibit mutate operation on " + "table \"%s\".", + x->id, x->role, table->schema->name); + } json_object_put(result, "count", json_integer_create(mr.n_matches)); } @@ -579,8 +610,15 @@ ovsdb_execute_delete(struct ovsdb_execution *x, struct ovsdb_parser *parser, dr.n_matches = 0; dr.table = table; dr.txn = x->txn; - ovsdb_query(table, &condition, delete_row_cb, &dr); + if (ovsdb_rbac_delete(x->db, table, &condition, x->role, x->id)) { + ovsdb_query(table, &condition, delete_row_cb, &dr); + } else { + error = ovsdb_perm_error("RBAC rules for client \"%s\" role " + "\"%s\" prohibit row deletion from " + "table \"%s\".", + x->id, x->role, table->schema->name); + } json_object_put(result, "count", json_integer_create(dr.n_matches)); } diff --git a/ovsdb/jsonrpc-server.c b/ovsdb/jsonrpc-server.c index 1ba6bb39027..1770c2616b5 100644 --- a/ovsdb/jsonrpc-server.c +++ b/ovsdb/jsonrpc-server.c @@ -128,6 +128,7 @@ struct ovsdb_jsonrpc_remote { struct ovs_list sessions; /* List of "struct ovsdb_jsonrpc_session"s. */ uint8_t dscp; bool read_only; + char *role; }; static struct ovsdb_jsonrpc_remote *ovsdb_jsonrpc_server_add_remote( @@ -270,6 +271,7 @@ ovsdb_jsonrpc_server_add_remote(struct ovsdb_jsonrpc_server *svr, ovs_list_init(&remote->sessions); remote->dscp = options->dscp; remote->read_only = options->read_only; + remote->role = nullable_xstrdup(options->role); shash_add(&svr->remotes, name, remote); if (!listener) { @@ -287,6 +289,7 @@ ovsdb_jsonrpc_server_del_remote(struct shash_node *node) ovsdb_jsonrpc_session_close_all(remote); pstream_close(remote->listener); shash_delete(&remote->server->remotes, node); + free(remote->role); free(remote); } @@ -1038,7 +1041,8 @@ ovsdb_jsonrpc_trigger_create(struct ovsdb_jsonrpc_session *s, struct ovsdb *db, /* Insert into trigger table. */ t = xmalloc(sizeof *t); ovsdb_trigger_init(&s->up, db, &t->trigger, params, time_msec(), - s->read_only); + s->read_only, s->remote->role, + jsonrpc_session_get_id(s->js)); t->id = id; hmap_insert(&s->triggers, &t->hmap_node, hash); diff --git a/ovsdb/jsonrpc-server.h b/ovsdb/jsonrpc-server.h index 3cacbb6a0be..1add3276d3b 100644 --- a/ovsdb/jsonrpc-server.h +++ b/ovsdb/jsonrpc-server.h @@ -37,6 +37,7 @@ struct ovsdb_jsonrpc_options { int probe_interval; /* Max idle time before probing, in msec. */ bool read_only; /* Only read-only transactions are allowed. */ int dscp; /* Dscp value for manager connections */ + char *role; /* Role, for role-based access controls */ }; struct ovsdb_jsonrpc_options * ovsdb_jsonrpc_default_options(const char *target); diff --git a/ovsdb/ovsdb-server.1.in b/ovsdb/ovsdb-server.1.in index 3c798dd1795..c655425e13e 100644 --- a/ovsdb/ovsdb-server.1.in +++ b/ovsdb/ovsdb-server.1.in @@ -269,6 +269,9 @@ narrow down the particular syntax that could not be parsed. The request triggered a bug in \fBovsdb\-server\fR. .IP "\fBovsdb error\fR" A map or set contains a duplicate key. +.IP "\fBpermission error\fR" +The request was denied by the role-based access control extension, +introduced in version 2.8. .RE . .IP "3.2. Schema Format" @@ -281,6 +284,36 @@ This raises the issue of the behavior of the weak reference when the rows that it references are deleted. Since version 2.6, \fBovsdb\-server\fR forces columns that contain weak references to be mutable. +.IP +Since version 2.8, the table name \fBRBAC_Role\fR is used internally +by the role-based access control extension to \fBovsdb\-server\fR and +should not be used for purposes other than defining mappings of role +names to table access permissions. This table has one row per role +name and the following columns: +.RS +.IP "\fBname\fR" +The role name. +.IP "\fBpermissions\fR" +A map of table name to a reference to a row in a separate permission +table. +.RE +.IP +The separate RBAC permission table has one row per access control +configuration and the following columns: +.RS +.IP "\fBname\fR" +The name of the table to which the row applies. +.IP "\fBauthorization\fR" +The set of column names and column:key pairs to be compared with +the client ID in order to determine the authorization status of +the requested operation. +.IP "\fBinsert_delete\fR" +A boolean value, true if authorized insertions and authorized are allowed, +false if no insertions or deletions are allowed. +.IP "\fBupdate\fR" +The set of columns and column:key pairs for which authorized update and +mutate operations should be permitted. +.RE . .IP "4. Wire Protocol" The original OVSDB specifications included the following reason, @@ -299,6 +332,18 @@ any corresponding advantage. The JSON-RPC specification for HTTP transport is incomplete. .RE . +.IP "4.1.3. Transact" +Since version 2.8, role-based access controls can be applied to operations +within a transaction that would modify the contents of the database +(these operations include row insert, row delete, column update, and +column mutate). Role-based access controls are applied when the database +schema contains a table with the name "\fBRBAC_Role\fR" and the connection +on which the transaction request was received has an associated role +name (from the "\fBrole\fR" column in the remote connection table). When +role-based access controls are enabled, transactions that are otherwise +well-formed may be rejected depending on the client's role, ID, and the +contents of the \fBRBAC_Role\fR table and associated permissions table. +. .IP "4.1.5. Monitor" For backward compatibility, \fBovsdb\-server\fR currently permits a single to be used instead of an array; it is treated diff --git a/ovsdb/ovsdb-server.c b/ovsdb/ovsdb-server.c index 50c35556cd2..030d86ba467 100644 --- a/ovsdb/ovsdb-server.c +++ b/ovsdb/ovsdb-server.c @@ -683,7 +683,7 @@ add_manager_options(struct shash *remotes, const struct ovsdb_row *row) struct ovsdb_jsonrpc_options *options; long long int max_backoff, probe_interval; bool read_only; - const char *target, *dscp_string; + const char *target, *dscp_string, *role; if (!ovsdb_util_read_string_column(row, "target", &target) || !target) { VLOG_INFO_RL(&rl, "Table `%s' has missing or invalid `target' column", @@ -703,6 +703,12 @@ add_manager_options(struct shash *remotes, const struct ovsdb_row *row) options->read_only = read_only; } + free(options->role); + options->role = NULL; + if (ovsdb_util_read_string_column(row, "role", &role) && role) { + options->role = xstrdup(role); + } + options->dscp = DSCP_DEFAULT; dscp_string = ovsdb_util_read_map_string_column(row, "other_config", "dscp"); diff --git a/ovsdb/ovsdb-tool.1.in b/ovsdb/ovsdb-tool.1.in index d01531e567c..8c799f4cc99 100644 --- a/ovsdb/ovsdb-tool.1.in +++ b/ovsdb/ovsdb-tool.1.in @@ -131,7 +131,7 @@ will print a blank line. . .SS "Other Commands" . -.IP "\fBquery\fI db transaction\fR" +.IP "[\fB\-\-rbac\-role=\fIrole\fR] \fBquery\fI db transaction\fR" Opens \fIdb\fR, executes \fItransaction\fR on it, and prints the results. The \fItransaction\fR must be a JSON array in the format of the \fBparams\fR array for the JSON-RPC \fBtransact\fR method, as @@ -142,8 +142,11 @@ safely run concurrently with other database activity, including \fBovsdb\-server\fR and other database writers. The \fItransaction\fR may specify database modifications, but these will have no effect on \fIdb\fR. +.IP +By default, the transaction is executed using the ``superuser'' RBAC +role. Use \fB\-\-rbac\-role\fR to specify a different role. . -.IP "\fBtransact\fI db transaction\fR" +.IP "[\fR\-\-rbac\-role=\fIrole\fR] \fBtransact\fI db transaction\fR" Opens \fIdb\fR, executes \fItransaction\fR on it, prints the results, and commits any changes to \fIdb\fR. The \fItransaction\fR must be a JSON array in the format of the \fBparams\fR array for the JSON-RPC @@ -154,6 +157,9 @@ command will fail if the database is opened for writing by any other process, including \fBovsdb\-server\fR(1). Use \fBovsdb\-client\fR(1), instead, to write to a database that is served by \fBovsdb\-server\fR(1). +.IP +By default, the transaction is executed using the ``superuser'' RBAC +role. Use \fB\-\-rbac\-role\fR to specify a different role. . .IP "\fBshow\-log\fI db\fR" Prints a summary of the records in \fIdb\fR's log, including the time diff --git a/ovsdb/ovsdb-tool.c b/ovsdb/ovsdb-tool.c index 8d7e76a97bc..8908bae3628 100644 --- a/ovsdb/ovsdb-tool.c +++ b/ovsdb/ovsdb-tool.c @@ -44,6 +44,9 @@ /* -m, --more: Verbosity level for "show-log" command output. */ static int show_log_verbosity; +/* --role: RBAC role to use for "transact" and "query" commands. */ +static const char *rbac_role; + static const struct ovs_cmdl_command *get_all_commands(void); OVS_NO_RETURN static void usage(void); @@ -68,8 +71,12 @@ main(int argc, char *argv[]) static void parse_options(int argc, char *argv[]) { + enum { + OPT_RBAC_ROLE = UCHAR_MAX + 1 + }; static const struct option long_options[] = { {"more", no_argument, NULL, 'm'}, + {"rbac-role", required_argument, NULL, OPT_RBAC_ROLE}, {"verbose", optional_argument, NULL, 'v'}, {"help", no_argument, NULL, 'h'}, {"option", no_argument, NULL, 'o'}, @@ -91,6 +98,10 @@ parse_options(int argc, char *argv[]) show_log_verbosity++; break; + case OPT_RBAC_ROLE: + rbac_role = optarg; + break; + case 'h': usage(); @@ -135,10 +146,12 @@ usage(void) "The default SCHEMA is %s.\n", program_name, program_name, default_db(), default_schema()); vlog_usage(); - printf("\nOther options:\n" - " -m, --more increase show-log verbosity\n" - " -h, --help display this help message\n" - " -V, --version display version information\n"); + printf("\ +\nOther options:\n\ + -m, --more increase show-log verbosity\n\ + --rbac-role=ROLE RBAC role for transact and query commands\n\ + -h, --help display this help message\n\ + -V, --version display version information\n"); exit(EXIT_SUCCESS); } @@ -366,7 +379,7 @@ transact(bool read_only, int argc, char *argv[]) check_ovsdb_error(ovsdb_file_open(db_file_name, read_only, &db, NULL)); request = parse_json(transaction); - result = ovsdb_execute(db, NULL, request, false, 0, NULL); + result = ovsdb_execute(db, NULL, request, false, rbac_role, NULL, 0, NULL); json_destroy(request); print_and_free_json(result); diff --git a/ovsdb/ovsdb-util.c b/ovsdb/ovsdb-util.c index 647f5df3ec4..5ee5e4ddaf8 100644 --- a/ovsdb/ovsdb-util.c +++ b/ovsdb/ovsdb-util.c @@ -88,6 +88,37 @@ ovsdb_util_read_map_string_column(const struct ovsdb_row *row, return atom_value ? atom_value->string : NULL; } +/* Read string-uuid key-values from a map. Returns the row associated with + * 'key', if found, or NULL */ +const struct ovsdb_row * +ovsdb_util_read_map_string_uuid_column(const struct ovsdb_row *row, + const char *column_name, + const char *key) +{ + const struct ovsdb_column *column + = ovsdb_table_schema_get_column(row->table->schema, column_name); + if (!column || + column->type.key.type != OVSDB_TYPE_STRING || + column->type.value.type != OVSDB_TYPE_UUID) { + return NULL; + } + + const struct ovsdb_table *ref_table = column->type.value.u.uuid.refTable; + if (!ref_table) { + return NULL; + } + + const struct ovsdb_datum *datum = &row->fields[column->index]; + for (size_t i = 0; i < datum->n; i++) { + union ovsdb_atom *atom_key = &datum->keys[i]; + if (!strcmp(atom_key->string, key)) { + const union ovsdb_atom *atom_value = &datum->values[i]; + return ovsdb_table_get_row(ref_table, &atom_value->uuid); + } + } + return NULL; +} + const union ovsdb_atom * ovsdb_util_read_column(const struct ovsdb_row *row, const char *column_name, enum ovsdb_atomic_type type) diff --git a/ovsdb/ovsdb-util.h b/ovsdb/ovsdb-util.h index effbec87b2e..abd81ff38cd 100644 --- a/ovsdb/ovsdb-util.h +++ b/ovsdb/ovsdb-util.h @@ -25,6 +25,10 @@ struct ovsdb_datum *ovsdb_util_get_datum(struct ovsdb_row *row, const char *ovsdb_util_read_map_string_column(const struct ovsdb_row *row, const char *column_name, const char *key); +const struct ovsdb_row *ovsdb_util_read_map_string_uuid_column( + const struct ovsdb_row *r, + const char *column_name, + const char *key); const union ovsdb_atom *ovsdb_util_read_column(const struct ovsdb_row *row, const char *column_name, enum ovsdb_atomic_type type); diff --git a/ovsdb/ovsdb.c b/ovsdb/ovsdb.c index 03919004d95..d8f441ad072 100644 --- a/ovsdb/ovsdb.c +++ b/ovsdb/ovsdb.c @@ -351,6 +351,9 @@ ovsdb_create(struct ovsdb_schema *schema) } } + /* Use RBAC roles table if present. */ + db->rbac_role = ovsdb_get_table(db, "RBAC_Role"); + return db; } diff --git a/ovsdb/ovsdb.h b/ovsdb/ovsdb.h index fc45c80a32d..89bbfa2512f 100644 --- a/ovsdb/ovsdb.h +++ b/ovsdb/ovsdb.h @@ -62,6 +62,8 @@ struct ovsdb { /* Triggers. */ struct ovs_list triggers; /* Contains "struct ovsdb_trigger"s. */ bool run_triggers; + + struct ovsdb_table *rbac_role; }; struct ovsdb *ovsdb_create(struct ovsdb_schema *); @@ -73,6 +75,7 @@ struct ovsdb_table *ovsdb_get_table(const struct ovsdb *, const char *); struct json *ovsdb_execute(struct ovsdb *, const struct ovsdb_session *, const struct json *params, bool read_only, + const char *role, const char *id, long long int elapsed_msec, long long int *timeout_msec); diff --git a/ovsdb/rbac.c b/ovsdb/rbac.c new file mode 100644 index 00000000000..b85ca9a9396 --- /dev/null +++ b/ovsdb/rbac.c @@ -0,0 +1,449 @@ +/* + * Copyright (c) 2017 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include + +#include "rbac.h" + +#include + +#include "column.h" +#include "condition.h" +#include "condition.h" +#include "file.h" +#include "mutation.h" +#include "openvswitch/vlog.h" +#include "ovsdb-data.h" +#include "ovsdb-error.h" +#include "ovsdb-parser.h" +#include "ovsdb-util.h" +#include "ovsdb.h" +#include "query.h" +#include "row.h" +#include "server.h" +#include "table.h" +#include "timeval.h" +#include "transaction.h" + +VLOG_DEFINE_THIS_MODULE(ovsdb_rbac); + +static const struct ovsdb_row * +ovsdb_find_row_by_string_key(const struct ovsdb_table *table, + const char *column_name, + const char *key) +{ + const struct ovsdb_column *column; + column = ovsdb_table_schema_get_column(table->schema, column_name); + + if (column) { + /* XXX This is O(n) in the size of the table. If the table has an + * index on the column, then we could implement it in O(1). */ + const struct ovsdb_row *row; + HMAP_FOR_EACH (row, hmap_node, &table->rows) { + const struct ovsdb_datum *datum = &row->fields[column->index]; + for (size_t i = 0; i < datum->n; i++) { + if (datum->keys[i].string[0] && + !strcmp(key, datum->keys[i].string)) { + return row; + } + } + } + } + + return NULL; +} + +static const struct ovsdb_row * +ovsdb_rbac_lookup_perms(const struct ovsdb *db, const char *role, + const char *table) +{ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + const struct ovsdb_row *role_row, *perm_row; + const struct ovsdb_column *column; + + /* Lookup role in roles table */ + role_row = ovsdb_find_row_by_string_key(db->rbac_role, "name", role); + if (!role_row) { + VLOG_INFO_RL(&rl, "rbac: role \"%s\" not found in rbac roles table", + role); + return NULL; + } + + /* Find row in permissions column for table from "permissions" column */ + column = ovsdb_table_schema_get_column(role_row->table->schema, + "permissions"); + if (!column) { + VLOG_INFO_RL(&rl, "rbac: \"permissions\" column not present in rbac " + "roles table"); + return NULL; + } + perm_row = ovsdb_util_read_map_string_uuid_column(role_row, "permissions", + table); + + return perm_row; +} + +static bool +ovsdb_rbac_authorized(const struct ovsdb_row *perms, + const char *id, + const struct ovsdb_row *row) +{ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + const struct ovsdb_datum *datum; + size_t i; + + datum = ovsdb_util_get_datum(CONST_CAST(struct ovsdb_row *, perms), + "authorization", + OVSDB_TYPE_STRING, OVSDB_TYPE_VOID, UINT_MAX); + + if (!datum) { + VLOG_INFO_RL(&rl, "rbac: error reading authorization column"); + return false; + } + + for (i = 0; i < datum->n; i++) { + const char *name = datum->keys[i].string; + const char *value = NULL; + bool is_map; + + if (name[0] == '\0') { + /* empty string means all are authorized */ + return true; + } + + is_map = strchr(name, ':') != NULL; + + if (is_map) { + char *tmp = xstrdup(name); + char *col_name, *key, *save_ptr = NULL; + col_name = strtok_r(tmp, ":", &save_ptr); + key = strtok_r(NULL, ":", &save_ptr); + + if (col_name && key) { + value = ovsdb_util_read_map_string_column(row, col_name, key); + } + free(tmp); + } else { + ovsdb_util_read_string_column(row, name, &value); + } + if (value && !strcmp(value, id)) { + return true; + } + } + + return false; +} + +bool +ovsdb_rbac_insert(const struct ovsdb *db, const struct ovsdb_table *table, + const struct ovsdb_row *row, + const char *role, const char *id) +{ + const struct ovsdb_table_schema *ts = table->schema; + const struct ovsdb_row *perms; + bool insdel; + + if (!db->rbac_role || !role || *role == '\0') { + return true; + } + + if (!id) { + goto denied; + } + + perms = ovsdb_rbac_lookup_perms(db, role, ts->name); + + if (!perms) { + goto denied; + } + + if (!ovsdb_rbac_authorized(perms, id, row)) { + goto denied; + } + + if (!ovsdb_util_read_bool_column(perms, "insert_delete", &insdel)) { + return false; + } + + if (insdel) { + return true; + } + +denied: + return false; +} + +struct rbac_delete_cbdata { + const struct ovsdb_table *table; + const struct ovsdb_row *perms; + const char *role; + const char *id; + bool permitted; +}; + +static bool +rbac_delete_cb(const struct ovsdb_row *row, void *rd_) +{ + struct rbac_delete_cbdata *rd = rd_; + bool insdel; + + if (!ovsdb_rbac_authorized(rd->perms, rd->id, row)) { + goto denied; + } + + if (!ovsdb_util_read_bool_column(rd->perms, "insert_delete", &insdel)) { + goto denied; + } + + if (!insdel) { + goto denied; + } + return true; + +denied: + rd->permitted = false; + return false; +} + +bool +ovsdb_rbac_delete(const struct ovsdb *db, struct ovsdb_table *table, + struct ovsdb_condition *condition, + const char *role, const char *id) +{ + const struct ovsdb_table_schema *ts = table->schema; + const struct ovsdb_row *perms; + struct rbac_delete_cbdata rd; + + if (!db->rbac_role || !role || *role == '\0') { + return true; + } + if (!id) { + goto denied; + } + + perms = ovsdb_rbac_lookup_perms(db, role, ts->name); + + if (!perms) { + goto denied; + } + + rd.permitted = true; + rd.perms = perms; + rd.table = table; + rd.role = role; + rd.id = id; + + ovsdb_query(table, condition, rbac_delete_cb, &rd); + + if (rd.permitted) { + return true; + } + +denied: + return false; +} + +struct rbac_update_cbdata { + const struct ovsdb_table *table; + const struct ovsdb_column_set *columns; /* columns to be modified */ + const struct ovsdb_datum *modifiable; /* modifiable column names */ + const struct ovsdb_row *perms; + const char *role; + const char *id; + bool permitted; +}; + +static bool +rbac_column_modification_permitted(const struct ovsdb_column *column, + const struct ovsdb_datum *modifiable) +{ + size_t i; + + for (i = 0; i < modifiable->n; i++) { + char *name = modifiable->keys[i].string; + + if (!strcmp(name, column->name)) { + return true; + } + } + return false; +} + +static bool +rbac_update_cb(const struct ovsdb_row *row, void *ru_) +{ + struct rbac_update_cbdata *ru = ru_; + size_t i; + + if (!ovsdb_rbac_authorized(ru->perms, ru->id, row)) { + goto denied; + } + + for (i = 0; i < ru->columns->n_columns; i++) { + const struct ovsdb_column *column = ru->columns->columns[i]; + + if (!rbac_column_modification_permitted(column, ru->modifiable)) { + goto denied; + } + } + return true; + +denied: + ru->permitted = false; + return false; +} + +bool +ovsdb_rbac_update(const struct ovsdb *db, + struct ovsdb_table *table, + struct ovsdb_column_set *columns, + struct ovsdb_condition *condition, + const char *role, const char *id) +{ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + const struct ovsdb_table_schema *ts = table->schema; + const struct ovsdb_datum *datum; + const struct ovsdb_row *perms; + struct rbac_update_cbdata ru; + + if (!db->rbac_role || !role || *role == '\0') { + return true; + } + if (!id) { + goto denied; + } + + perms = ovsdb_rbac_lookup_perms(db, role, ts->name); + + if (!perms) { + goto denied; + } + + datum = ovsdb_util_get_datum(CONST_CAST(struct ovsdb_row *, perms), + "update", + OVSDB_TYPE_STRING, OVSDB_TYPE_VOID, UINT_MAX); + + if (!datum) { + VLOG_INFO_RL(&rl, "ovsdb_rbac_update: could not read \"update\" " + "column"); + goto denied; + } + + ru.table = table; + ru.columns = columns; + ru.role = role; + ru.id = id; + ru.perms = perms; + ru.modifiable = datum; + ru.permitted = true; + + ovsdb_query(table, condition, rbac_update_cb, &ru); + + if (ru.permitted) { + return true; + } + +denied: + return false; +} + +struct rbac_mutate_cbdata { + const struct ovsdb_table *table; + const struct ovsdb_mutation_set *mutations; /* columns to be mutated */ + const struct ovsdb_datum *modifiable; /* modifiable column names */ + const struct ovsdb_row *perms; + const char *role; + const char *id; + bool permitted; +}; + +static bool +rbac_mutate_cb(const struct ovsdb_row *row, void *rm_) +{ + struct rbac_mutate_cbdata *rm = rm_; + size_t i; + + if (!ovsdb_rbac_authorized(rm->perms, rm->id, row)) { + goto denied; + } + + for (i = 0; i < rm->mutations->n_mutations; i++) { + const struct ovsdb_column *column = rm->mutations->mutations[i].column; + + if (!rbac_column_modification_permitted(column, rm->modifiable)) { + goto denied; + } + } + + return true; + +denied: + rm->permitted = false; + return false; +} + +bool +ovsdb_rbac_mutate(const struct ovsdb *db, + struct ovsdb_table *table, + struct ovsdb_mutation_set *mutations, + struct ovsdb_condition *condition, + const char *role, const char *id) +{ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + const struct ovsdb_table_schema *ts = table->schema; + const struct ovsdb_datum *datum; + const struct ovsdb_row *perms; + struct rbac_mutate_cbdata rm; + + if (!db->rbac_role || !role || *role == '\0') { + return true; + } + if (!id) { + goto denied; + } + + perms = ovsdb_rbac_lookup_perms(db, role, ts->name); + + if (!perms) { + goto denied; + } + + datum = ovsdb_util_get_datum(CONST_CAST(struct ovsdb_row *, perms), + "update", + OVSDB_TYPE_STRING, OVSDB_TYPE_VOID, UINT_MAX); + + if (!datum) { + VLOG_INFO_RL(&rl, "ovsdb_rbac_mutate: could not read \"update\" " + "column"); + goto denied; + } + + rm.table = table; + rm.mutations = mutations; + rm.role = role; + rm.id = id; + rm.perms = perms; + rm.modifiable = datum; + rm.permitted = true; + + ovsdb_query(table, condition, rbac_mutate_cb, &rm); + + if (rm.permitted) { + return true; + } + +denied: + return false; +} diff --git a/ovsdb/rbac.h b/ovsdb/rbac.h new file mode 100644 index 00000000000..42f2ef9256c --- /dev/null +++ b/ovsdb/rbac.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2017 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVSDB_RBAC_H +#define OVSDB_RBAC_H 1 + +#include + +struct ovsdb; +struct ovsdb_column_set; +struct ovsdb_condition; +struct ovsdb_mutation_set; +struct ovsdb_row; +struct ovsdb_table; + +bool ovsdb_rbac_insert(const struct ovsdb *, + const struct ovsdb_table *, + const struct ovsdb_row *, + const char *role, const char *id); +bool ovsdb_rbac_delete(const struct ovsdb *, + struct ovsdb_table *, + struct ovsdb_condition *, + const char *role, const char *id); +bool ovsdb_rbac_update(const struct ovsdb *, + struct ovsdb_table *, + struct ovsdb_column_set *, + struct ovsdb_condition *condition, + const char *role, const char *id); +bool ovsdb_rbac_mutate(const struct ovsdb *, + struct ovsdb_table *, + struct ovsdb_mutation_set *, + struct ovsdb_condition *, + const char *role, const char *id); + +#endif /* ovsdb/rbac.h */ diff --git a/ovsdb/trigger.c b/ovsdb/trigger.c index a859983f4ef..1b960f5723e 100644 --- a/ovsdb/trigger.c +++ b/ovsdb/trigger.c @@ -32,7 +32,8 @@ void ovsdb_trigger_init(struct ovsdb_session *session, struct ovsdb *db, struct ovsdb_trigger *trigger, struct json *request, long long int now, - bool read_only) + bool read_only, const char *role, + const char *id) { trigger->session = session; trigger->db = db; @@ -42,6 +43,8 @@ ovsdb_trigger_init(struct ovsdb_session *session, struct ovsdb *db, trigger->created = now; trigger->timeout_msec = LLONG_MAX; trigger->read_only = read_only; + trigger->role = nullable_xstrdup(role); + trigger->id = nullable_xstrdup(id); ovsdb_trigger_try(trigger, now); } @@ -51,6 +54,8 @@ ovsdb_trigger_destroy(struct ovsdb_trigger *trigger) ovs_list_remove(&trigger->node); json_destroy(trigger->request); json_destroy(trigger->result); + free(trigger->role); + free(trigger->id); } bool @@ -114,6 +119,7 @@ ovsdb_trigger_try(struct ovsdb_trigger *t, long long int now) { t->result = ovsdb_execute(t->db, t->session, t->request, t->read_only, + t->role, t->id, now - t->created, &t->timeout_msec); if (t->result) { ovsdb_trigger_complete(t); diff --git a/ovsdb/trigger.h b/ovsdb/trigger.h index c8474a481eb..90246a4a42b 100644 --- a/ovsdb/trigger.h +++ b/ovsdb/trigger.h @@ -30,12 +30,15 @@ struct ovsdb_trigger { long long int created; /* Time created. */ long long int timeout_msec; /* Max wait duration. */ bool read_only; /* Database is in read only mode. */ + char *role; /* Role, for role-based access controls. */ + char *id; /* ID, for role-based access controls. */ }; void ovsdb_trigger_init(struct ovsdb_session *, struct ovsdb *, struct ovsdb_trigger *, struct json *request, long long int now, - bool read_only); + bool read_only, const char *role, + const char *id); void ovsdb_trigger_destroy(struct ovsdb_trigger *); bool ovsdb_trigger_is_complete(const struct ovsdb_trigger *); diff --git a/tests/automake.mk b/tests/automake.mk index c6bd1201f49..0c28c47e508 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -82,6 +82,7 @@ TESTSUITE_AT = \ tests/ovsdb-monitor.at \ tests/ovsdb-idl.at \ tests/ovsdb-lock.at \ + tests/ovsdb-rbac.at \ tests/ovs-vsctl.at \ tests/ovs-xapi-sync.at \ tests/stp.at \ diff --git a/tests/ovsdb-rbac.at b/tests/ovsdb-rbac.at new file mode 100644 index 00000000000..351eab9637c --- /dev/null +++ b/tests/ovsdb-rbac.at @@ -0,0 +1,375 @@ +AT_BANNER([OVSDB -- ovsdb-server rbac]) + +AT_SETUP([ovsdb-server/rbac 2]) +AT_KEYWORDS([ovsdb server rbac]) +AT_SKIP_IF([test "$HAVE_OPENSSL" = no]) + +RBAC_PKIDIR="$(pwd)" +RBAC_PKI="sh $abs_top_srcdir/utilities/ovs-pki.in --dir=$RBAC_PKIDIR/pki --log=$RBAC_PKIDIR/rbac-pki.log" +$RBAC_PKI -B 1024 init +$RBAC_PKI -B 1024 req+sign ovsdb-server switch +$RBAC_PKI -B 1024 -u req+sign client-1 switch +$RBAC_PKI -B 1024 -u req+sign client-2 switch + +AT_DATA([schema], + [[{"name": "mydb", + "tables": { + "Root": { + "columns": { + "connections": { + "type": { + "key": {"type": "uuid", "refTable": "Connection"}, + "min": 0, + "max": "unlimited"}}}, + "isRoot": true}, + "Connection": { + "columns": { + "target": { + "type": "string"}, + "role": { + "type": "string"}}}, + "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}, + "fixed_colors": { + "columns": { + "name": {"type": "string"}, "value": {"type": "integer"}}, + "indexes": [["name"]], + "isRoot": true}, + "user_colors": { + "columns": { + "creator": {"type": "string"}, + "name": {"type": "string"}, + "value": {"type": "integer"}}, + "indexes": [["name"]], + "isRoot": true}, + "other_colors": { + "columns": { + "creator": { + "type": {"key": {"type": "string"}, + "value": {"type": "string"}, + "min": 0, "max": "unlimited"}}, + "name": {"type": "string"}, + "value": {"type": "integer"}}, + "indexes": [["name"]], + "isRoot": true} + }, + "version": "5.1.3", + "cksum": "12345678 9" +} +]]) + +AT_CHECK([ovsdb-tool create db schema], [0], [ignore], [ignore]) +AT_CHECK( + [[ovsdb-tool transact db \ + '["mydb", + {"op": "insert", + "table": "Root", + "row": { + "connections": ["set", [["named-uuid", "x"]]]}}, + {"op": "insert", + "table": "Connection", + "uuid-name": "x", + "row": {"target": "pssl:0:127.0.0.1", + "role": "testrole"}}, + {"op": "insert", + "table": "fixed_colors", + "row": {"name": "red", + "value": '16711680'}}, + {"op": "insert", + "table": "RBAC_Role", + "row": {"name": "testrole", + "permissions": ["map", [["user_colors", ["named-uuid", "y"]], + ["other_colors", ["named-uuid", "z"]]]]}}, + {"op": "insert", + "table": "RBAC_Permission", + "uuid-name": "y", + "row": {"authorization": "creator", + "insert_delete": true, + "table": "user_colors", + "update": ["set", ["name", "value"]]}}, + {"op": "insert", + "table": "RBAC_Permission", + "uuid-name": "z", + "row": {"authorization": "creator:chassis", + "insert_delete": true, + "table": "user_colors", + "update": ["set", ["name", "value"]]}} +]']], [0], [ignore], [ignore]) + +AT_CHECK([ovsdb-server --log-file --detach --no-chdir --pidfile --remote=db:mydb,Root,connections \ + --private-key=$RBAC_PKIDIR/ovsdb-server-privkey.pem \ + --certificate=$RBAC_PKIDIR/ovsdb-server-cert.pem \ + --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \ + db], [0], [ignore], [ignore]) +PARSE_LISTENING_PORT([ovsdb-server.log], [SSL_PORT]) + +# Test 1: +# Attempt to insert a row into the "fixed_colors" table. This should +# fail as there are no permissions for role "testrole" for this table. +AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \ + --private-key=$RBAC_PKIDIR/client-1-privkey.pem \ + --certificate=$RBAC_PKIDIR/client-1-cert.pem \ + --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \ + ['["mydb", + {"op": "insert", + "table": "fixed_colors", + "row": {"name": "chartreuse", "value": '8388352'}} + ]']], [0], [stdout], [ignore]) +cat stdout >> output +AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"details":"RBAC rules for client \"client-1\" role \"testrole\" prohibit row insertion into table \"fixed_colors\".","error":"permission error"}]] +], [ignore]) + +# Test 2: +# Attempt to insert a row into the "user_colors" table with a client ID that +# does not match the value in the column used for authorization. This should +# fail the authorization check for insertion. +AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \ + --private-key=$RBAC_PKIDIR/client-1-privkey.pem \ + --certificate=$RBAC_PKIDIR/client-1-cert.pem \ + --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \ + ['["mydb", + {"op": "insert", + "table": "user_colors", + "row": {"creator": "client-2", "name": "chartreuse", "value": '8388352'}} + ]']], [0], [stdout], [ignore]) +cat stdout >> output +AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"details":"RBAC rules for client \"client-1\" role \"testrole\" prohibit row insertion into table \"user_colors\".","error":"permission error"}]] +], [ignore]) + +# Test 3: +# Attempt to insert a row into the "user_colors" table. This should +# succeed since role "testrole" has permissions for this table that +# allow row insertion. +AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \ + --private-key=$RBAC_PKIDIR/client-1-privkey.pem \ + --certificate=$RBAC_PKIDIR/client-1-cert.pem \ + --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \ + ['["mydb", + {"op": "insert", + "table": "user_colors", + "row": {"creator": "client-1", "name": "chartreuse", "value": '8388352'}} + ]']], [0], [stdout], [ignore]) +cat stdout >> output +AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"uuid":["uuid","<0>"]}]] +], [ignore]) + +# Test 4: +# Attempt to update a column in the "user_colors" table. This should +# succeed since role "testrole" has permissions for this table that +# allow update of the "value" column when ID is equal to the value in +# the "creator" column. +AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \ + --private-key=$RBAC_PKIDIR/client-1-privkey.pem \ + --certificate=$RBAC_PKIDIR/client-1-cert.pem \ + --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \ + ['["mydb", + {"op": "update", + "table": "user_colors", + "where": [["name", "==", "chartreuse"]], + "row": {"value": '8388353'}} + ]']], [0], [stdout], [ignore]) +cat stdout >> output +AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"count":1}]] +], [ignore]) + +# Test 5: +# Attempt to update a column in the "user_colors" table. Same as +# previous test, but with a different client ID. This should fail +# the RBAC authorization test because "client-2" does not match the +# "creator" column for this row. +AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \ + --private-key=$RBAC_PKIDIR/client-2-privkey.pem \ + --certificate=$RBAC_PKIDIR/client-2-cert.pem \ + --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \ + ['["mydb", + {"op": "update", + "table": "user_colors", + "where": [["name", "==", "chartreuse"]], + "row": {"value": '8388354'}} + ]']], [0], [stdout], [ignore]) +cat stdout >> output +AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"details":"RBAC rules for client \"client-2\" role \"testrole\" prohibit modification of table \"user_colors\".","error":"permission error"}]] +], [ignore]) + +# Test 6: +# Attempt to mutate a column in the "user_colors" table. This should +# succeed since role "testrole" has permissions for this table that +# allow update of the "value" column when ID is equal to the value in +# the "creator" column. +AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \ + --private-key=$RBAC_PKIDIR/client-1-privkey.pem \ + --certificate=$RBAC_PKIDIR/client-1-cert.pem \ + --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \ + ['["mydb", + {"op": "mutate", + "table": "user_colors", + "where": [["name", "==", "chartreuse"]], + "mutations": [["value", "+=", '10']]} + ]']], [0], [stdout], [ignore]) +cat stdout >> output +AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"count":1}]] +], [ignore]) + +# Test 7: +# Attempt to mutate a column in the "user_colors" table. Same as +# previous test, but with a different client ID. This should fail +# the RBAC authorization test because "client-2" does not match the +# "creator" column for this row. +AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \ + --private-key=$RBAC_PKIDIR/client-2-privkey.pem \ + --certificate=$RBAC_PKIDIR/client-2-cert.pem \ + --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \ + ['["mydb", + {"op": "mutate", + "table": "user_colors", + "where": [["name", "==", "chartreuse"]], + "mutations": [["value", "+=", '10']]} + ]']], [0], [stdout], [ignore]) +cat stdout >> output +AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"details":"RBAC rules for client \"client-2\" role \"testrole\" prohibit mutate operation on table \"user_colors\".","error":"permission error"}]] +], [ignore]) + +# Test 8: +# Attempt to delete a row from the "user_colors" table. This should fail +# the RBAC authorization test because "client-2" does not match the +# "creator" column for this row. +AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \ + --private-key=$RBAC_PKIDIR/client-2-privkey.pem \ + --certificate=$RBAC_PKIDIR/client-2-cert.pem \ + --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \ + ['["mydb", + {"op": "delete", + "table": "user_colors", + "where": [["name", "==", "chartreuse"]]} + ]']], [0], [stdout], [ignore]) +cat stdout >> output +AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"details":"RBAC rules for client \"client-2\" role \"testrole\" prohibit row deletion from table \"user_colors\".","error":"permission error"}]] +], [ignore]) + +# Test 9: +# Attempt to delete a row from the "user_colors" table. This should pass +# the RBAC authorization test because "client-1" does matches the +# "creator" column for this row. +AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \ + --private-key=$RBAC_PKIDIR/client-1-privkey.pem \ + --certificate=$RBAC_PKIDIR/client-1-cert.pem \ + --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \ + ['["mydb", + {"op": "delete", + "table": "user_colors", + "where": [["name", "==", "chartreuse"]]} + ]']], [0], [stdout], [ignore]) +cat stdout >> output +AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"count":1}]] +], [ignore]) + +# Test 10: +# Attempt to insert a row into the "other_colors" table. This should +# succeed since role "testrole" has permissions for this table that +# allow row insertion. +AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \ + --private-key=$RBAC_PKIDIR/client-1-privkey.pem \ + --certificate=$RBAC_PKIDIR/client-1-cert.pem \ + --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \ + ['["mydb", + {"op": "insert", + "table": "other_colors", + "row": {"creator": ["map",[["chassis", "client-1"]]], "name": "seafoam", "value": '7466680'}} + ]']], [0], [stdout], [ignore]) +cat stdout >> output +AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"uuid":["uuid","<0>"]}]] +], [ignore]) + +# Test 11: +# Attempt to update a column in the "user_colors" table. This should +# succeed since role "testrole" has permissions for this table that +# allow update of the "value" column when ID is equal to the value in +# the "creator" column. +AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \ + --private-key=$RBAC_PKIDIR/client-1-privkey.pem \ + --certificate=$RBAC_PKIDIR/client-1-cert.pem \ + --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \ + ['["mydb", + {"op": "update", + "table": "other_colors", + "where": [["name", "==", "seafoam"]], + "row": {"value": '8388353'}} + ]']], [0], [stdout], [ignore]) +cat stdout >> output +AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"count":1}]] +], [ignore]) + +# Test 12: +# Attempt to update a column in the "other_colors" table. Same as +# previous test, but with a different client ID. This should fail +# the RBAC authorization test because "client-2" does not match the +# "creator" column for this row. +AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \ + --private-key=$RBAC_PKIDIR/client-2-privkey.pem \ + --certificate=$RBAC_PKIDIR/client-2-cert.pem \ + --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \ + ['["mydb", + {"op": "update", + "table": "other_colors", + "where": [["name", "==", "seafoam"]], + "row": {"value": '8388354'}} + ]']], [0], [stdout], [ignore]) +cat stdout >> output +AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"details":"RBAC rules for client \"client-2\" role \"testrole\" prohibit modification of table \"other_colors\".","error":"permission error"}]] +], [ignore]) + +# Test 13: +# Attempt to delete a row from the "other_colors" table. This should fail +# the RBAC authorization test because "client-2" does not match the +# "creator" column for this row. +AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \ + --private-key=$RBAC_PKIDIR/client-2-privkey.pem \ + --certificate=$RBAC_PKIDIR/client-2-cert.pem \ + --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \ + ['["mydb", + {"op": "delete", + "table": "other_colors", + "where": [["name", "==", "seafoam"]]} + ]']], [0], [stdout], [ignore]) +cat stdout >> output +AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"details":"RBAC rules for client \"client-2\" role \"testrole\" prohibit row deletion from table \"other_colors\".","error":"permission error"}]] +], [ignore]) + +# Test 14: +# Attempt to delete a row from the "other_colors" table. This should pass +# the RBAC authorization test because "client-1" does matches the +# "creator" column for this row. +AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \ + --private-key=$RBAC_PKIDIR/client-1-privkey.pem \ + --certificate=$RBAC_PKIDIR/client-1-cert.pem \ + --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \ + ['["mydb", + {"op": "delete", + "table": "other_colors", + "where": [["name", "==", "seafoam"]]} + ]']], [0], [stdout], [ignore]) +cat stdout >> output +AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"count":1}]] +], [ignore]) + +OVSDB_SERVER_SHUTDOWN +AT_CLEANUP diff --git a/tests/ovsdb.at b/tests/ovsdb.at index fe617a562e9..8a389b8aad3 100644 --- a/tests/ovsdb.at +++ b/tests/ovsdb.at @@ -148,3 +148,4 @@ m4_include([tests/ovsdb-server.at]) m4_include([tests/ovsdb-monitor.at]) m4_include([tests/ovsdb-idl.at]) m4_include([tests/ovsdb-lock.at]) +m4_include([tests/ovsdb-rbac.at]) diff --git a/tests/test-ovsdb.c b/tests/test-ovsdb.c index 0f2101e5756..9c51e323c1b 100644 --- a/tests/test-ovsdb.c +++ b/tests/test-ovsdb.c @@ -1435,7 +1435,7 @@ do_execute__(struct ovs_cmdl_context *ctx, bool ro) char *s; params = parse_json(ctx->argv[i]); - result = ovsdb_execute(db, NULL, params, ro, 0, NULL); + result = ovsdb_execute(db, NULL, params, ro, NULL, NULL, 0, NULL); s = json_to_string(result, JSSF_SORT); printf("%s\n", s); free(s); @@ -1513,7 +1513,8 @@ do_trigger(struct ovs_cmdl_context *ctx) json_destroy(params); } else { struct test_trigger *t = xmalloc(sizeof *t); - ovsdb_trigger_init(&session, db, &t->trigger, params, now, false); + ovsdb_trigger_init(&session, db, &t->trigger, params, now, false, + NULL, NULL); t->number = number++; if (ovsdb_trigger_is_complete(&t->trigger)) { do_trigger_dump(t, now, "immediate");