mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-02 09:48:04 +00:00
The OTLP/HTTP exporter selects TLS from the endpoint URL scheme alone (HttpSslOptions in the pinned SDK matches "https:" exactly), so a client certificate handed to it alongside an http:// traces_endpoint is loaded and never presented. The parser checked the cert/key pairing, use_tls and file readability, but never the scheme, and the default traces_endpoint is plain HTTP. makeTelemetrySetup() now requires traces_endpoint to start with "https://" whenever tls_client_cert is set, including when the key is left at its default. Nothing asserted the client options reaching the exporter, so a swapped certificate and key would have passed every test. Move the options mapping into makeTraceExporterOptions() and assert it at that boundary with distinct certificate and key paths, plus a one-way-TLS control and a use_tls=0 control. One case runs the whole path from a [telemetry] section. Runbook and example-config fixes: - tx.included is emitted per transaction of the agreed consensus set, before buildLCL() applies anything, so it is a superset of the accepted ledger rather than proof of inclusion. - the dispute.resolve query used the descendant operator, but the event is on the consensus.update_positions span itself, so it matched nothing. - the exhausted-retries query asked for txq_status="retried" with retries_remaining=0, which cannot occur: the attribute is stamped before the attempt and the retried branch only runs while retries are left. Exhaustion is txq_status="failed" with a zero count. - consensus_round_id is an int64, so the two queries comparing it to a quoted string matched nothing. - note that consensus_trace_strategy=random is experimental and not used. - note that a trailing "| attr = value" is rejected by current Tempo; attribute filters belong inside the braces.
151 lines
5.4 KiB
C++
151 lines
5.4 KiB
C++
// The whole file is telemetry-only: makeTraceExporterOptions() and the OTel
|
|
// exporter options type it returns are both declared behind
|
|
// XRPL_ENABLE_TELEMETRY, so without it there is nothing here to test.
|
|
#ifdef XRPL_ENABLE_TELEMETRY
|
|
|
|
#include <xrpl/basics/FileUtilities.h>
|
|
#include <xrpl/config/BasicConfig.h>
|
|
#include <xrpl/telemetry/Telemetry.h>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <fstream>
|
|
#include <string>
|
|
|
|
using namespace xrpl;
|
|
|
|
namespace {
|
|
|
|
/**
|
|
* Distinct placeholder paths for the three TLS files.
|
|
*
|
|
* They are deliberately different from one another in more than a suffix, so a
|
|
* certificate written into the key field, or a CA bundle written into either,
|
|
* shows up as an inequality naming both paths rather than as a near-miss.
|
|
*/
|
|
namespace tlsPath {
|
|
constexpr char const* ca = "/etc/xrpl/tls/collector-ca-bundle.pem";
|
|
constexpr char const* clientCert = "/etc/xrpl/tls/node-client-certificate.pem";
|
|
constexpr char const* clientKey = "/etc/xrpl/tls/node-client-private-key.pem";
|
|
} // namespace tlsPath
|
|
|
|
constexpr char const* kHttpsEndpoint = "https://collector.example:4318/v1/traces";
|
|
|
|
/**
|
|
* Build a Setup with mutual TLS configured and nothing else set.
|
|
*
|
|
* The struct is filled directly rather than parsed, so these cases isolate the
|
|
* Setup-to-exporter mapping. The end-to-end case at the bottom of this file
|
|
* covers the config-file side.
|
|
*
|
|
* @param useTls Value for Setup::useTls; the only thing the cases vary.
|
|
* @return The populated Setup.
|
|
*/
|
|
telemetry::Telemetry::Setup
|
|
makeMtlsSetup(bool useTls)
|
|
{
|
|
telemetry::Telemetry::Setup setup;
|
|
setup.enabled = true;
|
|
setup.tracesEndpoint = kHttpsEndpoint;
|
|
setup.useTls = useTls;
|
|
setup.tlsCertPath = tlsPath::ca;
|
|
setup.tlsClientCertPath = tlsPath::clientCert;
|
|
setup.tlsClientKeyPath = tlsPath::clientKey;
|
|
return setup;
|
|
}
|
|
|
|
/**
|
|
* Write a placeholder certificate file at the given path.
|
|
*
|
|
* makeTelemetrySetup() only needs the file to exist and be readable; nothing
|
|
* checks that the contents parse as PEM.
|
|
*
|
|
* @param path Where to write the file, typically from TempDir::file().
|
|
* @return The same path, ready to pass to Section::set().
|
|
*/
|
|
std::string
|
|
writeCertFile(std::string const& path)
|
|
{
|
|
std::ofstream out{path};
|
|
out << "placeholder\n";
|
|
out.close();
|
|
EXPECT_TRUE(out.good()) << "could not create " << path;
|
|
return path;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(TraceExporterOptions, mtls_paths_reach_the_matching_exporter_fields)
|
|
{
|
|
// The assertion the exporter boundary was missing: each configured path
|
|
// lands in its own field. The three paths differ, so swapping the client
|
|
// certificate and key, or writing the CA bundle into a client field, fails
|
|
// here instead of failing as a TLS handshake error on a live collector.
|
|
auto const opts = telemetry::makeTraceExporterOptions(makeMtlsSetup(true));
|
|
|
|
EXPECT_EQ(opts.url, kHttpsEndpoint);
|
|
EXPECT_EQ(opts.ssl_ca_cert_path, tlsPath::ca);
|
|
EXPECT_EQ(opts.ssl_client_cert_path, tlsPath::clientCert);
|
|
EXPECT_EQ(opts.ssl_client_key_path, tlsPath::clientKey);
|
|
}
|
|
|
|
TEST(TraceExporterOptions, one_way_tls_leaves_the_client_fields_empty)
|
|
{
|
|
// The one-way TLS control: a CA bundle and no client identity. The client
|
|
// fields must stay empty, so an unset client certificate cannot pick up a
|
|
// path from somewhere else in Setup.
|
|
auto setup = makeMtlsSetup(true);
|
|
setup.tlsClientCertPath.clear();
|
|
setup.tlsClientKeyPath.clear();
|
|
|
|
auto const opts = telemetry::makeTraceExporterOptions(setup);
|
|
|
|
EXPECT_EQ(opts.url, kHttpsEndpoint);
|
|
EXPECT_EQ(opts.ssl_ca_cert_path, tlsPath::ca);
|
|
EXPECT_EQ(opts.ssl_client_cert_path, "");
|
|
EXPECT_EQ(opts.ssl_client_key_path, "");
|
|
}
|
|
|
|
TEST(TraceExporterOptions, use_tls_off_passes_no_tls_paths_at_all)
|
|
{
|
|
// Every path is configured and use_tls is off, so all three fields must
|
|
// stay empty while the URL still goes through. This is the only case that
|
|
// distinguishes "gated on use_tls" from "always copied".
|
|
auto const opts = telemetry::makeTraceExporterOptions(makeMtlsSetup(false));
|
|
|
|
EXPECT_EQ(opts.url, kHttpsEndpoint);
|
|
EXPECT_EQ(opts.ssl_ca_cert_path, "");
|
|
EXPECT_EQ(opts.ssl_client_cert_path, "");
|
|
EXPECT_EQ(opts.ssl_client_key_path, "");
|
|
}
|
|
|
|
TEST(TraceExporterOptions, config_section_reaches_the_exporter_options)
|
|
{
|
|
// The whole path in one case: a [telemetry] section with an https endpoint
|
|
// and two different real files, parsed by makeTelemetrySetup() and then
|
|
// mapped. Nothing between the config file and the exporter is stubbed, so a
|
|
// break anywhere along it lands here.
|
|
TempDir const dir;
|
|
auto const cert = writeCertFile(dir.file("node-client-certificate.pem"));
|
|
auto const key = writeCertFile(dir.file("node-client-private-key.pem"));
|
|
ASSERT_NE(cert, key);
|
|
|
|
Section section;
|
|
section.set("enabled", "1");
|
|
section.set("traces_endpoint", kHttpsEndpoint);
|
|
section.set("use_tls", "1");
|
|
section.set("tls_client_cert", cert);
|
|
section.set("tls_client_key", key);
|
|
|
|
auto const setup = telemetry::makeTelemetrySetup(section, "nHUtest123", "2.0.0", 0);
|
|
auto const opts = telemetry::makeTraceExporterOptions(setup);
|
|
|
|
EXPECT_EQ(opts.url, kHttpsEndpoint);
|
|
EXPECT_EQ(opts.ssl_client_cert_path, cert);
|
|
EXPECT_EQ(opts.ssl_client_key_path, key);
|
|
// No tls_ca_cert in the section, so the exporter keeps its own trust store.
|
|
EXPECT_EQ(opts.ssl_ca_cert_path, "");
|
|
}
|
|
|
|
#endif // XRPL_ENABLE_TELEMETRY
|