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
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)
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 */
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)
{
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. */
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 *);
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++;
ovsdb/monitor.h \
ovsdb/query.c \
ovsdb/query.h \
+ ovsdb/rbac.c \
+ ovsdb/rbac.h \
ovsdb/replication.c \
ovsdb/replication.h \
ovsdb/row.c \
#include "ovsdb-parser.h"
#include "ovsdb.h"
#include "query.h"
+#include "rbac.h"
#include "row.h"
#include "server.h"
#include "table.h"
struct ovsdb_txn *txn;
struct ovsdb_symbol_table *symtab;
bool durable;
+ const char *role;
+ const char *id;
/* Triggers. */
long long int elapsed_msec;
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;
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;
}
}
}
+
+ 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);
struct ovsdb_txn *txn;
const struct ovsdb_row *row;
const struct ovsdb_column_set *columns;
+ const char *role;
+ const char *id;
};
static bool
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));
}
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));
}
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));
}
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(
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) {
ovsdb_jsonrpc_session_close_all(remote);
pstream_close(remote->listener);
shash_delete(&remote->server->remotes, node);
+ free(remote->role);
free(remote);
}
/* 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);
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);
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"
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,
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 <monitor-request> to be used instead of an array; it is treated
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",
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");
.
.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
\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
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
/* -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);
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'},
show_log_verbosity++;
break;
+ case OPT_RBAC_ROLE:
+ rbac_role = optarg;
+ break;
+
case 'h':
usage();
"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);
}
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);
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)
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);
}
}
+ /* Use RBAC roles table if present. */
+ db->rbac_role = ovsdb_get_table(db, "RBAC_Role");
+
return db;
}
/* 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 *);
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);
\f
--- /dev/null
+/*
+ * 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 "rbac.h"
+
+#include <limits.h>
+
+#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;
+}
--- /dev/null
+/*
+ * 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 <stdbool.h>
+
+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 */
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;
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);
}
ovs_list_remove(&trigger->node);
json_destroy(trigger->request);
json_destroy(trigger->result);
+ free(trigger->role);
+ free(trigger->id);
}
bool
{
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);
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 *);
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 \
--- /dev/null
+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
m4_include([tests/ovsdb-monitor.at])
m4_include([tests/ovsdb-idl.at])
m4_include([tests/ovsdb-lock.at])
+m4_include([tests/ovsdb-rbac.at])
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);
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");