]> git.proxmox.com Git - ovs.git/commitdiff
ovsdb: add support for role-based access controls
authorLance Richardson <lrichard@redhat.com>
Wed, 31 May 2017 23:04:32 +0000 (19:04 -0400)
committerBen Pfaff <blp@ovn.org>
Thu, 8 Jun 2017 20:58:27 +0000 (13:58 -0700)
Add suport for ovsdb RBAC (role-based access control). This includes:

   - Support for "RBAC_Role" table. A db schema containing a table
     by this name will enable role-based access controls using
     this table for RBAC role configuration.

     The "RBAC_Role" 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 the following 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 from the remote connection table
     and client id, to determine whether operations modifying database
     contents should be permitted.
   - Support for specifying RBAC role string as a command-line option
     to ovsdb-tool (Ben Pfaff).

Signed-off-by: Lance Richardson <lrichard@redhat.com>
Co-authored-by: Ben Pfaff <blp@ovn.org>
Signed-off-by: Ben Pfaff <blp@ovn.org>
26 files changed:
NEWS
lib/jsonrpc.c
lib/jsonrpc.h
lib/ovsdb-error.c
lib/ovsdb-error.h
lib/ovsdb-idl.c
ovsdb/automake.mk
ovsdb/execution.c
ovsdb/jsonrpc-server.c
ovsdb/jsonrpc-server.h
ovsdb/ovsdb-server.1.in
ovsdb/ovsdb-server.c
ovsdb/ovsdb-tool.1.in
ovsdb/ovsdb-tool.c
ovsdb/ovsdb-util.c
ovsdb/ovsdb-util.h
ovsdb/ovsdb.c
ovsdb/ovsdb.h
ovsdb/rbac.c [new file with mode: 0644]
ovsdb/rbac.h [new file with mode: 0644]
ovsdb/trigger.c
ovsdb/trigger.h
tests/automake.mk
tests/ovsdb-rbac.at [new file with mode: 0644]
tests/ovsdb.at
tests/test-ovsdb.c

diff --git a/NEWS b/NEWS
index 82004c845ce45c6d2d78adbdeadb4d5adfd219bd..2517b906328a8831ae4412f5ab0229ef5286a58c 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -38,6 +38,8 @@ Post-v2.7.0
        abbreviated to 4 hex digits.
      * "ovn-sbctl lflow-list" can now print OpenFlow flows that correspond
        to logical flows.
+   - OVSDB:
+     * New support for role-based access control (see ovsdb-server(1)).
    - Add the command 'ovs-appctl stp/show' (see ovs-vswitchd(8)).
    - OpenFlow:
      * All features required by OpenFlow 1.4 are now implemented, so
index a0ade9c14e25a949199a9e054a78cdc799e3b575..2fae057de1818b816b57ca11ce4c736c898bac5c 100644 (file)
@@ -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)
index 982017a7b5460f1b1eeb99e181324ac66ecd46ad..9b4fb0e51374ff90a739cdd168c849ca051591cf 100644 (file)
@@ -130,5 +130,6 @@ void jsonrpc_session_set_probe_interval(struct jsonrpc_session *,
                                         int probe_interval);
 void jsonrpc_session_set_dscp(struct jsonrpc_session *,
                               uint8_t dscp);
+const char *jsonrpc_session_get_id(const struct jsonrpc_session *);
 
 #endif /* jsonrpc.h */
index dfa424945a0a5348608dcb547dcd28d2d63ecb1c..d8161e6d7b9a88e241127efdbfb1e90dc6c85d5d 100644 (file)
@@ -167,6 +167,19 @@ ovsdb_internal_error(struct ovsdb_error *inner_error,
     return error;
 }
 
+struct ovsdb_error *
+ovsdb_perm_error(const char *details, ...)
+{
+    struct ovsdb_error *error;
+    va_list args;
+
+    va_start(args, details);
+    error = ovsdb_error_valist("permission error", details, args);
+    va_end(args);
+
+    return error;
+}
+
 void
 ovsdb_error_destroy(struct ovsdb_error *error)
 {
index 2bc259a548c42dce49bf4a90a0b8d2789b9b31f8..da91b74999d15d43356726ce9bda7037312f87e4 100644 (file)
@@ -41,6 +41,10 @@ struct ovsdb_error *ovsdb_internal_error(struct ovsdb_error *error,
     OVS_PRINTF_FORMAT(4, 5)
     OVS_WARN_UNUSED_RESULT;
 
+struct ovsdb_error *ovsdb_perm_error(const char *details, ...)
+    OVS_PRINTF_FORMAT(1, 2)
+    OVS_WARN_UNUSED_RESULT;
+
 /* Returns a pointer to an ovsdb_error that represents an internal error for
  * the current file name and line number with MSG as the associated message.
  * The caller is responsible for freeing the internal error. */
index 515541241be0769589d3dc33f459cf5cbc5d0907..893143c551f688dfc1301b394ac6978a3e0debb6 100644 (file)
@@ -160,6 +160,7 @@ static const char *row_update_names[] = {"row_update", "row_update2"};
 
 static struct vlog_rate_limit syntax_rl = VLOG_RATE_LIMIT_INIT(1, 5);
 static struct vlog_rate_limit semantic_rl = VLOG_RATE_LIMIT_INIT(1, 5);
+static struct vlog_rate_limit other_rl = VLOG_RATE_LIMIT_INIT(1, 5);
 
 static void ovsdb_idl_clear(struct ovsdb_idl *);
 static void ovsdb_idl_send_schema_request(struct ovsdb_idl *);
@@ -3768,9 +3769,14 @@ ovsdb_idl_txn_process_reply(struct ovsdb_idl *idl,
                             soft_errors++;
                         } else if (!strcmp(error->u.string, "not owner")) {
                             lock_errors++;
+                        } else if (!strcmp(error->u.string, "not allowed")) {
+                            hard_errors++;
+                            ovsdb_idl_txn_set_error_json(txn, op);
                         } else if (strcmp(error->u.string, "aborted")) {
                             hard_errors++;
                             ovsdb_idl_txn_set_error_json(txn, op);
+                            VLOG_WARN_RL(&other_rl,
+                                         "transaction error: %s", txn->error);
                         }
                     } else {
                         hard_errors++;
index db4b9604df76dbf7749bf2d84093e60c3930075b..50e5ab367eff2fd7787b1e267704bc2f10f2f68a 100644 (file)
@@ -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 \
index e2d320e2367e287591c596a0da0c764a1ccb3462..1ebe7a772e47da99670d01971b62954e0c65ecd7 100644 (file)
@@ -27,6 +27,7 @@
 #include "ovsdb-parser.h"
 #include "ovsdb.h"
 #include "query.h"
+#include "rbac.h"
 #include "row.h"
 #include "server.h"
 #include "table.h"
@@ -39,6 +40,8 @@ struct ovsdb_execution {
     struct ovsdb_txn *txn;
     struct ovsdb_symbol_table *symtab;
     bool durable;
+    const char *role;
+    const char *id;
 
     /* Triggers. */
     long long int elapsed_msec;
@@ -97,6 +100,7 @@ lookup_executor(const char *name, bool *read_only)
 struct json *
 ovsdb_execute(struct ovsdb *db, const struct ovsdb_session *session,
               const struct json *params, bool read_only,
+              const char *role, const char *id,
               long long int elapsed_msec, long long int *timeout_msec)
 {
     struct ovsdb_execution x;
@@ -126,6 +130,8 @@ ovsdb_execute(struct ovsdb *db, const struct ovsdb_session *session,
     x.txn = ovsdb_txn_create(db);
     x.symtab = ovsdb_symbol_table_create();
     x.durable = false;
+    x.role = role;
+    x.id = id;
     x.elapsed_msec = elapsed_msec;
     x.timeout_msec = LLONG_MAX;
     results = NULL;
@@ -348,6 +354,13 @@ ovsdb_execute_insert(struct ovsdb_execution *x, struct ovsdb_parser *parser,
             }
         }
     }
+
+    if (!error && !ovsdb_rbac_insert(x->db, table, row, x->role, x->id)) {
+        error = ovsdb_perm_error("RBAC rules for client \"%s\" role \"%s\" "
+                                 "prohibit row insertion into table \"%s\".",
+                                 x->id, x->role, table->schema->name);
+    }
+
     if (!error) {
         *ovsdb_row_get_uuid_rw(row) = row_uuid;
         ovsdb_txn_row_insert(x->txn, row);
@@ -410,6 +423,8 @@ struct update_row_cbdata {
     struct ovsdb_txn *txn;
     const struct ovsdb_row *row;
     const struct ovsdb_column_set *columns;
+    const char *role;
+    const char *id;
 };
 
 static bool
@@ -470,7 +485,15 @@ ovsdb_execute_update(struct ovsdb_execution *x, struct ovsdb_parser *parser,
         ur.txn = x->txn;
         ur.row = row;
         ur.columns = &columns;
-        ovsdb_query(table, &condition, update_row_cb, &ur);
+        if (ovsdb_rbac_update(x->db, table, &columns, &condition, x->role,
+                              x->id)) {
+            ovsdb_query(table, &condition, update_row_cb, &ur);
+        } else {
+            error = ovsdb_perm_error("RBAC rules for client \"%s\" role "
+                                     "\"%s\" prohibit modification of "
+                                     "table \"%s\".",
+                                     x->id, x->role, table->schema->name);
+        }
         json_object_put(result, "count", json_integer_create(ur.n_matches));
     }
 
@@ -529,7 +552,15 @@ ovsdb_execute_mutate(struct ovsdb_execution *x, struct ovsdb_parser *parser,
         mr.txn = x->txn;
         mr.mutations = &mutations;
         mr.error = &error;
-        ovsdb_query(table, &condition, mutate_row_cb, &mr);
+        if (ovsdb_rbac_mutate(x->db, table, &mutations, &condition, x->role,
+                              x->id)) {
+            ovsdb_query(table, &condition, mutate_row_cb, &mr);
+        } else {
+            error = ovsdb_perm_error("RBAC rules for client \"%s\" role "
+                                     "\"%s\" prohibit mutate operation on "
+                                     "table \"%s\".",
+                                     x->id, x->role, table->schema->name);
+        }
         json_object_put(result, "count", json_integer_create(mr.n_matches));
     }
 
@@ -579,8 +610,15 @@ ovsdb_execute_delete(struct ovsdb_execution *x, struct ovsdb_parser *parser,
         dr.n_matches = 0;
         dr.table = table;
         dr.txn = x->txn;
-        ovsdb_query(table, &condition, delete_row_cb, &dr);
 
+        if (ovsdb_rbac_delete(x->db, table, &condition, x->role, x->id)) {
+            ovsdb_query(table, &condition, delete_row_cb, &dr);
+        } else {
+            error = ovsdb_perm_error("RBAC rules for client \"%s\" role "
+                                     "\"%s\" prohibit row deletion from "
+                                     "table \"%s\".",
+                                     x->id, x->role, table->schema->name);
+        }
         json_object_put(result, "count", json_integer_create(dr.n_matches));
     }
 
index 1ba6bb39027fa629ea5399d8c11bba0ab0011820..1770c2616b57a679005ae3ec409cbb25cde64f3c 100644 (file)
@@ -128,6 +128,7 @@ struct ovsdb_jsonrpc_remote {
     struct ovs_list sessions;   /* List of "struct ovsdb_jsonrpc_session"s. */
     uint8_t dscp;
     bool read_only;
+    char *role;
 };
 
 static struct ovsdb_jsonrpc_remote *ovsdb_jsonrpc_server_add_remote(
@@ -270,6 +271,7 @@ ovsdb_jsonrpc_server_add_remote(struct ovsdb_jsonrpc_server *svr,
     ovs_list_init(&remote->sessions);
     remote->dscp = options->dscp;
     remote->read_only = options->read_only;
+    remote->role = nullable_xstrdup(options->role);
     shash_add(&svr->remotes, name, remote);
 
     if (!listener) {
@@ -287,6 +289,7 @@ ovsdb_jsonrpc_server_del_remote(struct shash_node *node)
     ovsdb_jsonrpc_session_close_all(remote);
     pstream_close(remote->listener);
     shash_delete(&remote->server->remotes, node);
+    free(remote->role);
     free(remote);
 }
 
@@ -1038,7 +1041,8 @@ ovsdb_jsonrpc_trigger_create(struct ovsdb_jsonrpc_session *s, struct ovsdb *db,
     /* Insert into trigger table. */
     t = xmalloc(sizeof *t);
     ovsdb_trigger_init(&s->up, db, &t->trigger, params, time_msec(),
-                       s->read_only);
+                       s->read_only, s->remote->role,
+                       jsonrpc_session_get_id(s->js));
     t->id = id;
     hmap_insert(&s->triggers, &t->hmap_node, hash);
 
index 3cacbb6a0be98c1c3c61b751137bd2b04109ba51..1add3276d3b618b1fc367de665cf3b7354676756 100644 (file)
@@ -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);
index 3c798dd17954611c372aea0452ef25469b17dc4c..c655425e13e743b988f135880a8bf485b4a9dbe1 100644 (file)
@@ -269,6 +269,9 @@ narrow down the particular syntax that could not be parsed.
 The request triggered a bug in \fBovsdb\-server\fR.
 .IP "\fBovsdb error\fR"
 A map or set contains a duplicate key.
+.IP "\fBpermission error\fR"
+The request was denied by the role-based access control extension,
+introduced in version 2.8.
 .RE
 .
 .IP "3.2. Schema Format"
@@ -281,6 +284,36 @@ This raises the issue of the behavior of the weak reference when the
 rows that it references are deleted.  Since version 2.6,
 \fBovsdb\-server\fR forces columns that contain weak references to be
 mutable.
+.IP
+Since version 2.8, the table name \fBRBAC_Role\fR is used internally
+by the role-based access control extension to \fBovsdb\-server\fR and
+should not be used for purposes other than defining mappings of role
+names to table access permissions. This table has one row per role
+name and the following columns:
+.RS
+.IP "\fBname\fR"
+The role name.
+.IP "\fBpermissions\fR"
+A map of table name to a reference to a row in a separate permission
+table.
+.RE
+.IP
+The separate RBAC permission table has one row per access control
+configuration and the following columns:
+.RS
+.IP "\fBname\fR"
+The name of the table to which the row applies.
+.IP "\fBauthorization\fR"
+The set of column names and column:key pairs to be compared with
+the client ID in order to determine the authorization status of
+the requested operation.
+.IP "\fBinsert_delete\fR"
+A boolean value, true if authorized insertions and authorized are allowed,
+false if no insertions or deletions are allowed.
+.IP "\fBupdate\fR"
+The set of columns and column:key pairs for which authorized update and
+mutate operations should be permitted.
+.RE
 .
 .IP "4. Wire Protocol"
 The original OVSDB specifications included the following reason,
@@ -299,6 +332,18 @@ any corresponding advantage.
 The JSON-RPC specification for HTTP transport is incomplete.
 .RE
 .
+.IP "4.1.3. Transact"
+Since version 2.8, role-based access controls can be applied to operations
+within a transaction that would modify the contents of the database
+(these operations include row insert, row delete, column update, and
+column mutate). Role-based access controls are applied when the database
+schema contains a table with the name "\fBRBAC_Role\fR" and the connection
+on which the transaction request was received has an associated role
+name (from the "\fBrole\fR" column in the remote connection table). When
+role-based access controls are enabled, transactions that are otherwise
+well-formed may be rejected depending on the client's role, ID, and the
+contents of the \fBRBAC_Role\fR table and associated permissions table.
+.
 .IP "4.1.5. Monitor"
 For backward compatibility, \fBovsdb\-server\fR currently permits a
 single <monitor-request> to be used instead of an array; it is treated
index 50c35556cd2b33d7c1702c5504acd19a065a8678..030d86ba467f13504906d933af8c95b41ad0cdf7 100644 (file)
@@ -683,7 +683,7 @@ add_manager_options(struct shash *remotes, const struct ovsdb_row *row)
     struct ovsdb_jsonrpc_options *options;
     long long int max_backoff, probe_interval;
     bool read_only;
-    const char *target, *dscp_string;
+    const char *target, *dscp_string, *role;
 
     if (!ovsdb_util_read_string_column(row, "target", &target) || !target) {
         VLOG_INFO_RL(&rl, "Table `%s' has missing or invalid `target' column",
@@ -703,6 +703,12 @@ add_manager_options(struct shash *remotes, const struct ovsdb_row *row)
         options->read_only = read_only;
     }
 
+    free(options->role);
+    options->role = NULL;
+    if (ovsdb_util_read_string_column(row, "role", &role) && role) {
+        options->role = xstrdup(role);
+    }
+
     options->dscp = DSCP_DEFAULT;
     dscp_string = ovsdb_util_read_map_string_column(row, "other_config",
                                                     "dscp");
index d01531e567c9f6cac26a4cbd4b27a1e2f7c70597..8c799f4cc990c2d9dd121c1b2142206c92523805 100644 (file)
@@ -131,7 +131,7 @@ will print a blank line.
 .
 .SS "Other Commands"
 .
-.IP "\fBquery\fI db transaction\fR"
+.IP "[\fB\-\-rbac\-role=\fIrole\fR] \fBquery\fI db transaction\fR"
 Opens \fIdb\fR, executes \fItransaction\fR on it, and prints the
 results.  The \fItransaction\fR must be a JSON array in the format of
 the \fBparams\fR array for the JSON-RPC \fBtransact\fR method, as
@@ -142,8 +142,11 @@ safely run concurrently with other database activity, including
 \fBovsdb\-server\fR and other database writers.  The \fItransaction\fR
 may specify database modifications, but these will have no effect on
 \fIdb\fR.
+.IP
+By default, the transaction is executed using the ``superuser'' RBAC
+role.  Use \fB\-\-rbac\-role\fR to specify a different role.
 .
-.IP "\fBtransact\fI db transaction\fR"
+.IP "[\fR\-\-rbac\-role=\fIrole\fR] \fBtransact\fI db transaction\fR"
 Opens \fIdb\fR, executes \fItransaction\fR on it, prints the results,
 and commits any changes to \fIdb\fR.  The \fItransaction\fR must be a
 JSON array in the format of the \fBparams\fR array for the JSON-RPC
@@ -154,6 +157,9 @@ command will fail if the database is opened for writing by any other
 process, including \fBovsdb\-server\fR(1).  Use \fBovsdb\-client\fR(1),
 instead, to write to a database that is served by
 \fBovsdb\-server\fR(1).
+.IP
+By default, the transaction is executed using the ``superuser'' RBAC
+role.  Use \fB\-\-rbac\-role\fR to specify a different role.
 .
 .IP "\fBshow\-log\fI db\fR"
 Prints a summary of the records in \fIdb\fR's log, including the time
index 8d7e76a97bc036eb34e7b3a2ed4157dcd36f86fa..8908bae3628a293210a30efdf9bbd26fbb3348c8 100644 (file)
@@ -44,6 +44,9 @@
 /* -m, --more: Verbosity level for "show-log" command output. */
 static int show_log_verbosity;
 
+/* --role: RBAC role to use for "transact" and "query" commands. */
+static const char *rbac_role;
+
 static const struct ovs_cmdl_command *get_all_commands(void);
 
 OVS_NO_RETURN static void usage(void);
@@ -68,8 +71,12 @@ main(int argc, char *argv[])
 static void
 parse_options(int argc, char *argv[])
 {
+    enum {
+        OPT_RBAC_ROLE = UCHAR_MAX + 1
+    };
     static const struct option long_options[] = {
         {"more", no_argument, NULL, 'm'},
+        {"rbac-role", required_argument, NULL, OPT_RBAC_ROLE},
         {"verbose", optional_argument, NULL, 'v'},
         {"help", no_argument, NULL, 'h'},
         {"option", no_argument, NULL, 'o'},
@@ -91,6 +98,10 @@ parse_options(int argc, char *argv[])
             show_log_verbosity++;
             break;
 
+        case OPT_RBAC_ROLE:
+            rbac_role = optarg;
+            break;
+
         case 'h':
             usage();
 
@@ -135,10 +146,12 @@ usage(void)
            "The default SCHEMA is %s.\n",
            program_name, program_name, default_db(), default_schema());
     vlog_usage();
-    printf("\nOther options:\n"
-           "  -m, --more                  increase show-log verbosity\n"
-           "  -h, --help                  display this help message\n"
-           "  -V, --version               display version information\n");
+    printf("\
+\nOther options:\n\
+  -m, --more                  increase show-log verbosity\n\
+  --rbac-role=ROLE            RBAC role for transact and query commands\n\
+  -h, --help                  display this help message\n\
+  -V, --version               display version information\n");
     exit(EXIT_SUCCESS);
 }
 
@@ -366,7 +379,7 @@ transact(bool read_only, int argc, char *argv[])
     check_ovsdb_error(ovsdb_file_open(db_file_name, read_only, &db, NULL));
 
     request = parse_json(transaction);
-    result = ovsdb_execute(db, NULL, request, false, 0, NULL);
+    result = ovsdb_execute(db, NULL, request, false, rbac_role, NULL, 0, NULL);
     json_destroy(request);
 
     print_and_free_json(result);
index 647f5df3ec4dc73093013097e810875954df2e61..5ee5e4ddaf8d335f4fccb1a7f36d5d47eef67a8c 100644 (file)
@@ -88,6 +88,37 @@ ovsdb_util_read_map_string_column(const struct ovsdb_row *row,
     return atom_value ? atom_value->string : NULL;
 }
 
+/* Read string-uuid key-values from a map.  Returns the row associated with
+ * 'key', if found, or NULL */
+const struct ovsdb_row *
+ovsdb_util_read_map_string_uuid_column(const struct ovsdb_row *row,
+                                       const char *column_name,
+                                       const char *key)
+{
+    const struct ovsdb_column *column
+        = ovsdb_table_schema_get_column(row->table->schema, column_name);
+    if (!column ||
+        column->type.key.type != OVSDB_TYPE_STRING ||
+        column->type.value.type != OVSDB_TYPE_UUID) {
+        return NULL;
+    }
+
+    const struct ovsdb_table *ref_table = column->type.value.u.uuid.refTable;
+    if (!ref_table) {
+        return NULL;
+    }
+
+    const struct ovsdb_datum *datum = &row->fields[column->index];
+    for (size_t i = 0; i < datum->n; i++) {
+        union ovsdb_atom *atom_key = &datum->keys[i];
+        if (!strcmp(atom_key->string, key)) {
+            const union ovsdb_atom *atom_value = &datum->values[i];
+            return ovsdb_table_get_row(ref_table, &atom_value->uuid);
+        }
+    }
+    return NULL;
+}
+
 const union ovsdb_atom *
 ovsdb_util_read_column(const struct ovsdb_row *row, const char *column_name,
                        enum ovsdb_atomic_type type)
index effbec87b2ebdedf00913849922c09270f6388c2..abd81ff38cd246cdc539acf7aec39a982fd2bee4 100644 (file)
@@ -25,6 +25,10 @@ struct ovsdb_datum *ovsdb_util_get_datum(struct ovsdb_row *row,
 const char *ovsdb_util_read_map_string_column(const struct ovsdb_row *row,
                                               const char *column_name,
                                               const char *key);
+const struct ovsdb_row *ovsdb_util_read_map_string_uuid_column(
+                                                    const struct ovsdb_row *r,
+                                                    const char *column_name,
+                                                    const char *key);
 const union ovsdb_atom *ovsdb_util_read_column(const struct ovsdb_row *row,
                                                const char *column_name,
                                                enum ovsdb_atomic_type type);
index 03919004d95fa50f047f5d2020e0923af9e75e37..d8f441ad0728778875c6caa3c7cd7d1ed3a8aee7 100644 (file)
@@ -351,6 +351,9 @@ ovsdb_create(struct ovsdb_schema *schema)
         }
     }
 
+    /* Use RBAC roles table if present. */
+    db->rbac_role = ovsdb_get_table(db, "RBAC_Role");
+
     return db;
 }
 
index fc45c80a32da0076795ae573eb6ba015eda7084a..89bbfa2512fafef41bca615fbed4896f3926ad21 100644 (file)
@@ -62,6 +62,8 @@ struct ovsdb {
     /* Triggers. */
     struct ovs_list triggers;   /* Contains "struct ovsdb_trigger"s. */
     bool run_triggers;
+
+    struct ovsdb_table *rbac_role;
 };
 
 struct ovsdb *ovsdb_create(struct ovsdb_schema *);
@@ -73,6 +75,7 @@ struct ovsdb_table *ovsdb_get_table(const struct ovsdb *, const char *);
 
 struct json *ovsdb_execute(struct ovsdb *, const struct ovsdb_session *,
                            const struct json *params, bool read_only,
+                           const char *role, const char *id,
                            long long int elapsed_msec,
                            long long int *timeout_msec);
 \f
diff --git a/ovsdb/rbac.c b/ovsdb/rbac.c
new file mode 100644 (file)
index 0000000..b85ca9a
--- /dev/null
@@ -0,0 +1,449 @@
+/*
+ * Copyright (c) 2017 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <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;
+}
diff --git a/ovsdb/rbac.h b/ovsdb/rbac.h
new file mode 100644 (file)
index 0000000..42f2ef9
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2017 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_RBAC_H
+#define OVSDB_RBAC_H 1
+
+#include <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 */
index a859983f4ef4d89c0133945a12d5dbf2b997a356..1b960f5723ec41790f1abdba01308b0d632a0706 100644 (file)
@@ -32,7 +32,8 @@ void
 ovsdb_trigger_init(struct ovsdb_session *session, struct ovsdb *db,
                    struct ovsdb_trigger *trigger,
                    struct json *request, long long int now,
-                   bool read_only)
+                   bool read_only, const char *role,
+                   const char *id)
 {
     trigger->session = session;
     trigger->db = db;
@@ -42,6 +43,8 @@ ovsdb_trigger_init(struct ovsdb_session *session, struct ovsdb *db,
     trigger->created = now;
     trigger->timeout_msec = LLONG_MAX;
     trigger->read_only = read_only;
+    trigger->role = nullable_xstrdup(role);
+    trigger->id = nullable_xstrdup(id);
     ovsdb_trigger_try(trigger, now);
 }
 
@@ -51,6 +54,8 @@ ovsdb_trigger_destroy(struct ovsdb_trigger *trigger)
     ovs_list_remove(&trigger->node);
     json_destroy(trigger->request);
     json_destroy(trigger->result);
+    free(trigger->role);
+    free(trigger->id);
 }
 
 bool
@@ -114,6 +119,7 @@ ovsdb_trigger_try(struct ovsdb_trigger *t, long long int now)
 {
     t->result = ovsdb_execute(t->db, t->session,
                               t->request, t->read_only,
+                              t->role, t->id,
                               now - t->created, &t->timeout_msec);
     if (t->result) {
         ovsdb_trigger_complete(t);
index c8474a481eb6082d579b57420aa1df4507f47fc1..90246a4a42bdd650a4f0d16cb13dfc03935e5944 100644 (file)
@@ -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 *);
index c6bd1201f49fcba5f4d926f1929df651b021312f..0c28c47e508afca2be2145f333555f7f3d5892d7 100644 (file)
@@ -82,6 +82,7 @@ TESTSUITE_AT = \
        tests/ovsdb-monitor.at \
        tests/ovsdb-idl.at \
        tests/ovsdb-lock.at \
+       tests/ovsdb-rbac.at \
        tests/ovs-vsctl.at \
        tests/ovs-xapi-sync.at \
        tests/stp.at \
diff --git a/tests/ovsdb-rbac.at b/tests/ovsdb-rbac.at
new file mode 100644 (file)
index 0000000..351eab9
--- /dev/null
@@ -0,0 +1,375 @@
+AT_BANNER([OVSDB -- ovsdb-server rbac])
+
+AT_SETUP([ovsdb-server/rbac 2])
+AT_KEYWORDS([ovsdb server rbac])
+AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
+
+RBAC_PKIDIR="$(pwd)"
+RBAC_PKI="sh $abs_top_srcdir/utilities/ovs-pki.in --dir=$RBAC_PKIDIR/pki --log=$RBAC_PKIDIR/rbac-pki.log"
+$RBAC_PKI -B 1024 init
+$RBAC_PKI -B 1024 req+sign ovsdb-server switch
+$RBAC_PKI -B 1024 -u req+sign client-1 switch
+$RBAC_PKI -B 1024 -u req+sign client-2 switch
+
+AT_DATA([schema],
+  [[{"name": "mydb",
+     "tables": {
+       "Root": {
+         "columns": {
+           "connections": {
+             "type": {
+               "key": {"type": "uuid", "refTable": "Connection"},
+               "min": 0,
+               "max": "unlimited"}}},
+          "isRoot": true},
+       "Connection": {
+         "columns": {
+           "target": {
+             "type": "string"},
+           "role": {
+             "type": "string"}}},
+        "RBAC_Role": {
+            "columns": {
+                "name": {"type": "string"},
+                "permissions": {
+                    "type": {"key": {"type": "string"},
+                             "value": {"type": "uuid",
+                                       "refTable": "RBAC_Permission",
+                                       "refType": "weak"},
+                                     "min": 0, "max": "unlimited"}}},
+            "isRoot": true},
+        "RBAC_Permission": {
+            "columns": {
+                "table": {"type": "string"},
+                "authorization": {"type": {"key": "string",
+                                           "min": 0,
+                                           "max": "unlimited"}},
+                "insert_delete": {"type": "boolean"},
+                "update" : {"type": {"key": "string",
+                                     "min": 0,
+                                     "max": "unlimited"}}},
+            "isRoot": true},
+       "fixed_colors": {
+         "columns": {
+           "name": {"type": "string"}, "value": {"type": "integer"}},
+         "indexes": [["name"]],
+         "isRoot": true},
+       "user_colors": {
+         "columns": {
+           "creator": {"type": "string"},
+           "name": {"type": "string"},
+           "value": {"type": "integer"}},
+         "indexes": [["name"]],
+         "isRoot": true},
+       "other_colors": {
+         "columns": {
+           "creator": {
+             "type": {"key": {"type": "string"},
+                      "value": {"type": "string"},
+                      "min": 0, "max": "unlimited"}},
+           "name": {"type": "string"},
+           "value": {"type": "integer"}},
+         "indexes": [["name"]],
+         "isRoot": true}
+    },
+     "version": "5.1.3",
+     "cksum": "12345678 9"
+}
+]])
+
+AT_CHECK([ovsdb-tool create db schema], [0], [ignore], [ignore])
+AT_CHECK(
+  [[ovsdb-tool transact db \
+     '["mydb",
+       {"op": "insert",
+        "table": "Root",
+        "row": {
+          "connections": ["set", [["named-uuid", "x"]]]}},
+       {"op": "insert",
+        "table": "Connection",
+        "uuid-name": "x",
+        "row": {"target": "pssl:0:127.0.0.1",
+                "role": "testrole"}},
+       {"op": "insert",
+        "table": "fixed_colors",
+        "row": {"name": "red",
+                "value": '16711680'}},
+       {"op": "insert",
+        "table": "RBAC_Role",
+        "row": {"name": "testrole",
+                "permissions": ["map", [["user_colors", ["named-uuid", "y"]],
+                                        ["other_colors", ["named-uuid", "z"]]]]}},
+       {"op": "insert",
+        "table": "RBAC_Permission",
+        "uuid-name": "y",
+        "row": {"authorization": "creator",
+                "insert_delete": true,
+                "table": "user_colors",
+                "update": ["set", ["name", "value"]]}},
+       {"op": "insert",
+        "table": "RBAC_Permission",
+        "uuid-name": "z",
+        "row": {"authorization": "creator:chassis",
+                "insert_delete": true,
+                "table": "user_colors",
+                "update": ["set", ["name", "value"]]}}
+]']], [0], [ignore], [ignore])
+
+AT_CHECK([ovsdb-server --log-file --detach --no-chdir --pidfile --remote=db:mydb,Root,connections \
+        --private-key=$RBAC_PKIDIR/ovsdb-server-privkey.pem \
+        --certificate=$RBAC_PKIDIR/ovsdb-server-cert.pem \
+        --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \
+        db], [0], [ignore], [ignore])
+PARSE_LISTENING_PORT([ovsdb-server.log], [SSL_PORT])
+
+# Test 1:
+# Attempt to insert a row into the "fixed_colors" table.  This should
+# fail as there are no permissions for role "testrole" for this table.
+AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \
+        --private-key=$RBAC_PKIDIR/client-1-privkey.pem \
+        --certificate=$RBAC_PKIDIR/client-1-cert.pem \
+        --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \
+        ['["mydb",
+         {"op": "insert",
+          "table": "fixed_colors",
+          "row": {"name": "chartreuse", "value": '8388352'}}
+         ]']], [0], [stdout], [ignore])
+cat stdout >> output
+AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"details":"RBAC rules for client \"client-1\" role \"testrole\" prohibit row insertion into table \"fixed_colors\".","error":"permission error"}]]
+], [ignore])
+
+# Test 2:
+# Attempt to insert a row into the "user_colors" table with a client ID that
+# does not match the value in the column used for authorization.  This should
+# fail the authorization check for insertion.
+AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \
+        --private-key=$RBAC_PKIDIR/client-1-privkey.pem \
+        --certificate=$RBAC_PKIDIR/client-1-cert.pem \
+        --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \
+        ['["mydb",
+         {"op": "insert",
+          "table": "user_colors",
+          "row": {"creator": "client-2", "name": "chartreuse", "value": '8388352'}}
+         ]']], [0], [stdout], [ignore])
+cat stdout >> output
+AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"details":"RBAC rules for client \"client-1\" role \"testrole\" prohibit row insertion into table \"user_colors\".","error":"permission error"}]]
+], [ignore])
+
+# Test 3:
+# Attempt to insert a row into the "user_colors" table.  This should
+# succeed since role "testrole" has permissions for this table that
+# allow row insertion.
+AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \
+        --private-key=$RBAC_PKIDIR/client-1-privkey.pem \
+        --certificate=$RBAC_PKIDIR/client-1-cert.pem \
+        --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \
+        ['["mydb",
+         {"op": "insert",
+          "table": "user_colors",
+          "row": {"creator": "client-1", "name": "chartreuse", "value": '8388352'}}
+         ]']], [0], [stdout], [ignore])
+cat stdout >> output
+AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"uuid":["uuid","<0>"]}]]
+], [ignore])
+
+# Test 4:
+# Attempt to update a column in the "user_colors" table.  This should
+# succeed since role "testrole" has permissions for this table that
+# allow update of the "value" column when ID is equal to the value in
+# the "creator" column.
+AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \
+        --private-key=$RBAC_PKIDIR/client-1-privkey.pem \
+        --certificate=$RBAC_PKIDIR/client-1-cert.pem \
+        --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \
+        ['["mydb",
+         {"op": "update",
+          "table": "user_colors",
+          "where": [["name", "==", "chartreuse"]],
+          "row": {"value": '8388353'}}
+         ]']], [0], [stdout], [ignore])
+cat stdout >> output
+AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"count":1}]]
+], [ignore])
+
+# Test 5:
+# Attempt to update a column in the "user_colors" table.  Same as
+# previous test, but with a different client ID. This should fail
+# the RBAC authorization test because "client-2" does not match the
+# "creator" column for this row.
+AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \
+        --private-key=$RBAC_PKIDIR/client-2-privkey.pem \
+        --certificate=$RBAC_PKIDIR/client-2-cert.pem \
+        --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \
+        ['["mydb",
+         {"op": "update",
+          "table": "user_colors",
+          "where": [["name", "==", "chartreuse"]],
+          "row": {"value": '8388354'}}
+         ]']], [0], [stdout], [ignore])
+cat stdout >> output
+AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"details":"RBAC rules for client \"client-2\" role \"testrole\" prohibit modification of table \"user_colors\".","error":"permission error"}]]
+], [ignore])
+
+# Test 6:
+# Attempt to mutate a column in the "user_colors" table.  This should
+# succeed since role "testrole" has permissions for this table that
+# allow update of the "value" column when ID is equal to the value in
+# the "creator" column.
+AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \
+        --private-key=$RBAC_PKIDIR/client-1-privkey.pem \
+        --certificate=$RBAC_PKIDIR/client-1-cert.pem \
+        --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \
+        ['["mydb",
+         {"op": "mutate",
+          "table": "user_colors",
+          "where": [["name", "==", "chartreuse"]],
+          "mutations": [["value", "+=", '10']]}
+         ]']], [0], [stdout], [ignore])
+cat stdout >> output
+AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"count":1}]]
+], [ignore])
+
+# Test 7:
+# Attempt to mutate a column in the "user_colors" table.  Same as
+# previous test, but with a different client ID. This should fail
+# the RBAC authorization test because "client-2" does not match the
+# "creator" column for this row.
+AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \
+        --private-key=$RBAC_PKIDIR/client-2-privkey.pem \
+        --certificate=$RBAC_PKIDIR/client-2-cert.pem \
+        --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \
+        ['["mydb",
+         {"op": "mutate",
+          "table": "user_colors",
+          "where": [["name", "==", "chartreuse"]],
+          "mutations": [["value", "+=", '10']]}
+         ]']], [0], [stdout], [ignore])
+cat stdout >> output
+AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"details":"RBAC rules for client \"client-2\" role \"testrole\" prohibit mutate operation on table \"user_colors\".","error":"permission error"}]]
+], [ignore])
+
+# Test 8:
+# Attempt to delete a row from the "user_colors" table. This should fail
+# the RBAC authorization test because "client-2" does not match the
+# "creator" column for this row.
+AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \
+        --private-key=$RBAC_PKIDIR/client-2-privkey.pem \
+        --certificate=$RBAC_PKIDIR/client-2-cert.pem \
+        --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \
+        ['["mydb",
+         {"op": "delete",
+          "table": "user_colors",
+          "where": [["name", "==", "chartreuse"]]}
+         ]']], [0], [stdout], [ignore])
+cat stdout >> output
+AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"details":"RBAC rules for client \"client-2\" role \"testrole\" prohibit row deletion from table \"user_colors\".","error":"permission error"}]]
+], [ignore])
+
+# Test 9:
+# Attempt to delete a row from the "user_colors" table. This should pass
+# the RBAC authorization test because "client-1" does matches the
+# "creator" column for this row.
+AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \
+        --private-key=$RBAC_PKIDIR/client-1-privkey.pem \
+        --certificate=$RBAC_PKIDIR/client-1-cert.pem \
+        --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \
+        ['["mydb",
+         {"op": "delete",
+          "table": "user_colors",
+          "where": [["name", "==", "chartreuse"]]}
+         ]']], [0], [stdout], [ignore])
+cat stdout >> output
+AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"count":1}]]
+], [ignore])
+
+# Test 10:
+# Attempt to insert a row into the "other_colors" table.  This should
+# succeed since role "testrole" has permissions for this table that
+# allow row insertion.
+AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \
+        --private-key=$RBAC_PKIDIR/client-1-privkey.pem \
+        --certificate=$RBAC_PKIDIR/client-1-cert.pem \
+        --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \
+        ['["mydb",
+         {"op": "insert",
+          "table": "other_colors",
+          "row": {"creator": ["map",[["chassis", "client-1"]]], "name": "seafoam", "value": '7466680'}}
+         ]']], [0], [stdout], [ignore])
+cat stdout >> output
+AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"uuid":["uuid","<0>"]}]]
+], [ignore])
+
+# Test 11:
+# Attempt to update a column in the "user_colors" table.  This should
+# succeed since role "testrole" has permissions for this table that
+# allow update of the "value" column when ID is equal to the value in
+# the "creator" column.
+AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \
+        --private-key=$RBAC_PKIDIR/client-1-privkey.pem \
+        --certificate=$RBAC_PKIDIR/client-1-cert.pem \
+        --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \
+        ['["mydb",
+         {"op": "update",
+          "table": "other_colors",
+          "where": [["name", "==", "seafoam"]],
+          "row": {"value": '8388353'}}
+         ]']], [0], [stdout], [ignore])
+cat stdout >> output
+AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"count":1}]]
+], [ignore])
+
+# Test 12:
+# Attempt to update a column in the "other_colors" table.  Same as
+# previous test, but with a different client ID. This should fail
+# the RBAC authorization test because "client-2" does not match the
+# "creator" column for this row.
+AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \
+        --private-key=$RBAC_PKIDIR/client-2-privkey.pem \
+        --certificate=$RBAC_PKIDIR/client-2-cert.pem \
+        --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \
+        ['["mydb",
+         {"op": "update",
+          "table": "other_colors",
+          "where": [["name", "==", "seafoam"]],
+          "row": {"value": '8388354'}}
+         ]']], [0], [stdout], [ignore])
+cat stdout >> output
+AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"details":"RBAC rules for client \"client-2\" role \"testrole\" prohibit modification of table \"other_colors\".","error":"permission error"}]]
+], [ignore])
+
+# Test 13:
+# Attempt to delete a row from the "other_colors" table. This should fail
+# the RBAC authorization test because "client-2" does not match the
+# "creator" column for this row.
+AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \
+        --private-key=$RBAC_PKIDIR/client-2-privkey.pem \
+        --certificate=$RBAC_PKIDIR/client-2-cert.pem \
+        --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \
+        ['["mydb",
+         {"op": "delete",
+          "table": "other_colors",
+          "where": [["name", "==", "seafoam"]]}
+         ]']], [0], [stdout], [ignore])
+cat stdout >> output
+AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"details":"RBAC rules for client \"client-2\" role \"testrole\" prohibit row deletion from table \"other_colors\".","error":"permission error"}]]
+], [ignore])
+
+# Test 14:
+# Attempt to delete a row from the "other_colors" table. This should pass
+# the RBAC authorization test because "client-1" does matches the
+# "creator" column for this row.
+AT_CHECK([ovsdb-client transact ssl:127.0.0.1:$SSL_PORT \
+        --private-key=$RBAC_PKIDIR/client-1-privkey.pem \
+        --certificate=$RBAC_PKIDIR/client-1-cert.pem \
+        --ca-cert=$RBAC_PKIDIR/pki/switchca/cacert.pem \
+        ['["mydb",
+         {"op": "delete",
+          "table": "other_colors",
+          "where": [["name", "==", "seafoam"]]}
+         ]']], [0], [stdout], [ignore])
+cat stdout >> output
+AT_CHECK([${PERL} $srcdir/uuidfilt.pl stdout], [0], [[[{"count":1}]]
+], [ignore])
+
+OVSDB_SERVER_SHUTDOWN
+AT_CLEANUP
index fe617a562e9994f91151bef6619220c052fa2db4..8a389b8aad3a0219f48537cde75d87221c0e1243 100644 (file)
@@ -148,3 +148,4 @@ m4_include([tests/ovsdb-server.at])
 m4_include([tests/ovsdb-monitor.at])
 m4_include([tests/ovsdb-idl.at])
 m4_include([tests/ovsdb-lock.at])
+m4_include([tests/ovsdb-rbac.at])
index 0f2101e575677a7a739c9ac11cd670d9b7b22d0a..9c51e323c1b08ec9ae22e1d2fc291f8c19700387 100644 (file)
@@ -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");