[ovs-dev] [PATCH] ovn-sb: support for managing SSL and connection config in sb db
Lance Richardson
lrichard at redhat.com
Tue Nov 8 00:14:29 UTC 2016
Add SSL configuration to the southbound database schema and add
commands to ovn-sbctl to allow management of these entries.
Added commands:
Display all configured connections, with read-only/read-write
status:
ovn-sbctl get-connection
Delete all configured connections:
ovn-sbctl del-connection
Add a list of connection targets:
ovn-sbctl set-connection [access-specifier] TARGET...
[access-specifier] is optional, and can be "read-only" or
"read-write". Access is read-write by default, and when specified
persists for subsequent targets until changed by another access
specifier. For example:
ovn-sbctl set-connection read-only ptcp:0:127.0.0.1 \
pssl:0:127.0.0.1 \
read-write ptcp:0:192.168.100.4
Print SSL configuration:
ovn-sbctl get-ssl
Delete SSL configuration:
ovn-sbctl del-ssl
Set SSL configuration:
ovn-sbctl [--bootstrap] set-ssl PRIV-KEY CERT CA-CERT
Signed-off-by: Lance Richardson <lrichard at redhat.com>
---
ovn/ovn-sb.ovsschema | 21 ++++-
ovn/ovn-sb.xml | 49 ++++++++++-
ovn/utilities/ovn-sbctl.c | 219 +++++++++++++++++++++++++++++++++++++++++++++-
tests/ovn.at | 52 +++++++++++
4 files changed, 336 insertions(+), 5 deletions(-)
diff --git a/ovn/ovn-sb.ovsschema b/ovn/ovn-sb.ovsschema
index 89342fe..0212a5e 100644
--- a/ovn/ovn-sb.ovsschema
+++ b/ovn/ovn-sb.ovsschema
@@ -1,7 +1,7 @@
{
"name": "OVN_Southbound",
"version": "1.9.0",
- "cksum": "239060528 9012",
+ "cksum": "2240045372 9719",
"tables": {
"SB_Global": {
"columns": {
@@ -13,7 +13,11 @@
"type": {"key": {"type": "uuid",
"refTable": "Connection"},
"min": 0,
- "max": "unlimited"}}},
+ "max": "unlimited"}},
+ "ssl": {
+ "type": {"key": {"type": "uuid",
+ "refTable": "SSL"},
+ "min": 0, "max": 1}}},
"maxRows": 1,
"isRoot": true},
"Chassis": {
@@ -183,4 +187,15 @@
"min": 0,
"max": "unlimited"},
"ephemeral": true}},
- "indexes": [["target"]]}}}
+ "indexes": [["target"]]},
+ "SSL": {
+ "columns": {
+ "private_key": {"type": "string"},
+ "certificate": {"type": "string"},
+ "ca_cert": {"type": "string"},
+ "bootstrap_ca_cert": {"type": "boolean"},
+ "external_ids": {"type": {"key": "string",
+ "value": "string",
+ "min": 0,
+ "max": "unlimited"}}},
+ "maxRows": 1}}}
diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
index 45c473c..cf82b22 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -169,6 +169,9 @@
connections should be configured. See the <ref table="Connection"/>
table for more information.
</column>
+ <column name="ssl">
+ Global SSL configuration.
+ </column>
</group>
</table>
@@ -2294,7 +2297,10 @@ tcp.flags = RST;
<p>
The specified SSL <var>port</var> on the host at the given
<var>ip</var>, which must be expressed as an IP address
- (not a DNS name).
+ (not a DNS name). A valid SSL configuration must be provided
+ when this form is used, this configuration can be specified
+ either via command-line options or the <ref table="SB_Global"
+ column="ssl"/> column.
</p>
<p>
If <var>port</var> is not specified, it defaults to 6640.
@@ -2330,6 +2336,9 @@ tcp.flags = RST;
address, wrap in square brackets,
e.g. <code>pssl:6640:[::1]</code>. If <var>ip</var> is not
specified then it listens only on IPv4 (but not IPv6) addresses.
+ A valid SSL configuration must be provided when this form is used,
+ this can be specified either via command-line options or the
+ <ref table="SB_Global" column="ssl"/> column.
</p>
<p>
If <var>port</var> is not specified, it defaults to 6640.
@@ -2502,4 +2511,42 @@ tcp.flags = RST;
<column name="other_config"/>
</group>
</table>
+ <table name="SSL">
+ SSL configuration for the ovn-sb database.
+
+ <column name="private_key">
+ Name of a PEM file containing the private key used as the switch's
+ identity for SSL connections to the controller.
+ </column>
+
+ <column name="certificate">
+ Name of a PEM file containing a certificate, signed by the
+ certificate authority (CA) used by the controller and manager,
+ that certifies the switch's private key, identifying a trustworthy
+ switch.
+ </column>
+
+ <column name="ca_cert">
+ Name of a PEM file containing the CA certificate used to verify
+ that the switch is connected to a trustworthy controller.
+ </column>
+
+ <column name="bootstrap_ca_cert">
+ If set to <code>true</code>, then Open vSwitch will attempt to
+ obtain the CA certificate from the controller on its first SSL
+ connection and save it to the named PEM file. If it is successful,
+ it will immediately drop the connection and reconnect, and from then
+ on all SSL connections must be authenticated by a certificate signed
+ by the CA certificate thus obtained. <em>This option exposes the
+ SSL connection to a man-in-the-middle attack obtaining the initial
+ CA certificate.</em> It may still be useful for bootstrapping.
+ </column>
+
+ <group title="Common Columns">
+ The overall purpose of these columns is described under <code>Common
+ Columns</code> at the beginning of this document.
+
+ <column name="external_ids"/>
+ </group>
+ </table>
</database>
diff --git a/ovn/utilities/ovn-sbctl.c b/ovn/utilities/ovn-sbctl.c
index b72d554..9a9fc90 100644
--- a/ovn/utilities/ovn-sbctl.c
+++ b/ovn/utilities/ovn-sbctl.c
@@ -48,6 +48,7 @@
#include "table.h"
#include "timeval.h"
#include "util.h"
+#include "svec.h"
VLOG_DEFINE_THIS_MODULE(sbctl);
@@ -308,6 +309,16 @@ Logical flow commands:\n\
lflow-list [DATAPATH] List logical flows for all or a single datapath\n\
dump-flows [DATAPATH] Alias for lflow-list\n\
\n\
+Connection commands:\n\
+ get-connection print the connections\n\
+ del-connection delete the connections\n\
+ set-connection TARGET... set the list of connections to TARGET...\n\
+\n\
+SSL commands:\n\
+ get-ssl print the SSL configuration\n\
+ del-ssl delete the SSL configuration\n\
+ set-ssl PRIV-KEY CERT CA-CERT set the SSL configuration\n\
+\n\
%s\
\n\
Options:\n\
@@ -738,6 +749,199 @@ cmd_lflow_list(struct ctl_context *ctx)
free(lflows);
}
+static void
+verify_connections(struct ctl_context *ctx)
+{
+ const struct sbrec_sb_global *sb_global = sbrec_sb_global_first(ctx->idl);
+ const struct sbrec_connection *conn;
+
+ sbrec_sb_global_verify_connections(sb_global);
+
+ SBREC_CONNECTION_FOR_EACH(conn, ctx->idl) {
+ sbrec_connection_verify_target(conn);
+ }
+}
+
+static void
+pre_connection(struct ctl_context *ctx)
+{
+ ovsdb_idl_add_column(ctx->idl, &sbrec_sb_global_col_connections);
+ ovsdb_idl_add_column(ctx->idl, &sbrec_connection_col_target);
+ ovsdb_idl_add_column(ctx->idl, &sbrec_connection_col_read_only);
+}
+
+static void
+cmd_get_connection(struct ctl_context *ctx)
+{
+ const struct sbrec_connection *conn;
+ struct svec targets;
+ size_t i;
+
+ verify_connections(ctx);
+
+ /* Print the targets in sorted order for reproducibility. */
+ svec_init(&targets);
+
+ SBREC_CONNECTION_FOR_EACH(conn, ctx->idl) {
+ char *s;
+
+ s = xasprintf("%s %s", conn->read_only ? "read-only" : "read-write",
+ conn->target);
+ svec_add(&targets, s);
+ free(s);
+ }
+
+ svec_sort_unique(&targets);
+ for (i = 0; i < targets.n; i++) {
+ ds_put_format(&ctx->output, "%s\n", targets.names[i]);
+ }
+ svec_destroy(&targets);
+}
+
+static void
+delete_connections(struct ctl_context *ctx)
+{
+ const struct sbrec_sb_global *sb_global = sbrec_sb_global_first(ctx->idl);
+ const struct sbrec_connection *conn, *next;
+
+ /* Delete Manager rows pointed to by 'connection_options' column. */
+ SBREC_CONNECTION_FOR_EACH_SAFE(conn, next, ctx->idl) {
+ sbrec_connection_delete(conn);
+ }
+
+ /* Delete 'Manager' row refs in 'manager_options' column. */
+ sbrec_sb_global_set_connections(sb_global, NULL, 0);
+}
+
+static void
+cmd_del_connection(struct ctl_context *ctx)
+{
+ verify_connections(ctx);
+ delete_connections(ctx);
+}
+
+static void
+insert_connections(struct ctl_context *ctx, char *targets[], size_t n)
+{
+ const struct sbrec_sb_global *sb_global = sbrec_sb_global_first(ctx->idl);
+ struct sbrec_connection **connections;
+ size_t i, conns=0;
+ bool read_only = false;
+
+ /* Insert each connection in a new row in Connection table. */
+ connections = xmalloc(n * sizeof *connections);
+ for (i = 0; i < n; i++) {
+ if (!strcmp(targets[i], "read-only")) {
+ read_only = true;
+ continue;
+ } else if (!strcmp(targets[i], "read-write")) {
+ read_only = false;
+ continue;
+ } else if (stream_verify_name(targets[i]) &&
+ pstream_verify_name(targets[i])) {
+ VLOG_WARN("target type \"%s\" is possibly erroneous", targets[i]);
+ }
+
+ connections[conns] = sbrec_connection_insert(ctx->txn);
+ sbrec_connection_set_target(connections[conns], targets[i]);
+ sbrec_connection_set_read_only(connections[conns], read_only);
+ conns++;
+ }
+
+ /* Store uuids of new connection rows in 'connection' column. */
+ sbrec_sb_global_set_connections(sb_global, connections, conns);
+ free(connections);
+}
+
+static void
+cmd_set_connection(struct ctl_context *ctx)
+{
+ const size_t n = ctx->argc - 1;
+
+ verify_connections(ctx);
+ delete_connections(ctx);
+ insert_connections(ctx, &ctx->argv[1], n);
+}
+
+static void
+pre_cmd_get_ssl(struct ctl_context *ctx)
+{
+ ovsdb_idl_add_column(ctx->idl, &sbrec_sb_global_col_ssl);
+
+ ovsdb_idl_add_column(ctx->idl, &sbrec_ssl_col_private_key);
+ ovsdb_idl_add_column(ctx->idl, &sbrec_ssl_col_certificate);
+ ovsdb_idl_add_column(ctx->idl, &sbrec_ssl_col_ca_cert);
+ ovsdb_idl_add_column(ctx->idl, &sbrec_ssl_col_bootstrap_ca_cert);
+}
+
+static void
+cmd_get_ssl(struct ctl_context *ctx)
+{
+ const struct sbrec_sb_global *sb_global = sbrec_sb_global_first(ctx->idl);
+ const struct sbrec_ssl *ssl = sbrec_ssl_first(ctx->idl);
+
+ sbrec_sb_global_verify_ssl(sb_global);
+ if (ssl) {
+ sbrec_ssl_verify_private_key(ssl);
+ sbrec_ssl_verify_certificate(ssl);
+ sbrec_ssl_verify_ca_cert(ssl);
+ sbrec_ssl_verify_bootstrap_ca_cert(ssl);
+
+ ds_put_format(&ctx->output, "Private key: %s\n", ssl->private_key);
+ ds_put_format(&ctx->output, "Certificate: %s\n", ssl->certificate);
+ ds_put_format(&ctx->output, "CA Certificate: %s\n", ssl->ca_cert);
+ ds_put_format(&ctx->output, "Bootstrap: %s\n",
+ ssl->bootstrap_ca_cert ? "true" : "false");
+ }
+}
+
+static void
+pre_cmd_del_ssl(struct ctl_context *ctx)
+{
+ ovsdb_idl_add_column(ctx->idl, &sbrec_sb_global_col_ssl);
+}
+
+static void
+cmd_del_ssl(struct ctl_context *ctx)
+{
+ const struct sbrec_sb_global *sb_global = sbrec_sb_global_first(ctx->idl);
+ const struct sbrec_ssl *ssl = sbrec_ssl_first(ctx->idl);
+
+ if (ssl) {
+ sbrec_sb_global_verify_ssl(sb_global);
+ sbrec_ssl_delete(ssl);
+ sbrec_sb_global_set_ssl(sb_global, NULL);
+ }
+}
+
+static void
+pre_cmd_set_ssl(struct ctl_context *ctx)
+{
+ ovsdb_idl_add_column(ctx->idl, &sbrec_sb_global_col_ssl);
+}
+
+static void
+cmd_set_ssl(struct ctl_context *ctx)
+{
+ bool bootstrap = shash_find(&ctx->options, "--bootstrap");
+ const struct sbrec_sb_global *sb_global = sbrec_sb_global_first(ctx->idl);
+ const struct sbrec_ssl *ssl = sbrec_ssl_first(ctx->idl);
+
+ sbrec_sb_global_verify_ssl(sb_global);
+ if (ssl) {
+ sbrec_ssl_delete(ssl);
+ }
+ ssl = sbrec_ssl_insert(ctx->txn);
+
+ sbrec_ssl_set_private_key(ssl, ctx->argv[1]);
+ sbrec_ssl_set_certificate(ssl, ctx->argv[2]);
+ sbrec_ssl_set_ca_cert(ssl, ctx->argv[3]);
+
+ sbrec_ssl_set_bootstrap_ca_cert(ssl, bootstrap);
+
+ sbrec_sb_global_set_ssl(sb_global, ssl);
+}
+
static const struct ctl_table_class tables[] = {
{&sbrec_table_sb_global,
@@ -781,6 +985,9 @@ static const struct ctl_table_class tables[] = {
{{&sbrec_table_connection, NULL, NULL},
{NULL, NULL, NULL}}},
+ {&sbrec_table_ssl,
+ {{&sbrec_table_sb_global, NULL, &sbrec_sb_global_col_ssl}}},
+
{NULL, {{NULL, NULL, NULL}, {NULL, NULL, NULL}}}
};
@@ -1043,7 +1250,17 @@ static const struct ctl_command_syntax sbctl_commands[] = {
{"dump-flows", 0, 1, "[DATAPATH]", pre_get_info, cmd_lflow_list, NULL,
"", RO}, /* Friendly alias for lflow-list */
- /* SSL commands (To Be Added). */
+ /* Connection commands. */
+ {"get-connection", 0, 0, "", pre_connection, cmd_get_connection, NULL, "", RO},
+ {"del-connection", 0, 0, "", pre_connection, cmd_del_connection, NULL, "", RW},
+ {"set-connection", 1, INT_MAX, "TARGET...", pre_connection, cmd_set_connection,
+ NULL, "", RW},
+
+ /* SSL commands. */
+ {"get-ssl", 0, 0, "", pre_cmd_get_ssl, cmd_get_ssl, NULL, "", RO},
+ {"del-ssl", 0, 0, "", pre_cmd_del_ssl, cmd_del_ssl, NULL, "", RW},
+ {"set-ssl", 3, 3, "PRIVATE-KEY CERTIFICATE CA-CERT", pre_cmd_set_ssl,
+ cmd_set_ssl, NULL, "--bootstrap", RW},
{NULL, 0, 0, NULL, NULL, NULL, NULL, NULL, RO},
};
diff --git a/tests/ovn.at b/tests/ovn.at
index 6ae4247..39d2af8 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -5555,6 +5555,58 @@ AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \
OVS_APP_EXIT_AND_WAIT([ovsdb-server])
AT_CLEANUP
+AT_SETUP([ovn -- sb connection/ssl commands])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
+PKIDIR="$(cd $abs_top_builddir/tests && pwd)"
+AT_SKIP_IF([expr "$PKIDIR" : ".*[ '\"
+\\]"])
+
+: > .$1.db.~lock~
+ovsdb-tool create ovn-sb.db "$abs_top_srcdir"/ovn/ovn-sb.ovsschema
+
+# Start sb db server using db connection/ssl entries (unpopulated initially)
+start_daemon ovsdb-server --remote=punix:ovnsb_db.sock \
+ --remote=db:OVN_Southbound,SB_Global,connections \
+ --private-key=db:OVN_Southbound,SSL,private_key \
+ --certificate=db:OVN_Southbound,SSL,certificate \
+ --ca-cert=db:OVN_Southbound,SSL,ca_cert \
+ ovn-sb.db
+
+# Populate SSL configuration entries in sb db
+AT_CHECK(
+ [ovn-sbctl set-ssl $PKIDIR/testpki-privkey.pem \
+ $PKIDIR/testpki-cert.pem \
+ $PKIDIR/testpki-cacert.pem], [0], [stdout], [ignore])
+
+# Populate a passive SSL connection in sb db
+AT_CHECK([ovn-sbctl set-connection pssl:0:127.0.0.1], [0], [stdout], [ignore])
+
+PARSE_LISTENING_PORT([ovsdb-server.log], [TCP_PORT])
+
+# Verify SSL connetivity to sb db server
+AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \
+ --private-key=$PKIDIR/testpki-privkey.pem \
+ --certificate=$PKIDIR/testpki-cert.pem \
+ --ca-cert=$PKIDIR/testpki-cacert.pem \
+ list SB_Global],
+ [0], [stdout], [ignore])
+AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \
+ --private-key=$PKIDIR/testpki-privkey.pem \
+ --certificate=$PKIDIR/testpki-cert.pem \
+ --ca-cert=$PKIDIR/testpki-cacert.pem \
+ list Connection],
+ [0], [stdout], [ignore])
+AT_CHECK([ovn-sbctl --db=ssl:127.0.0.1:$TCP_PORT \
+ --private-key=$PKIDIR/testpki-privkey.pem \
+ --certificate=$PKIDIR/testpki-cert.pem \
+ --ca-cert=$PKIDIR/testpki-cacert.pem \
+ get-connection],
+ [0], [stdout], [ignore])
+
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+AT_CLEANUP
+
AT_SETUP([ovn -- nested containers])
ovn_start
--
2.5.5
More information about the dev
mailing list