Skip to content

Commit 1e44b9d

Browse files
committed
agents: add option to dump grpc keylog file
By setting the `NSOLID_GRPC_KEYLOG` to a truthy value, a new keylog file will be generated that should allow us to decrypt the TLS v1.3 connections gRPC Agent uses. Very useful to debug issues on production. The file will be generated in the binary path with the following format: `nsolid-tls-keylog-<process_pid>.log`.
1 parent adf21b5 commit 1e44b9d

File tree

6 files changed

+134
-27
lines changed

6 files changed

+134
-27
lines changed

agents/grpc/src/grpc_agent.cc

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ constexpr size_t span_msg_q_min_size = 1000;
6565

6666
const char* const kNSOLID_GRPC_INSECURE = "NSOLID_GRPC_INSECURE";
6767
const char* const kNSOLID_GRPC_CERTS = "NSOLID_GRPC_CERTS";
68+
const char* const kNSOLID_GRPC_KEYLOG = "NSOLID_GRPC_KEYLOG";
6869

6970
const int MAX_AUTH_RETRIES = 20;
7071
const uint64_t auth_timer_interval = 500;
@@ -460,6 +461,15 @@ GrpcAgent::GrpcAgent(): hooks_init_(false),
460461
cacert_ += "\n";
461462
}
462463
}
464+
465+
auto keylog = per_process::system_environment->Get(kNSOLID_GRPC_KEYLOG);
466+
if (keylog.has_value() && !keylog.value().empty() && keylog.value() != "0") {
467+
tls_keylog_file_ = "./nsolid-tls-keylog-" +
468+
std::to_string(uv_os_getpid()) + ".log";
469+
uv_fs_t req;
470+
uv_fs_unlink(nullptr, &req, tls_keylog_file_.c_str(), nullptr);
471+
uv_fs_req_cleanup(&req);
472+
}
463473
}
464474

465475
GrpcAgent::~GrpcAgent() {
@@ -1053,11 +1063,16 @@ int GrpcAgent::config(const json& config) {
10531063
}
10541064
}
10551065

1056-
nsolid_service_stub_ = GrpcClient::MakeNSolidServiceStub(opts);
1066+
nsolid_service_stub_ =
1067+
GrpcClient::MakeNSolidServiceStub(opts, tls_keylog_file_);
1068+
10571069
// CommandStream needs to be created before the OTLP client to avoid
10581070
// a race condition with abseil mutexes.
10591071
reset_command_stream();
10601072

1073+
// Enable TLS keylog for the OTLP client
1074+
opts.credentials = GrpcClient::MakeCredentials(opts, tls_keylog_file_);
1075+
10611076
std::shared_ptr<OtlpGrpcClient> client =
10621077
OtlpGrpcClientFactory::Create(opts);
10631078

agents/grpc/src/grpc_agent.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ class GrpcAgent: public std::enable_shared_from_this<GrpcAgent>,
367367
std::unique_ptr<CommandStream> command_stream_;
368368
std::string cacert_;
369369
std::string custom_certs_;
370+
std::string tls_keylog_file_;
370371

371372
// For the gRPC server
372373
nsuv::ns_async command_msg_;

agents/grpc/src/grpc_client.cc

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,66 @@
11
#include "grpc_client.h"
22
#include "debug_utils-inl.h"
33
#include "opentelemetry/exporters/otlp/otlp_grpc_client_options.h"
4+
#include <grpcpp/security/tls_credentials_options.h>
45

5-
using grpc::Channel;
6-
using grpc::ChannelArguments;
7-
using grpc::ClientContext;
8-
using grpc::CreateCustomChannel;
9-
using grpc::InsecureChannelCredentials;
10-
using grpc::SslCredentials;
11-
using grpc::SslCredentialsOptions;
6+
using ::grpc::Channel;
7+
using ::grpc::ChannelArguments;
8+
using ::grpc::ClientContext;
9+
using ::grpc::CreateCustomChannel;
10+
using ::grpc::InsecureChannelCredentials;
11+
using ::grpc::SslCredentials;
12+
using ::grpc::SslCredentialsOptions;
13+
// The following experimental gRPC TLS APIs are required for TLS session key
14+
// logging (via set_tls_session_key_log_file_path), which is not currently
15+
// supported by the stable SslCredentials API.
16+
// These APIs are subject to change in future gRPC releases. This project
17+
// currently pins gRPC to version 1.76.0
18+
// (see deps/grpc/include/grpcpp/version_info.h).
19+
using ::grpc::experimental::IdentityKeyCertPair;
20+
using ::grpc::experimental::TlsCredentials;
21+
using ::grpc::experimental::TlsChannelCredentialsOptions;
22+
using ::grpc::experimental::StaticDataCertificateProvider;
1223
using grpcagent::NSolidService;
1324
using opentelemetry::v1::exporter::otlp::OtlpGrpcClientOptions;
1425

1526
namespace node {
1627
namespace nsolid {
1728
namespace grpc {
1829

30+
/**
31+
* Create gRPC channel credentials.
32+
*/
33+
std::shared_ptr<::grpc::ChannelCredentials>
34+
GrpcClient::MakeCredentials(const OtlpGrpcClientOptions& options,
35+
const std::string& tls_keylog_file) {
36+
if (!options.use_ssl_credentials) {
37+
return InsecureChannelCredentials();
38+
}
39+
40+
if (!tls_keylog_file.empty()) {
41+
TlsChannelCredentialsOptions tls_opts;
42+
if (!options.ssl_credentials_cacert_as_string.empty()) {
43+
auto cert_provider = std::make_shared<StaticDataCertificateProvider>(
44+
options.ssl_credentials_cacert_as_string,
45+
std::vector<IdentityKeyCertPair>());
46+
tls_opts.set_certificate_provider(cert_provider);
47+
tls_opts.watch_root_certs();
48+
}
49+
tls_opts.set_tls_session_key_log_file_path(tls_keylog_file);
50+
return TlsCredentials(tls_opts);
51+
}
52+
53+
SslCredentialsOptions ssl_opts;
54+
ssl_opts.pem_root_certs = options.ssl_credentials_cacert_as_string;
55+
return SslCredentials(ssl_opts);
56+
}
57+
1958
/**
2059
* Create gRPC channel.
2160
*/
2261
std::shared_ptr<Channel>
23-
GrpcClient::MakeChannel(const OtlpGrpcClientOptions& options) {
62+
GrpcClient::MakeChannel(const OtlpGrpcClientOptions& options,
63+
const std::string& tls_keylog_file) {
2464
std::shared_ptr<Channel> channel;
2565
ChannelArguments grpc_arguments;
2666
// Configure the keepalive of the Client Channel. The keepalive time period is
@@ -32,21 +72,10 @@ std::shared_ptr<Channel>
3272
grpc_arguments.SetInt(GRPC_ARG_KEEPALIVE_TIMEOUT_MS, 15 * 1000 /* 15 sec*/);
3373
grpc_arguments.SetInt(GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS, 1);
3474
grpc_arguments.SetInt(GRPC_ARG_HTTP2_MAX_PINGS_WITHOUT_DATA, 0);
35-
if (!options.use_ssl_credentials) {
36-
channel = CreateCustomChannel(options.endpoint,
37-
InsecureChannelCredentials(),
38-
grpc_arguments);
39-
return channel;
40-
}
41-
4275

43-
SslCredentialsOptions ssl_opts;
44-
ssl_opts.pem_root_certs = options.ssl_credentials_cacert_as_string;
45-
auto channel_creds = SslCredentials(ssl_opts);
46-
channel = CreateCustomChannel(options.endpoint,
47-
channel_creds,
48-
grpc_arguments);
49-
return channel;
76+
return CreateCustomChannel(options.endpoint,
77+
MakeCredentials(options, tls_keylog_file),
78+
grpc_arguments);
5079
}
5180

5281
/**
@@ -68,8 +97,9 @@ GrpcClient::MakeClientContext(const std::string& agent_id,
6897
* Create N|Solid service stub to communicate with the N|Solid Console.
6998
*/
7099
std::unique_ptr<NSolidService::StubInterface>
71-
GrpcClient::MakeNSolidServiceStub(const OtlpGrpcClientOptions& options) {
72-
return NSolidService::NewStub(MakeChannel(options));
100+
GrpcClient::MakeNSolidServiceStub(const OtlpGrpcClientOptions& options,
101+
const std::string& tls_keylog_file) {
102+
return NSolidService::NewStub(MakeChannel(options, tls_keylog_file));
73103
}
74104

75105
} // namespace grpc

agents/grpc/src/grpc_client.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,15 @@ class GrpcClient {
6060
* Create gRPC channel.
6161
*/
6262
static std::shared_ptr<::grpc::Channel>
63-
MakeChannel(const OtlpGrpcClientOptions& options);
63+
MakeChannel(const OtlpGrpcClientOptions& options,
64+
const std::string& tls_keylog_file = "");
65+
66+
/**
67+
* Create gRPC channel credentials.
68+
*/
69+
static std::shared_ptr<::grpc::ChannelCredentials>
70+
MakeCredentials(const OtlpGrpcClientOptions& options,
71+
const std::string& tls_keylog_file);
6472

6573
/**
6674
* Create gRPC client context to call RPC.
@@ -72,7 +80,8 @@ class GrpcClient {
7280
* Create N|Solid service stub to communicate with the N|Solid Console.
7381
*/
7482
static std::unique_ptr<grpcagent::NSolidService::StubInterface>
75-
MakeNSolidServiceStub(const OtlpGrpcClientOptions& options);
83+
MakeNSolidServiceStub(const OtlpGrpcClientOptions& options,
84+
const std::string& tls_keylog_file);
7685

7786
/**
7887
* Generic DelegateAsyncExport for any event type.

deps/opentelemetry-cpp/otlp-http-exporter.gyp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
'defines': [
6363
'BUILDING_LIBCURL',
6464
'ENABLE_ASYNC_EXPORT',
65+
'ENABLE_OTLP_GRPC_CREDENTIAL_PREVIEW',
6566
'OPENTELEMETRY_STL_VERSION=2020',
6667
],
6768
'dependencies': [
@@ -74,6 +75,7 @@
7475
'direct_dependent_settings': {
7576
'defines': [
7677
'ENABLE_ASYNC_EXPORT',
78+
'ENABLE_OTLP_GRPC_CREDENTIAL_PREVIEW',
7779
'OPENTELEMETRY_STL_VERSION=2020',
7880
],
7981
'include_dirs': [
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Flags: --expose-internals
2+
import { mustSucceed } from '../common/index.mjs';
3+
import fixtures from '../common/fixtures.js';
4+
import fs from 'node:fs';
5+
import {
6+
GRPCServer,
7+
TestClient,
8+
} from '../common/nsolid-grpc-agent/index.js';
9+
10+
async function runTest({ getEnv, nsolidConfig }) {
11+
return new Promise((resolve, reject) => {
12+
const grpcServer = new GRPCServer({ tls: true });
13+
grpcServer.start(mustSucceed(async (port) => {
14+
console.log('GRPC server started', port);
15+
const env = getEnv(port);
16+
const opts = {
17+
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
18+
env,
19+
};
20+
const child = new TestClient([], opts);
21+
// Make sure grpc connections are up
22+
await child.id();
23+
// Check keylog file exists
24+
const keylogFile = `./nsolid-tls-keylog-${child.child().pid}.log`;
25+
// Should throw if file does not exist
26+
fs.unlinkSync(keylogFile);
27+
await child.shutdown(0);
28+
grpcServer.close();
29+
resolve();
30+
}));
31+
});
32+
}
33+
34+
const testConfigs = [
35+
{
36+
getEnv: (port) => {
37+
return {
38+
NODE_DEBUG_NATIVE: 'nsolid_grpc_agent',
39+
NSOLID_GRPC: `localhost:${port}`,
40+
NSOLID_GRPC_CERTS: fixtures.path('keys', 'selfsigned-no-keycertsign', 'cert.pem'),
41+
NSOLID_GRPC_KEYLOG: '1',
42+
};
43+
},
44+
},
45+
];
46+
47+
for (const testConfig of testConfigs) {
48+
await runTest(testConfig);
49+
console.log('run test!');
50+
}

0 commit comments

Comments
 (0)