[ovs-dev] [RFC 3/5] ovsdb: add support for role-based access controls
Lance Richardson
lrichard at redhat.com
Mon Mar 27 18:56:11 UTC 2017
Add suport for ovsdb RBAC (role-based access control). This includes:
- Support for new "--rbac <table>" 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 <lrichard at redhat.com>
---
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 <config.h>
+
#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 <config.h>
+#include <limits.h>
+
+#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");
--
2.7.4
More information about the dev
mailing list