diff --git a/include/qpid/dispatch/amqp.h b/include/qpid/dispatch/amqp.h index cb0baf989..b78049372 100644 --- a/include/qpid/dispatch/amqp.h +++ b/include/qpid/dispatch/amqp.h @@ -165,6 +165,7 @@ extern const char * const QD_CONNECTION_PROPERTY_ADAPTOR_KEY; extern const char * const QD_CONNECTION_PROPERTY_TCP_ADAPTOR_VALUE; extern const char * const QD_CONNECTION_PROPERTY_ANNOTATIONS_VERSION_KEY; extern const char * const QD_CONNECTION_PROPERTY_ACCESS_ID; +extern const char * const QD_CONNECTION_PROPERTY_TLS_ORDINAL; /// @} /** @name Terminus Addresses */ diff --git a/include/qpid/dispatch/protocol_adaptor.h b/include/qpid/dispatch/protocol_adaptor.h index 3b03ac649..2bedd0123 100644 --- a/include/qpid/dispatch/protocol_adaptor.h +++ b/include/qpid/dispatch/protocol_adaptor.h @@ -928,6 +928,7 @@ qdr_connection_info_t *qdr_connection_info(bool is_encrypted, const char *user, const char *container, pn_data_t *connection_properties, + uint64_t tls_ordinal, int ssl_ssf, bool ssl, const char *version, diff --git a/include/qpid/dispatch/tls_common.h b/include/qpid/dispatch/tls_common.h index 406472fa5..a06bc9a5b 100644 --- a/include/qpid/dispatch/tls_common.h +++ b/include/qpid/dispatch/tls_common.h @@ -84,15 +84,17 @@ struct qd_ssl2_profile_t { char *uid_name_mapping_file; /** - * version: Version assigned to the current configuration - * oldest_valid_version: Previous sslProfile updates with versions values < oldest_valid_version have expired. + * Certificate Rotation + * ordinal: Identifies the current configuration's revision + * oldest_valid_ordinal: Previous configurations with ordinal values < oldest_valid_ordinal have expired. */ - long version; - long oldest_valid_version; + uint64_t ordinal; + uint64_t oldest_valid_ordinal; }; /** - * Create a new TLS qd_tls_config_t instance with the given configuration + * Create a new TLS qd_tls_config_t instance with the given configuration. This is called when a listener/connector + * record is created. * * @param ssl_profile_name the name of the sslProfile configuration to use * @param p_type protocol type for the child connections (TCP or AMQP) @@ -117,6 +119,45 @@ qd_tls_config_t *qd_tls_config(const char *ssl_profile_name, void qd_tls_config_decref(qd_tls_config_t *config); +/** + * Get the values of the ordinal/oldestValidOrdinal assocated with the TLS configuration. + * + * Note: To avoid races this function can only be called from the context of the management thread. + * + * @param config The qd_tls_config_t to query. + * @return the value for the ordinal/lastValidOrdinal sslProfile attributes used by this config + */ +uint64_t qd_tls_config_get_ordinal(const qd_tls_config_t *config); +uint64_t qd_tls_config_get_oldest_valid_ordinal(const qd_tls_config_t *config); + + +/** Register a callback to monitor updates to the TLS configuration + * + * Register a callback function that will be invoked by the management thread whenever the sslProfile record associated + * with the qd_tls_config_t is updated. Note that the update callback is invoked on the management thread while the + * qd_tls_config is locked. This prevents new TLS sessions from being created using the updated configuration until + * after the update callback returns. + * + * @param update_cb_context Opaque handle passed to update callback function. + * @param update_cb Optional callback when the sslProfile has been updated by management. + */ +typedef void (*qd_tls_config_update_cb_t)(const qd_tls_config_t *config, + void *update_cb_context); +void qd_tls_config_register_update_callback(qd_tls_config_t *config, void *update_cb_context, + qd_tls_config_update_cb_t update_cb); + + +/** + * Cancel the update callback. + * + * Deregisters the update callback provided in qd_tls_config(). No further calls to the callback will occur on return + * from this call. Can only be called from the context of the management thread. + * + * @param config The qd_tls_config_t whose callback will be cancelled + */ +void qd_tls_config_cancel_update_callback(qd_tls_config_t *config); + + /** * Release a TLS session context. * @@ -153,6 +194,13 @@ char *qd_tls_session_get_protocol_ciphers(const qd_tls_session_t *session); */ int qd_tls_session_get_ssf(const qd_tls_session_t *session); +/** + * Get the ordinal of the sslProfile used to create this session + * + * @param session to be queried + * @return the value of the sslProfile ordinal associated with this session. + */ +uint64_t qd_tls_session_get_profile_ordinal(const qd_tls_session_t *session); /** * Fill out the given *profile with the configuration from the named sslProfile record. diff --git a/python/skupper_router/management/skrouter.json b/python/skupper_router/management/skrouter.json index 29a8bd088..d68144a00 100644 --- a/python/skupper_router/management/skrouter.json +++ b/python/skupper_router/management/skrouter.json @@ -725,15 +725,15 @@ "description": "The absolute path to the file containing the unique id to display name mapping", "create": true }, - "version": { + "ordinal": { "type": "integer", - "description": "RESERVED FOR FUTURE USE", + "description": "Indicates the revision of the TLS certificates provided. Used by certificate rotation. Each time the TLS certificates associated with this profile are updated the ordinal value will be increased. The most recent version of the TLS credentials will have the highest numerical ordinal value.", "create": true, "update": true }, - "oldestValidVersion": { + "oldestValidOrdinal": { "type": "integer", - "description": "RESERVED FOR FUTURE USE", + "description": "Used by certificate rotation to remove connections that were created using TLS certificates that are no longer considered valid. Any active connections based on TLS certificates an ordinal value less than oldesValidOrdinal will be immediately terminated by the router.", "create": true, "update": true } diff --git a/src/adaptors/amqp/amqp_adaptor.c b/src/adaptors/amqp/amqp_adaptor.c index 9cfe1d517..a193ba297 100644 --- a/src/adaptors/amqp/amqp_adaptor.c +++ b/src/adaptors/amqp/amqp_adaptor.c @@ -1567,6 +1567,7 @@ static void AMQP_opened_handler(qd_router_t *router, qd_connection_t *conn, bool (char*) user, container, props, + qd_tls_session_get_profile_ordinal(conn->ssl), ssl_ssf, !!conn->ssl, rversion, diff --git a/src/adaptors/amqp/connection_manager.c b/src/adaptors/amqp/connection_manager.c index 6f56cfeda..390ac9c51 100644 --- a/src/adaptors/amqp/connection_manager.c +++ b/src/adaptors/amqp/connection_manager.c @@ -80,6 +80,8 @@ QD_EXPORT qd_listener_t *qd_dispatch_configure_listener(qd_dispatch_t *qd, qd_en qd_listener_decref(li); return 0; } + li->tls_ordinal = qd_tls_config_get_ordinal(li->tls_config); + li->tls_oldest_valid_ordinal = qd_tls_config_get_oldest_valid_ordinal(li->tls_config); } char *fol = qd_entity_opt_string(entity, "failoverUrls", 0); @@ -285,8 +287,8 @@ QD_EXPORT qd_error_t qd_entity_refresh_connector(qd_entity_t* entity, void *impl QD_EXPORT qd_connector_config_t *qd_dispatch_configure_connector(qd_dispatch_t *qd, qd_entity_t *entity) { - qd_connection_manager_t *cm = qd->connection_manager; - qd_connector_config_t *ctor_config = qd_connector_config_create(qd, entity); + qd_connection_manager_t *cm = qd->connection_manager; + qd_connector_config_t *ctor_config = qd_connector_config_create(qd, entity); if (!ctor_config) { return 0; } diff --git a/src/adaptors/amqp/qd_connection.c b/src/adaptors/amqp/qd_connection.c index fe56a6aec..fd2a074c3 100644 --- a/src/adaptors/amqp/qd_connection.c +++ b/src/adaptors/amqp/qd_connection.c @@ -74,18 +74,14 @@ static void connection_wake(qd_connection_t *ctx) } -static void decorate_connection(qd_connection_t *ctx, const qd_server_config_t *config) +/** Setup connection capabilities and properties. + * These are communicated to the peer via the Open performative. + */ +static void decorate_connection(qd_connection_t *ctx) { - qd_server_t *qd_server = ctx->server; - pn_connection_t *conn = ctx->pn_conn; - // - // Set the container name - // - pn_connection_set_container(conn, qd_server_get_container_name(qd_server)); + pn_connection_t *conn = ctx->pn_conn; + const qd_server_config_t *config = qd_connection_config(ctx); - // - // Advertise our container capabilities. - // { // offered: extension capabilities this router supports pn_data_t *ocaps = pn_connection_offered_capabilities(conn); @@ -151,10 +147,17 @@ static void decorate_connection(qd_connection_t *ctx, const qd_server_config_t * } if (ctx->connector && (ctx->connector->is_data_connector || !!ctx->connector->ctor_config->data_connection_count)) { + uint64_t tls_ordinal; pn_data_put_symbol(pn_connection_properties(conn), pn_bytes(strlen(QD_CONNECTION_PROPERTY_GROUP_CORRELATOR_KEY), QD_CONNECTION_PROPERTY_GROUP_CORRELATOR_KEY)); pn_data_put_string(pn_connection_properties(conn), pn_bytes(strnlen(ctx->group_correlator, QD_DISCRIMINATOR_SIZE - 1), ctx->group_correlator)); + + if (qd_connection_get_tls_ordinal(qd_conn, &tls_ordinal)) { + pn_data_put_symbol(pn_connection_properties(conn), + pn_bytes(strlen(QD_CONNECTION_PROPERTY_TLS_ORDINAL), QD_CONNECTION_PROPERTY_TLS_ORDINAL)); + pn_data_put_ulong(pn_connection_properties(conn), tls_ordinal); + } } if (ctx->listener && !!ctx->listener->vflow_record) { @@ -246,6 +249,8 @@ void qd_connection_init(qd_connection_t *ctx, qd_server_t *server, const qd_serv { ctx->pn_conn = pn_connection(); assert(ctx->pn_conn); + + pn_connection_set_container(ctx->pn_conn, qd_server_get_container_name(server)); sys_mutex_init(&ctx->deferred_call_lock); ctx->role = qd_strdup(config->role); ctx->server = server; @@ -262,9 +267,6 @@ void qd_connection_init(qd_connection_t *ctx, qd_server_t *server, const qd_serv DEQ_INIT(ctx->free_link_list); DEQ_INIT(ctx->child_sessions); - // note: setup connector or listener before decorating the connection since - // decoration involves accessing the connection's parent. - if (!!connector) { assert(!listener); qd_connector_add_connection(connector, ctx); @@ -272,8 +274,6 @@ void qd_connection_init(qd_connection_t *ctx, qd_server_t *server, const qd_serv qd_listener_add_connection(listener, ctx); } - decorate_connection(ctx, config); - sys_mutex_lock(&amqp_adaptor.lock); DEQ_INSERT_TAIL(amqp_adaptor.conn_list, ctx); sys_mutex_unlock(&amqp_adaptor.lock); @@ -619,13 +619,15 @@ static bool setup_ssl_sasl_and_open(qd_connection_t *ctx) pn_sasl_allowed_mechs(sasl, config->sasl_mechanisms); pn_sasl_set_allow_insecure_mechs(sasl, config->allowInsecureAuthentication); + decorate_connection(ctx); pn_connection_open(ctx->pn_conn); return true; } /* Configure the transport once it is bound to the connection */ -static void on_connection_bound(qd_server_t *server, pn_event_t *e) { +static void on_connection_bound(qd_server_t *server, pn_event_t *e) +{ pn_connection_t *pn_conn = pn_event_connection(e); qd_connection_t *ctx = pn_connection_get_context(pn_conn); pn_transport_t *tport = pn_connection_transport(pn_conn); @@ -643,9 +645,21 @@ static void on_connection_bound(qd_server_t *server, pn_event_t *e) { pn_transport_set_tracer(tport, transport_tracer); } - const qd_server_config_t *config = NULL; + const qd_server_config_t *config = qd_connection_config(ctx); + assert(config); + + // + // Common transport configuration. + // + pn_transport_set_max_frame(tport, config->max_frame_size); + pn_transport_set_idle_timeout(tport, config->idle_timeout_seconds * 1000); + // pn_transport_set_channel_max sets the maximum session *identifier*, not the total number of sessions. Thus Proton + // will allow sessions with identifiers [0..max_sessions], which is one greater than the value we pass to + // pn_transport_set_channel_max. So to limit the maximum number of simultaineous sessions to config->max_sessions we + // have to decrement it by one for Proton. + pn_transport_set_channel_max(tport, config->max_sessions - 1); + if (ctx->listener) { /* Accepting an incoming connection */ - config = &ctx->listener->config; const char *name = config->host_port; pn_transport_set_server(tport); set_rhost_port(ctx); @@ -676,13 +690,14 @@ static void on_connection_bound(qd_server_t *server, pn_event_t *e) { pn_transport_require_auth(tport, config->requireAuthentication); pn_transport_require_encryption(tport, config->requireEncryption); pn_sasl_set_allow_insecure_mechs(sasl, config->allowInsecureAuthentication); + decorate_connection(ctx); // This log statement is kept at INFO level because this shows the inter-router // connections and that is useful when debugging router issues. qd_log(LOG_SERVER, QD_LOG_INFO, "[C%" PRIu64 "] Accepted connection to %s from %s", ctx->connection_id, name, ctx->rhost_port); + } else if (ctx->connector) { /* Establishing an outgoing connection */ - config = &ctx->connector->ctor_config->config; if (!setup_ssl_sasl_and_open(ctx)) { qd_log(LOG_SERVER, QD_LOG_ERROR, "[C%" PRIu64 "] Connection aborted due to internal setup error", ctx->connection_id); @@ -695,17 +710,6 @@ static void on_connection_bound(qd_server_t *server, pn_event_t *e) { connect_fail(ctx, QD_AMQP_COND_INTERNAL_ERROR, "unknown Connection"); return; } - - // - // Common transport configuration. - // - pn_transport_set_max_frame(tport, config->max_frame_size); - pn_transport_set_idle_timeout(tport, config->idle_timeout_seconds * 1000); - // pn_transport_set_channel_max sets the maximum session *identifier*, not the total number of sessions. Thus Proton - // will allow sessions with identifiers [0..max_sessions], which is one greater than the value we pass to - // pn_transport_set_channel_max. So to limit the maximum number of simultaineous sessions to config->max_sessions we - // have to decrement it by one for Proton. - pn_transport_set_channel_max(tport, config->max_sessions - 1); } void qd_container_handle_event(qd_container_t *container, pn_event_t *event, pn_connection_t *pn_conn, qd_connection_t *qd_conn); @@ -848,3 +852,14 @@ void qd_amqp_connection_set_tracing(bool enable_tracing) sys_mutex_unlock(&amqp_adaptor.lock); } } + + +bool qd_connection_get_tls_ordinal(const qd_connection_t *qd_conn, uint64_t *tls_ordinal) +{ + if (qd_conn->ssl) { + *tls_ordinal = qd_tls_session_get_profile_ordinal(qd_conn->ssl); + return true; + } + *tls_ordinal = 0; + return false; +} diff --git a/src/adaptors/amqp/qd_connection.h b/src/adaptors/amqp/qd_connection.h index c74040799..2b719cfbb 100644 --- a/src/adaptors/amqp/qd_connection.h +++ b/src/adaptors/amqp/qd_connection.h @@ -297,4 +297,13 @@ void qd_connection_transport_tracer(pn_transport_t *transport, const char *messa bool qd_connection_handle_event(qd_server_t *qd_server, pn_event_t *e, void *context); bool qd_connection_strip_annotations_in(const qd_connection_t *c); + +/** + * Get the value of the TLS ordinal that is in use by this connection. + * + * @return True if the TLS ordinal is configured and tls_ordinal has been set, false if the connection has no TLS + * ordinal. + */ +bool qd_connection_get_tls_ordinal(const qd_connection_t *qd_conn, uint64_t *tls_ordinal); + #endif diff --git a/src/adaptors/amqp/qd_connector.c b/src/adaptors/amqp/qd_connector.c index be39cdb78..8d868e399 100644 --- a/src/adaptors/amqp/qd_connector.c +++ b/src/adaptors/amqp/qd_connector.c @@ -37,6 +37,16 @@ ALLOC_DEFINE(qd_connector_t); ALLOC_DEFINE(qd_connector_config_t); +/* Thread verification + * + * We can avoid locking during various connector operations if we ensure that these operations happen on the same + * thread. + * + * Initial configuration file load occurs on main thread, management updates runs via a zero timer. + */ +#define ASSERT_MGMT_THREAD assert(sys_thread_role(0) == SYS_THREAD_MAIN || sys_thread_proactor_mode() == SYS_THREAD_PROACTOR_MODE_TIMER) + + static qd_failover_item_t *qd_connector_get_conn_info_lh(qd_connector_t *ct) TA_REQ(ct->lock) { qd_failover_item_t *item = DEQ_HEAD(ct->conn_info_list); @@ -425,11 +435,43 @@ void qd_connector_remove_connection(qd_connector_t *connector, bool final, const } +// Handler invoked by mgmt thread whenever the sslProfile is updated for a given qd_connector_t. Check for changes to +// the sslProfile ordinal and oldestValidOrdinal attributes. Note this is called with the sslProfile lock held to +// prevent new connections from being activated until after this call returns. +// +static void handle_connector_ssl_profile_mgmt_update(const qd_tls_config_t *config, void *context) +{ + ASSERT_MGMT_THREAD; + + uint64_t new_ordinal = qd_tls_config_get_ordinal(config); + uint64_t new_oldest_ordinal = qd_tls_config_get_oldest_valid_ordinal(config); + qd_connector_config_t *ctor_config = (qd_connector_config_t *) context; + + if (new_ordinal > ctor_config->tls_ordinal) { + // TBD + qd_log(LOG_SERVER, QD_LOG_DEBUG, + "Connector %s new ordinal: %"PRIu64", previous: %"PRIu64, + ctor_config->config.name, new_ordinal, ctor_config->tls_ordinal); + ctor_config->tls_ordinal = new_ordinal; + } + + if (new_oldest_ordinal > ctor_config->tls_oldest_valid_ordinal) { + // TBD + qd_log(LOG_SERVER, QD_LOG_DEBUG, + "Connector %s new oldest valid ordinal: %"PRIu64", previous: %"PRIu64, + ctor_config->config.name, new_oldest_ordinal, ctor_config->tls_oldest_valid_ordinal); + ctor_config->tls_oldest_valid_ordinal = new_oldest_ordinal; + } +} + + /** * Create a new qd_connector_config_t instance */ qd_connector_config_t *qd_connector_config_create(qd_dispatch_t *qd, qd_entity_t *entity) { + ASSERT_MGMT_THREAD; + qd_connector_config_t *ctor_config = new_qd_connector_config_t(); if (!ctor_config) { char *name = qd_entity_opt_string(entity, "name", "UNKNOWN"); @@ -465,14 +507,20 @@ qd_connector_config_t *qd_connector_config_create(qd_dispatch_t *qd, qd_entity_t // if (ctor_config->config.ssl_profile_name) { ctor_config->tls_config = qd_tls_config(ctor_config->config.ssl_profile_name, - QD_TLS_TYPE_PROTON_AMQP, - QD_TLS_CONFIG_CLIENT_MODE, - ctor_config->config.verify_host_name, - ctor_config->config.ssl_require_peer_authentication); + QD_TLS_TYPE_PROTON_AMQP, + QD_TLS_CONFIG_CLIENT_MODE, + ctor_config->config.verify_host_name, + ctor_config->config.ssl_require_peer_authentication); if (!ctor_config->tls_config) { // qd_tls2_config() has set the qd_error_message(), which is logged below goto error; } + ctor_config->tls_ordinal = qd_tls_config_get_ordinal(ctor_config->tls_config); + ctor_config->tls_oldest_valid_ordinal = qd_tls_config_get_oldest_valid_ordinal(ctor_config->tls_config); + if (strcmp(ctor_config->config.role, "inter-router") == 0) { + qd_tls_config_register_update_callback(ctor_config->tls_config, ctor_config, + handle_connector_ssl_profile_mgmt_update); + } } // For inter-router connectors create associated inter-router data connectors if configured @@ -523,6 +571,8 @@ qd_connector_config_t *qd_connector_config_create(qd_dispatch_t *qd, qd_entity_t void qd_connector_config_delete(qd_connector_config_t *ctor_config) { + ASSERT_MGMT_THREAD; + qd_connector_t *ct = DEQ_HEAD(ctor_config->connectors); while (ct) { DEQ_REMOVE_HEAD(ctor_config->connectors); @@ -531,6 +581,8 @@ void qd_connector_config_delete(qd_connector_config_t *ctor_config) ct = DEQ_HEAD(ctor_config->connectors); } + qd_tls_config_cancel_update_callback(ctor_config->tls_config); + // drop ref held by the caller qd_connector_config_decref(ctor_config); } @@ -568,3 +620,4 @@ void qd_connector_config_connect(qd_connector_config_t *ctor_config) } } } + diff --git a/src/adaptors/amqp/qd_connector.h b/src/adaptors/amqp/qd_connector.h index 667fc743b..5b6955180 100644 --- a/src/adaptors/amqp/qd_connector.h +++ b/src/adaptors/amqp/qd_connector.h @@ -95,7 +95,11 @@ struct qd_connector_config_t { qd_server_config_t config; qd_server_t *server; char *policy_vhost; /* Optional policy vhost name */ + + // TLS Configuration. Keep a local copy of the TLS ordinals to monitor changes by management qd_tls_config_t *tls_config; + uint64_t tls_ordinal; + uint64_t tls_oldest_valid_ordinal; uint32_t data_connection_count; // # of child inter-router data connections // The group correlation id for all child connections @@ -164,5 +168,6 @@ void qd_connector_add_link(qd_connector_t *ctor); // remove the child connection // NOTE WELL: this may free the connector if the connection is holding the last // reference to it -void qd_connector_remove_connection(qd_connector_t *ctor, bool final, const char *condition_name, const char *condition_description); +void qd_connector_remove_connection(qd_connector_t *connector, bool final, const char *condition_name, const char *condition_description); + #endif diff --git a/src/adaptors/amqp/qd_listener.c b/src/adaptors/amqp/qd_listener.c index 254b9e21b..3f4b19757 100644 --- a/src/adaptors/amqp/qd_listener.c +++ b/src/adaptors/amqp/qd_listener.c @@ -216,3 +216,20 @@ void qd_listener_remove_link(qd_listener_t *li) vflow_set_uint64(li->vflow_record, VFLOW_ATTRIBUTE_LINK_COUNT, count); } } + + +void qd_listener_update_tls_ordinal(qd_listener_t *li, uint64_t new_ordinal) +{ + qd_log(LOG_SERVER, QD_LOG_DEBUG, + "Listener %s new ordinal: %"PRIu64", previous: %"PRIu64, + li->config.name, new_ordinal, li->tls_ordinal); + li->tls_ordinal = new_ordinal; +} + +void qd_listener_update_tls_oldest_valid_ordinal(qd_listener_t *li, uint64_t new_oldest_valid_ordinal) +{ + qd_log(LOG_SERVER, QD_LOG_DEBUG, + "Listener %s new oldest valid ordinal: %"PRIu64", previous: %"PRIu64, + li->config.name, new_oldest_valid_ordinal, li->tls_oldest_valid_ordinal); + li->tls_oldest_valid_ordinal = new_oldest_valid_ordinal; +} diff --git a/src/adaptors/amqp/qd_listener.h b/src/adaptors/amqp/qd_listener.h index 85e82fbb2..d3e59f089 100644 --- a/src/adaptors/amqp/qd_listener.h +++ b/src/adaptors/amqp/qd_listener.h @@ -49,7 +49,11 @@ struct qd_listener_t { DEQ_LINKS(qd_listener_t); bool exit_on_error; vflow_record_t *vflow_record; + + // TLS Configuration. Keep a local copy of the TLS ordinals to monitor changes by management qd_tls_config_t *tls_config; + uint64_t tls_ordinal; + uint64_t tls_oldest_valid_ordinal; }; DEQ_DECLARE(qd_listener_t, qd_listener_list_t); @@ -78,4 +82,6 @@ void qd_listener_add_link(qd_listener_t *li); // account for a removed link from the listener void qd_listener_remove_link(qd_listener_t *li); +void qd_listener_update_tls_ordinal(qd_listener_t *li, uint64_t new_ordinal); +void qd_listener_update_tls_oldest_valid_ordinal(qd_listener_t *li, uint64_t new_oldest_valid_ordinal); #endif diff --git a/src/adaptors/tcp/tcp_adaptor.c b/src/adaptors/tcp/tcp_adaptor.c index 8a8040c6e..2b765d83d 100644 --- a/src/adaptors/tcp/tcp_adaptor.c +++ b/src/adaptors/tcp/tcp_adaptor.c @@ -336,6 +336,7 @@ static qdr_connection_t *TL_open_core_connection(uint64_t conn_id, bool incoming "", // user, "TcpAdaptor", // container, properties, // connection_properties, + 0, // TLS Ordinal 0, // ssl_ssf, false, // ssl, "", // peer router version, diff --git a/src/amqp.c b/src/amqp.c index 879a7165d..73b00fbbc 100644 --- a/src/amqp.c +++ b/src/amqp.c @@ -57,6 +57,7 @@ const char * const QD_CONNECTION_PROPERTY_ADAPTOR_KEY = "qd.adaptor"; const char * const QD_CONNECTION_PROPERTY_TCP_ADAPTOR_VALUE = "tcp"; const char * const QD_CONNECTION_PROPERTY_ANNOTATIONS_VERSION_KEY = "qd.annotations-version"; const char * const QD_CONNECTION_PROPERTY_ACCESS_ID = "qd.access-id"; +const char * const QD_CONNECTION_PROPERTY_TLS_ORDINAL = "qd.tls-ordinal"; const char * const QD_TERMINUS_EDGE_ADDRESS_TRACKING = "_$qd.edge_addr_tracking"; const char * const QD_TERMINUS_HEARTBEAT = "_$qd.edge_heartbeat"; diff --git a/src/router_core/connections.c b/src/router_core/connections.c index 61ef70e40..7bb193616 100644 --- a/src/router_core/connections.c +++ b/src/router_core/connections.c @@ -190,6 +190,7 @@ qdr_connection_info_t *qdr_connection_info(bool is_encrypted, const char *user, const char *container, pn_data_t *connection_properties, + uint64_t tls_ordinal, int tls_ssf, bool tls, const char *version, @@ -223,8 +224,9 @@ qdr_connection_info_t *qdr_connection_info(bool is_encrypted, pn_data_copy(qdr_conn_properties, connection_properties); connection_info->connection_properties = qdr_conn_properties; - connection_info->tls_ssf = tls_ssf; - connection_info->tls = tls; + connection_info->tls_ssf = tls_ssf; + connection_info->tls = tls; + connection_info->tls_ordinal = tls_ordinal; connection_info->streaming_links = streaming_links; connection_info->connection_trunking = connection_trunking; sys_mutex_init(&connection_info->connection_info_lock); diff --git a/src/router_core/router_core_private.h b/src/router_core/router_core_private.h index 540639e10..738a15000 100644 --- a/src/router_core/router_core_private.h +++ b/src/router_core/router_core_private.h @@ -654,10 +654,11 @@ struct qdr_connection_info_t { bool opened; bool streaming_links; // will allow streaming links bool connection_trunking; // peer supports connection trunking + bool tls; qd_direction_t dir; qdr_connection_role_t role; pn_data_t *connection_properties; - bool tls; + uint64_t tls_ordinal; // sslProfile revision used for the TLS session int tls_ssf; // TLS strength factor char *version; // if role is router or edge sys_mutex_t connection_info_lock; diff --git a/src/tls/private.h b/src/tls/private.h index cb2832215..2bb2ec6af 100644 --- a/src/tls/private.h +++ b/src/tls/private.h @@ -69,7 +69,7 @@ struct qd_tls_session_t { // copies from parent qd_tls_config_t to avoid locking during I/O: char *ssl_profile_name; char *uid_format; - long version; + uint64_t ordinal; bool tls_error; bool output_eos; // pn_tls_close_output() called @@ -97,7 +97,12 @@ struct qd_tls_config_t { qd_proton_config_t *proton_tls_cfg; // lock must be held qd_tls_type_t p_type; sys_atomic_t ref_count; - long version; // lock must be held + uint64_t ordinal; // lock must be held + uint64_t oldest_valid_ordinal; // lock must be held + + // Invoked on management thread whenever sslProfile is updated + qd_tls_config_update_cb_t update_callback; + void *update_context; bool authenticate_peer; bool verify_hostname; diff --git a/src/tls/tls.c b/src/tls/tls.c index 12291371c..0152e2853 100644 --- a/src/tls/tls.c +++ b/src/tls/tls.c @@ -283,12 +283,14 @@ qd_tls_config_t *qd_tls_config(const char *ssl_profile_name, tls_config->ssl_profile_name = qd_strdup(ssl_profile_name); tls_config->uid_format = CHECKED_STRDUP(tls_context->profile.uid_format); - tls_config->version = tls_context->profile.version; + tls_config->ordinal = tls_context->profile.ordinal; + tls_config->oldest_valid_ordinal = tls_context->profile.oldest_valid_ordinal; tls_config->authenticate_peer = authenticate_peer; tls_config->verify_hostname = verify_hostname; tls_config->is_listener = is_listener; tls_config->p_type = p_type; tls_config->proton_tls_cfg = qd_proton_config(pn_raw_config, pn_amqp_config); + DEQ_INSERT_TAIL(tls_context->tls_configs, tls_config); return tls_config; @@ -314,6 +316,50 @@ void qd_tls_config_decref(qd_tls_config_t *tls_config) } +uint64_t qd_tls_config_get_ordinal(const qd_tls_config_t *config) +{ + // config->ordinal can be changed via management. Calling this from any other thread risks returning a stale value. + ASSERT_MGMT_THREAD; + + return config->ordinal; +} + + +uint64_t qd_tls_config_get_oldest_valid_ordinal(const qd_tls_config_t *config) +{ + // config->oldest_valid_ordinal can be changed via management. Calling this from any other thread risks returning a + // stale value. + ASSERT_MGMT_THREAD; + + return config->oldest_valid_ordinal; +} + + +void qd_tls_config_register_update_callback(qd_tls_config_t *config, void *update_cb_context, + qd_tls_config_update_cb_t update_cb) +{ + // Since this function can only be called on the mgmt thread there is no chance that the update fields are being + // accessed by another thread + ASSERT_MGMT_THREAD; + + config->update_callback = update_cb; + config->update_context = update_cb_context; +} + + +void qd_tls_config_cancel_update_callback(qd_tls_config_t *config) +{ + // Since this function can only be called on the mgmt thread there is no chance that the handler is being run while + // it is being cancelled. + ASSERT_MGMT_THREAD; + + if (config) { + config->update_callback = 0; + config->update_context = 0; + } +} + + qd_tls_session_t *qd_tls_session_raw(qd_tls_config_t *tls_config, const char *peer_hostname, const char **alpn_protocols, size_t alpn_protocol_count, void *context, qd_tls_session_on_secure_cb_t *on_secure) @@ -339,7 +385,7 @@ qd_tls_session_t *qd_tls_session_raw(qd_tls_config_t *tls_config, const char *pe assert(p_cfg); sys_atomic_inc(&p_cfg->ref_count); // prevents free after we drop lock tls_session->uid_format = CHECKED_STRDUP(tls_config->uid_format); // may be changed by mgmt thread - tls_session->version = tls_config->version; // may be changed by mgmt thread + tls_session->ordinal = tls_config->ordinal; // may be changed by mgmt thread sys_mutex_unlock(&tls_config->lock); tls_session->proton_tls_cfg = p_cfg; @@ -434,7 +480,7 @@ qd_tls_session_t *qd_tls_session_amqp(qd_tls_config_t *tls_config, pn_transport_ assert(p_cfg); sys_atomic_inc(&p_cfg->ref_count); // prevents free after we drop lock tls_session->uid_format = CHECKED_STRDUP(tls_config->uid_format); // may be changed by mgmt thread - tls_session->version = tls_config->version; // may be changed by mgmt thread + tls_session->ordinal = tls_config->ordinal; // may be changed by mgmt thread sys_mutex_unlock(&tls_config->lock); tls_session->proton_tls_cfg = p_cfg; @@ -589,6 +635,14 @@ int qd_tls_session_get_ssf(const qd_tls_session_t *tls_session) } +uint64_t qd_tls_session_get_profile_ordinal(const qd_tls_session_t *session) +{ + if (session) + return session->ordinal; + return 0; +} + + qd_ssl2_profile_t *qd_tls_read_ssl_profile(const char *ssl_profile_name, qd_ssl2_profile_t *profile) { ASSERT_MGMT_THREAD; @@ -607,8 +661,8 @@ qd_ssl2_profile_t *qd_tls_read_ssl_profile(const char *ssl_profile_name, qd_ssl2 profile->private_key_file = CHECKED_STRDUP(tls_context->profile.private_key_file); profile->uid_name_mapping_file = CHECKED_STRDUP(tls_context->profile.uid_name_mapping_file); profile->trusted_certificate_db = CHECKED_STRDUP(tls_context->profile.trusted_certificate_db); - profile->version = tls_context->profile.version; - profile->oldest_valid_version = tls_context->profile.oldest_valid_version; + profile->ordinal = tls_context->profile.ordinal; + profile->oldest_valid_ordinal = tls_context->profile.oldest_valid_ordinal; return profile; } @@ -637,6 +691,8 @@ static qd_error_t _read_tls_profile(qd_entity_t *entity, qd_ssl2_profile_t *prof { ZERO(profile); + long ordinal; + long oldest_valid_ordinal; char *name = 0; name = qd_entity_opt_string(entity, "name", ""); if (qd_error_code()) goto error; @@ -657,9 +713,9 @@ static qd_error_t _read_tls_profile(qd_entity_t *entity, qd_ssl2_profile_t *prof if (qd_error_code()) goto error; profile->uid_name_mapping_file = qd_entity_opt_string(entity, "uidNameMappingFile", 0); if (qd_error_code()) goto error; - profile->version = qd_entity_opt_long(entity, "version", 0); + ordinal = qd_entity_opt_long(entity, "ordinal", 0); if (qd_error_code()) goto error; - profile->oldest_valid_version = qd_entity_opt_long(entity, "oldestValidVersion", 0); + oldest_valid_ordinal = qd_entity_opt_long(entity, "oldestValidOrdinal", 0); if (qd_error_code()) goto error; if (profile->uid_format) { @@ -695,16 +751,19 @@ static qd_error_t _read_tls_profile(qd_entity_t *entity, qd_ssl2_profile_t *prof } } - // simple validation of version fields: - if (profile->version < 0 || profile->oldest_valid_version < 0) { - qd_error(QD_ERROR_CONFIG, "Negative version field values are invalid (sslProfile '%s')", name); + // simple validation of ordinal fields: + if (ordinal < 0 || oldest_valid_ordinal < 0) { + qd_error(QD_ERROR_CONFIG, "Negative ordinal field values are invalid (sslProfile '%s')", name); goto error; } - if (profile->version < profile->oldest_valid_version) { - qd_error(QD_ERROR_CONFIG, "version must be >= oldestValidVersion (sslProfile '%s')", name); + if (ordinal < oldest_valid_ordinal) { + qd_error(QD_ERROR_CONFIG, "ordinal must be >= oldestValidOrdinal (sslProfile '%s')", name); goto error; } + profile->ordinal = (uint64_t) ordinal; + profile->oldest_valid_ordinal = (uint64_t) oldest_valid_ordinal; + free(name); return QD_ERROR_NONE; @@ -788,11 +847,19 @@ static qd_error_t _update_tls_config(qd_tls_config_t *tls_config, const qd_ssl2_ old_cfg = tls_config->proton_tls_cfg; tls_config->proton_tls_cfg = new_cfg; // And refresh any parameters that must be used when creating new sessions: - tls_config->version = profile->version; + tls_config->ordinal = profile->ordinal; + tls_config->oldest_valid_ordinal = profile->oldest_valid_ordinal; free(tls_config->uid_format); tls_config->uid_format = CHECKED_STRDUP(profile->uid_format); + + // Calling the callback under lock ensures that no new TLS sessions can be created using the updated + // configuration until after the callback is run. + if (tls_config->update_callback) { + tls_config->update_callback(tls_config, tls_config->update_context); + } sys_mutex_unlock(&tls_config->lock); + // no need to hold the lock here because the decref is atomic and if this // is the last reference then by definition there are no other threads involved. qd_proton_config_decref(old_cfg); diff --git a/tests/system_tests_one_router.py b/tests/system_tests_one_router.py index ca09bd68a..926750d01 100644 --- a/tests/system_tests_one_router.py +++ b/tests/system_tests_one_router.py @@ -487,7 +487,7 @@ def setUpClass(cls): 'certFile': SERVER_CERTIFICATE, 'privateKeyFile': SERVER_PRIVATE_KEY, 'password': "server-password", - 'version': -1}) + 'ordinal': -1}) ]) cls.routers.append(cls.tester.qdrouterd(name, config_23, wait=False, expect=Process.EXIT_FAIL)) @@ -504,8 +504,8 @@ def setUpClass(cls): 'certFile': SERVER_CERTIFICATE, 'privateKeyFile': SERVER_PRIVATE_KEY, 'password': "server-password", - 'version': 1, - 'oldestValidVersion': 42}) + 'ordinal': 1, + 'oldestValidOrdinal': 42}) ]) cls.routers.append(cls.tester.qdrouterd(name, config_24, wait=False, expect=Process.EXIT_FAIL)) @@ -668,10 +668,10 @@ def test_48_router_in_error(self): err = "Failed to configure TLS Ciphers 'Blah-Blah-Blabbity-Blab' for sslProfile 'BadCipherProfile'" self.routers[22].wait_log_message(err, timeout=1.0) - err = "Negative version field values are invalid" + err = "Negative ordinal field values are invalid" self.routers[23].wait_log_message(err, timeout=1.0) - err = "version must be >= oldestValidVersion" + err = "ordinal must be >= oldestValidOrdinal" self.routers[24].wait_log_message(err, timeout=1.0) with open(self.routers[25].outfile + '.out', 'r') as out_file: