Add support for managing remote connections, including SSL configuration, to southbound db schema, and add necessary commands to ovn-sbctl.
Signed-off-by: Lance Richardson <lrich...@redhat.com> --- NEWS | 2 +- manpages.mk | 6 ++ ovn/ovn-sb.ovsschema | 21 +++- ovn/ovn-sb.xml | 48 +++++++++- ovn/utilities/ovn-sbctl.8.in | 85 ++++++++++++++++- ovn/utilities/ovn-sbctl.c | 221 ++++++++++++++++++++++++++++++++++++++++++- tests/ovn.at | 52 ++++++++++ 7 files changed, 423 insertions(+), 12 deletions(-) diff --git a/NEWS b/NEWS index 2ec3dbb..3a33abf 100644 --- a/NEWS +++ b/NEWS @@ -8,7 +8,7 @@ Post-v2.6.0 * IPAM now supports fixed MAC addresses. * Support for source IP address based routing. * Support for managing SSL and remote connection configuration in - northbound database. + northbound and southbound databases. - Fixed regression in table stats maintenance introduced in OVS 2.3.0, wherein the number of OpenFlow table hits and misses was not accurate. diff --git a/manpages.mk b/manpages.mk index 2fb8ef4..11ec023 100644 --- a/manpages.mk +++ b/manpages.mk @@ -4,6 +4,9 @@ ovn/utilities/ovn-sbctl.8: \ ovn/utilities/ovn-sbctl.8.in \ lib/common.man \ lib/db-ctl-base.man \ + lib/ssl-bootstrap.man \ + lib/ssl-peer-ca-cert.man \ + lib/ssl.man \ lib/table.man \ lib/vlog.man \ ovsdb/remote-active.man \ @@ -11,6 +14,9 @@ ovn/utilities/ovn-sbctl.8: \ ovn/utilities/ovn-sbctl.8.in: lib/common.man: lib/db-ctl-base.man: +lib/ssl-bootstrap.man: +lib/ssl-peer-ca-cert.man: +lib/ssl.man: lib/table.man: lib/vlog.man: ovsdb/remote-active.man: 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 65191ed..e2f88b5 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> @@ -2309,7 +2312,9 @@ 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 + via command-line options or the <ref table="SSL"/> table. </p> <p> If <var>port</var> is not specified, it defaults to 6640. @@ -2345,6 +2350,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="SSL"/> table. </p> <p> If <var>port</var> is not specified, it defaults to 6640. @@ -2517,4 +2525,42 @@ tcp.flags = RST; <column name="other_config"/> </group> </table> + <table name="SSL"> + SSL configuration for ovn-sb database access. + + <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.8.in b/ovn/utilities/ovn-sbctl.8.in index 8036397..9a6978a 100644 --- a/ovn/utilities/ovn-sbctl.8.in +++ b/ovn/utilities/ovn-sbctl.8.in @@ -22,10 +22,6 @@ ovn\-sbctl \- utility for querying and configuring \fBOVN_Southbound\fR database \fR[\fIargs\fR] [\fB\-\-\fR [\fIoptions\fR] \fIcommand \fR[\fIargs\fR]]... . .SH DESCRIPTION -The command should only be used for advanced debugging and troubleshooting -of the \fBOVN_Southbound\fR database; and should never be used in normal -operation. -.PP The \fBovn\-sbctl\fR program configures the \fBOVN_Southbound\fR database by providing a high\-level interface to its configuration database. See \fBovn\-sb\fR(5) for comprehensive documentation of the database schema. @@ -105,6 +101,11 @@ These options control the format of output from the \fBlist\fR and \fBfind\fR commands. .so lib/table.man . +.SS "Public Key Infrastructure Options" +.so lib/ssl.man +.so lib/ssl-bootstrap.man +.so lib/ssl-peer-ca-cert.man +. .SH COMMANDS The commands implemented by \fBovn\-sbctl\fR are described in the sections below. @@ -166,6 +167,82 @@ that logical datapath. .IP "\fBdump\-flows\fR [\fIlogical\-datapath\fR]" Alias for \fBlflow\-list\fB. . +.SS "Remote Connectivity Commands" +. +These commands manipulate the \fBconnections\fR column in the \fBSB_Global\fR +table and rows in the \fBConnection\fR table. When \fBovsdb\-server\fR +is configured to use the \fBconnections\fR column for OVSDB connections, +this allows the administrator to use \fBovn\-sbctl\fR to configure database +connections. +. +.IP "\fBget\-connection\fR" +Prints the configured connection(s). +. +.IP "\fBdel\-connection\fR" +Deletes the configured connection(s). +. +.IP "\fBset\-connection\fR [\fIaccess\-specifier\fR] \fItarget\fR\&..." +Sets the configured manager target or targets. Each \fItarget\fR may +be preceded by an optional access-specifier (\fBread\-only\fR or +\fBread\-write\fR) and may use any of the following forms: +. +.RS +.so ovsdb/remote-active.man +.so ovsdb/remote-passive.man +.RE + +If provided, the effect of the access specifier persists for subsequent +targets until changed by another access specifier. +. +.SS "SSL Configuration" +When \fBovsdb\-server\fR is configured to connect using SSL, the +following parameters are required: +.TP +\fIprivate-key\fR +Specifies a PEM file containing the private key used for SSL connections. +.TP +\fIcertificate\fR +Specifies a PEM file containing a certificate, signed by the +certificate authority (CA) used by the connection peers, that +certifies the private key, identifying a trustworthy peer. +.TP +\fIca-cert\fR +Specifies a PEM file containing the CA certificate used to verify that +the connection peers are trustworthy. +.PP +These SSL settings apply to all SSL connections made by the southbound +database server. +. +.IP "\fBget\-ssl\fR" +Prints the SSL configuration. +. +.IP "\fBdel\-ssl\fR" +Deletes the current SSL configuration. +. +.IP "[\fB\-\-bootstrap\fR] \fBset\-ssl\fR \fIprivate-key\fR \fIcertificate\fR \fIca-cert\fR" +Sets the SSL configuration. The \fB\-\-bootstrap\fR option is described +below. +. +.ST "CA Certificate Bootstrap" +.PP +Ordinarily, all of the files named in the SSL configuration must exist +before SSL connectivity can be used. However, if the \fIca-cert\fR file +does not exist and the \fB\-\-bootstrap\fR +option is given, then \fBovsdb\-server\fR will attempt to obtain the +CA certificate from the target 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. +.PP +\fBThis option exposes the SSL connection to a man-in-the-middle +attack obtaining the initial CA certificate\fR, but it may be useful +for bootstrapping. +.PP +This option is only useful if the SSL peer sends its CA certificate +as part of the SSL certificate chain. The SSL protocol does not +require the controller to send the CA certificate. +. .so lib/db-ctl-base.man .SH "EXIT STATUS" .IP "0" diff --git a/ovn/utilities/ovn-sbctl.c b/ovn/utilities/ovn-sbctl.c index 0763b79..92ae3e5 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); @@ -287,8 +288,6 @@ usage(void) printf("\ %s: OVN southbound DB management utility\n\ \n\ -For debugging and testing only, not for use in production.\n\ -\n\ usage: %s [OPTIONS] COMMAND [ARG...]\n\ \n\ General commands:\n\ @@ -309,6 +308,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\ @@ -739,6 +748,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, @@ -782,6 +984,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}}} }; @@ -1044,7 +1249,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 f9dcd0a..31b5ede 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -5639,6 +5639,58 @@ AT_CHECK([ovn-nbctl --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 _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev