From patchwork Mon Mar 27 18:56:11 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lance Richardson X-Patchwork-Id: 743963 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 3vsNb54RNBz9s73 for ; Tue, 28 Mar 2017 05:58:25 +1100 (AEDT) Received: from mail.linux-foundation.org (localhost [127.0.0.1]) by mail.linuxfoundation.org (Postfix) with ESMTP id B6DE1B63; Mon, 27 Mar 2017 18:56:25 +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 56929B6D for ; Mon, 27 Mar 2017 18:56:19 +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 43C4A160 for ; Mon, 27 Mar 2017 18:56:17 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id A148FC0528C3; Mon, 27 Mar 2017 18:56:16 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mx1.redhat.com A148FC0528C3 Authentication-Results: ext-mx07.extmail.prod.ext.phx2.redhat.com; dmarc=none (p=none dis=none) header.from=redhat.com Authentication-Results: ext-mx07.extmail.prod.ext.phx2.redhat.com; spf=pass smtp.mailfrom=lrichard@redhat.com DKIM-Filter: OpenDKIM Filter v2.11.0 mx1.redhat.com A148FC0528C3 Received: from localhost.localdomain.com (ovpn-123-223.rdu2.redhat.com [10.10.123.223]) by smtp.corp.redhat.com (Postfix) with ESMTP id 268E05C886; Mon, 27 Mar 2017 18:56:16 +0000 (UTC) From: Lance Richardson To: dev@openvswitch.org, blp@ovn.org, russellb@ovn.org, mickeys.dev@gmail.com Date: Mon, 27 Mar 2017 14:56:11 -0400 Message-Id: <1490640973-21630-4-git-send-email-lrichard@redhat.com> In-Reply-To: <1490640973-21630-1-git-send-email-lrichard@redhat.com> References: <1490640973-21630-1-git-send-email-lrichard@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.16 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.31]); Mon, 27 Mar 2017 18:56:16 +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] [RFC 3/5] ovsdb: add support for role-based access controls 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 suport for ovsdb RBAC (role-based access control). This includes: - Support for new "--rbac " command-line option to ovsdb-server, to specify the RBAC roles table to be used. This table has one row per role, with each row having a "name" column (role name) and a "permissions" column (map of table name to UUID of row in separate permission table.) The permission table has one row per access control configuration, with columns: "name" - name of table to which this row applies "authorization" - set of column names and column:key pairs to be compared against client ID to determine authorization status "insert_delete" - boolean, true if insertions and authorized deletions are allowed. "update" - Set of columns and column:key pairs for which authorized updates are allowed. - Support for a new "role" column in the remote configuration table. - Logic for applying the RBAC role and permission tables, in combination with session role and client id, to determine whether operations modifying database contents should be permitted. Signed-off-by: Lance Richardson --- lib/jsonrpc.c | 10 ++ lib/jsonrpc.h | 2 + ovsdb/automake.mk | 2 + ovsdb/execution.c | 38 ++++++- ovsdb/jsonrpc-server.c | 6 +- ovsdb/jsonrpc-server.h | 1 + ovsdb/mutation.c | 11 +- ovsdb/mutation.h | 6 +- ovsdb/ovsdb-server.c | 70 ++++++++++++- ovsdb/ovsdb-tool.c | 2 +- ovsdb/ovsdb-util.c | 40 +++++++ ovsdb/ovsdb-util.h | 3 + ovsdb/ovsdb.h | 1 + ovsdb/rbac.c | 279 +++++++++++++++++++++++++++++++++++++++++++++++++ ovsdb/rbac.h | 26 +++++ ovsdb/trigger.c | 8 +- ovsdb/trigger.h | 5 +- tests/test-ovsdb.c | 7 +- 18 files changed, 503 insertions(+), 14 deletions(-) create mode 100644 ovsdb/rbac.c create mode 100644 ovsdb/rbac.h diff --git a/lib/jsonrpc.c b/lib/jsonrpc.c index a0ade9c..2fae057 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 982017a..6a82954 100644 --- a/lib/jsonrpc.h +++ b/lib/jsonrpc.h @@ -130,5 +130,7 @@ 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/ovsdb/automake.mk b/ovsdb/automake.mk index c218bf5..ac0f741 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 e2d320e..2c9ca3d 100644 --- a/ovsdb/execution.c +++ b/ovsdb/execution.c @@ -32,6 +32,7 @@ #include "table.h" #include "timeval.h" #include "transaction.h" +#include "rbac.h" struct ovsdb_execution { struct ovsdb *db; @@ -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; @@ -305,6 +311,13 @@ ovsdb_execute_insert(struct ovsdb_execution *x, struct ovsdb_parser *parser, return error; } + if (!ovsdb_rbac_insert(table, x->role, x->id)) { + return ovsdb_syntax_error(parser->json, + "Insert operation prohibited by RBAC, " + "client \"%s\" role \"%s\" table \"%s\"", + x->id, x->role, table->schema->name); + } + if (uuid_name) { struct ovsdb_symbol *symbol; @@ -459,6 +472,17 @@ ovsdb_execute_update(struct ovsdb_execution *x, struct ovsdb_parser *parser, column->name, table->schema->name); break; } + + if (!ovsdb_rbac_modify(table, x->role, x->id, column->name, row)) { + return ovsdb_syntax_error(parser->json, + "Update operation prohibited by RBAC, " + "client \"%s\" role \"%s\" table \"%s\"" + "column \"%s\"", + x->id, x->role, table->schema->name, + column->name); + break; + } + } } if (!error) { @@ -486,6 +510,8 @@ struct mutate_row_cbdata { struct ovsdb_txn *txn; const struct ovsdb_mutation_set *mutations; struct ovsdb_error **error; + const char *role; + const char *id; }; static bool @@ -495,7 +521,7 @@ mutate_row_cb(const struct ovsdb_row *row, void *mr_) mr->n_matches++; *mr->error = ovsdb_mutation_set_execute(ovsdb_txn_row_modify(mr->txn, row), - mr->mutations); + mr->mutations, mr->role, mr->id); return *mr->error == NULL; } @@ -529,6 +555,8 @@ ovsdb_execute_mutate(struct ovsdb_execution *x, struct ovsdb_parser *parser, mr.txn = x->txn; mr.mutations = &mutations; mr.error = &error; + mr.role = x->role; + mr.id = x->id; ovsdb_query(table, &condition, mutate_row_cb, &mr); json_object_put(result, "count", json_integer_create(mr.n_matches)); } @@ -544,6 +572,8 @@ struct delete_row_cbdata { size_t n_matches; const struct ovsdb_table *table; struct ovsdb_txn *txn; + const char *role; + const char *id; }; static bool @@ -551,6 +581,10 @@ delete_row_cb(const struct ovsdb_row *row, void *dr_) { struct delete_row_cbdata *dr = dr_; + if (!ovsdb_rbac_delete(row->table, dr->role, dr->id, row)) { + return false; + } + dr->n_matches++; ovsdb_txn_row_delete(dr->txn, row); @@ -579,6 +613,8 @@ ovsdb_execute_delete(struct ovsdb_execution *x, struct ovsdb_parser *parser, dr.n_matches = 0; dr.table = table; dr.txn = x->txn; + dr.role = x->role; + dr.id = x->id; ovsdb_query(table, &condition, delete_row_cb, &dr); json_object_put(result, "count", json_integer_create(dr.n_matches)); diff --git a/ovsdb/jsonrpc-server.c b/ovsdb/jsonrpc-server.c index 1ba6bb3..6edfe24 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 = options->role ? xstrdup(options->role) : NULL; 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 3cacbb6..1add327 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/mutation.c b/ovsdb/mutation.c index e5d192e..0959f06 100644 --- a/ovsdb/mutation.c +++ b/ovsdb/mutation.c @@ -29,6 +29,7 @@ #include "table.h" #include "util.h" +#include "rbac.h" struct ovsdb_error * ovsdb_mutator_from_string(const char *name, enum ovsdb_mutator *mutator) @@ -347,7 +348,8 @@ ovsdb_mutation_check_count(struct ovsdb_datum *dst, struct ovsdb_error * ovsdb_mutation_set_execute(struct ovsdb_row *row, - const struct ovsdb_mutation_set *set) + const struct ovsdb_mutation_set *set, + const char *role, const char *id) { size_t i; @@ -359,6 +361,13 @@ ovsdb_mutation_set_execute(struct ovsdb_row *row, const struct ovsdb_type *arg_type = &m->type; struct ovsdb_error *error; + if (!ovsdb_rbac_modify(row->table, role, id, m->column->name, row)) { + return ovsdb_error("mutate execution denied by RBAC", + "client \"%s\" role \"%s\" table \"%s\"" + "column \"%s\"", + id, role, row->table->schema->name, + m->column->name); + } switch (m->mutator) { case OVSDB_M_ADD: error = mutate_scalar(dst_type, dst, &arg->keys[0], &add_mutation); diff --git a/ovsdb/mutation.h b/ovsdb/mutation.h index 7566ef1..42fe1ac 100644 --- a/ovsdb/mutation.h +++ b/ovsdb/mutation.h @@ -51,6 +51,8 @@ struct ovsdb_mutation { const struct ovsdb_column *column; struct ovsdb_datum arg; struct ovsdb_type type; + const char *role; + const char *id; }; struct ovsdb_mutation_set { @@ -67,6 +69,8 @@ struct ovsdb_error *ovsdb_mutation_set_from_json( struct json *ovsdb_mutation_set_to_json(const struct ovsdb_mutation_set *); void ovsdb_mutation_set_destroy(struct ovsdb_mutation_set *); struct ovsdb_error *ovsdb_mutation_set_execute( - struct ovsdb_row *, const struct ovsdb_mutation_set *) OVS_WARN_UNUSED_RESULT; + struct ovsdb_row *, const struct ovsdb_mutation_set *, + const char *, const char *) + OVS_WARN_UNUSED_RESULT; #endif /* ovsdb/mutation.h */ diff --git a/ovsdb/ovsdb-server.c b/ovsdb/ovsdb-server.c index f1e8fbf..3ebcb81 100644 --- a/ovsdb/ovsdb-server.c +++ b/ovsdb/ovsdb-server.c @@ -58,6 +58,7 @@ #include "perf-counter.h" #include "ovsdb-util.h" #include "openvswitch/vlog.h" +#include "rbac.h" VLOG_DEFINE_THIS_MODULE(ovsdb_server); @@ -116,9 +117,14 @@ static void close_db(struct db *db); static void parse_options(int *argc, char **argvp[], struct sset *remotes, char **unixctl_pathp, char **run_command, char **sync_from, - char **sync_exclude, bool *is_backup); + char **sync_exclude, bool *is_backup, + char **rbac); OVS_NO_RETURN static void usage(void); +static char *ovsdb_parse_rbac(const struct shash *all_dbs, + char *rbac, + const struct ovsdb_table **rbac_roles_table); + static char *reconfigure_remotes(struct ovsdb_jsonrpc_server *, const struct shash *all_dbs, struct sset *remotes); @@ -259,6 +265,7 @@ main(int argc, char *argv[]) struct sset remotes, db_filenames; char *sync_from, *sync_exclude; bool is_backup; + char *rbac; const char *db_filename; struct process *run_process; bool exiting; @@ -269,6 +276,7 @@ main(int argc, char *argv[]) struct shash_node *node, *next; char *error; int i; + const struct ovsdb_table *rbac_roles_table = NULL; ovs_cmdl_proctitle_init(argc, argv); set_program_name(argv[0]); @@ -278,7 +286,7 @@ main(int argc, char *argv[]) bool active = false; parse_options(&argc, &argv, &remotes, &unixctl_path, &run_command, - &sync_from, &sync_exclude, &active); + &sync_from, &sync_exclude, &active, &rbac); is_backup = sync_from && !active; daemon_become_new_user(false); @@ -431,6 +439,13 @@ main(int argc, char *argv[]) ovsdb_replication_init(sync_from, sync_exclude, &all_dbs, server_uuid); } + error = ovsdb_parse_rbac(&all_dbs, rbac, &rbac_roles_table); + if (error) { + ovs_fatal(0, "%s", error); + } else { + ovsdb_rbac_set_roles(rbac_roles_table); + } + main_loop(jsonrpc, &all_dbs, unixctl, &remotes, run_process, &exiting, &is_backup); @@ -675,6 +690,41 @@ query_db_string(const struct shash *all_dbs, const char *name, } } +static char * +ovsdb_parse_rbac(const struct shash *all_dbs, char *rbac, + const struct ovsdb_table **rbac_roles_table) +{ + const char *db_name, *table_name; + const struct ovsdb_table *table; + const struct db *db; + char *save_ptr = NULL; + + if (!rbac) { + return NULL; + } + + strtok_r(rbac, ":", &save_ptr); /* "db:" */ + db_name = strtok_r(NULL, ",", &save_ptr); + table_name = strtok_r(NULL, ",", &save_ptr); + if (!db_name || !table_name) { + return xasprintf("\"%s\": invalid syntax", rbac); + } + + db = find_db(all_dbs, db_name); + if (!db) { + return xasprintf("\"%s\": no database named %s", rbac, db_name); + } + + table = ovsdb_get_table(db->db, table_name); + if (!table) { + return xasprintf("\"%s\": no table named %s", rbac, table_name); + } + + *rbac_roles_table = table; + free(rbac); + return NULL; +} + static struct ovsdb_jsonrpc_options * add_remote(struct shash *remotes, const char *target) { @@ -698,7 +748,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 (!read_string_column(row, "target", &target) || !target) { VLOG_INFO_RL(&rl, "Table `%s' has missing or invalid `target' column", @@ -716,6 +766,10 @@ add_manager_options(struct shash *remotes, const struct ovsdb_row *row) if (read_bool_column(row, "read_only", &read_only)) { options->read_only = read_only; } + free(options->role); + if (read_string_column(row, "role", &role) && role) { + options->role = xstrdup(role); /* FIXME: free role */ + } options->dscp = DSCP_DEFAULT; dscp_string = read_map_string_column(row, "other_config", "dscp"); @@ -1356,7 +1410,8 @@ ovsdb_server_get_sync_status(struct unixctl_conn *conn, int argc OVS_UNUSED, static void parse_options(int *argcp, char **argvp[], struct sset *remotes, char **unixctl_pathp, char **run_command, - char **sync_from, char **sync_exclude, bool *active) + char **sync_from, char **sync_exclude, bool *active, + char **rbac) { enum { OPT_REMOTE = UCHAR_MAX + 1, @@ -1367,6 +1422,7 @@ parse_options(int *argcp, char **argvp[], OPT_SYNC_FROM, OPT_SYNC_EXCLUDE, OPT_ACTIVE, + OPT_RBAC, VLOG_OPTION_ENUMS, DAEMON_OPTION_ENUMS, SSL_OPTION_ENUMS, @@ -1387,6 +1443,7 @@ parse_options(int *argcp, char **argvp[], {"sync-from", required_argument, NULL, OPT_SYNC_FROM}, {"sync-exclude-tables", required_argument, NULL, OPT_SYNC_EXCLUDE}, {"active", no_argument, NULL, OPT_ACTIVE}, + {"rbac", required_argument, NULL, OPT_RBAC}, {NULL, 0, NULL, 0}, }; char *short_options = ovs_cmdl_long_options_to_short_options(long_options); @@ -1395,6 +1452,7 @@ parse_options(int *argcp, char **argvp[], *sync_from = NULL; *sync_exclude = NULL; + *rbac = NULL; sset_init(remotes); for (;;) { int c; @@ -1473,6 +1531,10 @@ parse_options(int *argcp, char **argvp[], *active = true; break; + case OPT_RBAC: + *rbac = xstrdup(optarg); + break; + case '?': exit(EXIT_FAILURE); diff --git a/ovsdb/ovsdb-tool.c b/ovsdb/ovsdb-tool.c index 8d7e76a..fb8eb1e 100644 --- a/ovsdb/ovsdb-tool.c +++ b/ovsdb/ovsdb-tool.c @@ -366,7 +366,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, NULL, NULL, 0, NULL); json_destroy(request); print_and_free_json(result); diff --git a/ovsdb/ovsdb-util.c b/ovsdb/ovsdb-util.c index 749ac0e..0a5394e 100644 --- a/ovsdb/ovsdb-util.c +++ b/ovsdb/ovsdb-util.c @@ -13,6 +13,8 @@ * limitations under the License. */ +#include + #include "row.h" #include "sset.h" #include "table.h" @@ -85,6 +87,44 @@ read_map_string_column(const struct ovsdb_row *row, const char *column_name, 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 * +read_map_string_uuid_column(const struct ovsdb_row *row, + const char *column_name, + const char *key) +{ + union ovsdb_atom *atom_key = NULL, *atom_value = NULL; + const struct ovsdb_table *ref_table; + const struct ovsdb_column *column; + const struct ovsdb_datum *datum; + size_t i; + + 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; + } + + datum = &row->fields[column->index]; + + for (i = 0; i < datum->n; i++) { + atom_key = &datum->keys[i]; + if (!strcmp(atom_key->string, key)) { + atom_value = &datum->values[i]; + break; + } + } + + if (!atom_value) { + return NULL; + } + ref_table = column->type.value.u.uuid.refTable; + return ovsdb_table_get_row(ref_table, &atom_value->uuid); +} + + const union ovsdb_atom * 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 2354216..5340684 100644 --- a/ovsdb/ovsdb-util.h +++ b/ovsdb/ovsdb-util.h @@ -24,6 +24,9 @@ struct ovsdb_datum * get_datum(struct ovsdb_row *row, const char *column_name, const char * read_map_string_column(const struct ovsdb_row *row, const char *column_name, const char *key); +const struct ovsdb_row *read_map_string_uuid_column(const struct ovsdb_row *r, + const char *column_name, + const char *key); const union ovsdb_atom * read_column(const struct ovsdb_row *row, const char *column_name, enum ovsdb_atomic_type type); diff --git a/ovsdb/ovsdb.h b/ovsdb/ovsdb.h index fc45c80..793f829 100644 --- a/ovsdb/ovsdb.h +++ b/ovsdb/ovsdb.h @@ -73,6 +73,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 0000000..f8cb8f7 --- /dev/null +++ b/ovsdb/rbac.c @@ -0,0 +1,279 @@ +/* + * 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 + +#include "column.h" +#include "condition.h" +#include "file.h" +#include "mutation.h" +#include "ovsdb-data.h" +#include "ovsdb-error.h" +#include "ovsdb-parser.h" +#include "ovsdb.h" +#include "query.h" +#include "row.h" +#include "server.h" +#include "table.h" +#include "timeval.h" +#include "transaction.h" +#include "ovsdb-util.h" +#include "rbac.h" +#include "openvswitch/vlog.h" + +VLOG_DEFINE_THIS_MODULE(ovsdb_rbac); + +const struct ovsdb_table *rbac_roles_table; + +void +ovsdb_rbac_set_roles(const struct ovsdb_table *table) +{ + rbac_roles_table = table; +} + +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; + const struct ovsdb_row *row; + + column = ovsdb_table_schema_get_column(table->schema, column_name); + + if (column) { + HMAP_FOR_EACH (row, hmap_node, &table->rows) { + const struct ovsdb_datum *datum; + size_t i; + + datum = &row->fields[column->index]; + for (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 char *role, const char *table) +{ + 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(rbac_roles_table, "name", role); + if (!role_row) { + VLOG_INFO("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("rbac: \"permissions\" column not present in rbac roles " + "table"); + return NULL; + } + perm_row = 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) +{ + const struct ovsdb_datum *datum; + size_t i; + + datum = get_datum(CONST_CAST(struct ovsdb_row *, perms), "authorization", + OVSDB_TYPE_STRING, OVSDB_TYPE_VOID, UINT_MAX); + + if (!datum) { + VLOG_INFO("rbac: error reading authorization column"); + return false; + } + + for (i = 0; i < datum->n; i++) { + 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(name, ":", &save_ptr); + key = strtok_r(NULL, ":", &save_ptr); + + if (col_name && key) { + value = read_map_string_column(row, col_name, key); + } + free(tmp); + } else { + read_string_column(row, name, &value); + } + if (value && !strcmp(value, id)) { + return true; + } + } + + VLOG_INFO("rbac: authorization denied"); + return false; +} + +bool +ovsdb_rbac_insert(const struct ovsdb_table *table, + const char *role, const char *id) +{ + const struct ovsdb_table_schema *ts = table->schema; + const struct ovsdb_row *perms; + bool insdel; + + if (!rbac_roles_table || !role || *role == '\0') { + return true; + } + + if (!id) { + VLOG_INFO("rbac: client id not available"); + goto denied; + } + + perms = ovsdb_rbac_lookup_perms(role, ts->name); + + if (!perms) { + goto denied; + } + + if (!read_bool_column(perms, "insert_delete", &insdel)) { + return false; + } + + if (insdel) { + return true; + } else { + VLOG_INFO("rbac: \"insert_delete\" is false"); + } + +denied: + VLOG_INFO("rbac: insertion of row in table %s not permitted " + "(role %s, id %s)", ts->name, role, id); + return false; +} + +bool +ovsdb_rbac_delete(const struct ovsdb_table *table, + const char *role, const char *id, + const struct ovsdb_row *row) +{ + const struct ovsdb_table_schema *ts = table->schema; + const struct ovsdb_row *perms; + bool insdel; + + if (!rbac_roles_table || !role || *role == '\0') { + return true; + } + if (!id) { + VLOG_INFO("rbac: client id not available"); + goto denied; + } + + perms = ovsdb_rbac_lookup_perms(role, ts->name); + + if (!perms) { + goto denied; + } + + if (!ovsdb_rbac_authorized(perms, id, row)) { + goto denied; + } + + if (!read_bool_column(perms, "insert_delete", &insdel)) { + return false; + } + + if (insdel) { + return true; + } else { + VLOG_INFO("rbac: \"insert_delete\" is false"); + } + +denied: + VLOG_INFO("rbac: deletion of row in table %s not permitted " + "(role %s, id %s)", ts->name, role, id); + return false; +} + +bool +ovsdb_rbac_modify(const struct ovsdb_table *table, + const char *role, const char *id,const char *col, + const struct ovsdb_row *row) +{ + const struct ovsdb_table_schema *ts = table->schema; + const struct ovsdb_datum *datum; + const struct ovsdb_row *perms; + size_t i; + + if (!rbac_roles_table || !role || *role == '\0') { + return true; + } + if (!id) { + VLOG_INFO("rbac: client id not available"); + goto denied; + } + + perms = ovsdb_rbac_lookup_perms(role, ts->name); + + if (!perms) { + goto denied; + } + + if (!ovsdb_rbac_authorized(perms, id, row)) { + goto denied; + } + + datum = get_datum(CONST_CAST(struct ovsdb_row *, perms), "update", + OVSDB_TYPE_STRING, OVSDB_TYPE_VOID, UINT_MAX); + + if (!datum) { + VLOG_INFO("could not read \"update\" column"); + goto denied; + } + + for (i = 0; i < datum->n; i++) { + char *name = datum->keys[i].string; + if (!strcmp(name, col)) { + return true; + } + } + +denied: + VLOG_INFO("rbac: modification of column \"%s\" in table \"%s\" not " + "permitted (role %s, id %s)", col, ts->name, role, id); + return false; +} diff --git a/ovsdb/rbac.h b/ovsdb/rbac.h new file mode 100644 index 0000000..c22293e --- /dev/null +++ b/ovsdb/rbac.h @@ -0,0 +1,26 @@ +/* + * 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. + */ + +bool ovsdb_rbac_insert(const struct ovsdb_table *, + const char *, const char *); +bool ovsdb_rbac_delete(const struct ovsdb_table *, + const char *, const char *, + const struct ovsdb_row *); +bool ovsdb_rbac_modify(const struct ovsdb_table *, + const char *, const char *, + const char *, const struct ovsdb_row *); +void ovsdb_rbac_set_roles(const struct ovsdb_table *); + diff --git a/ovsdb/trigger.c b/ovsdb/trigger.c index a859983..94b95ea 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 = role ? xstrdup(role): NULL; + trigger->id = id ? xstrdup(id): NULL; 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 c8474a4..90246a4 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/test-ovsdb.c b/tests/test-ovsdb.c index 09e4f0d..3726c7d 100644 --- a/tests/test-ovsdb.c +++ b/tests/test-ovsdb.c @@ -1106,7 +1106,7 @@ do_execute_mutations(struct ovs_cmdl_context *ctx) struct ovsdb_row *row; row = ovsdb_row_clone(rows[j]); - error = ovsdb_mutation_set_execute(row, &sets[i]); + error = ovsdb_mutation_set_execute(row, &sets[i], NULL, NULL); printf("row %"PRIuSIZE": ", j); if (error) { @@ -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");