Skip to content

Commit f1bf8c9

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 current working directory with the following format: `nsolid-tls-keylog-<process_pid>.log`. PR-URL: #406 Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
1 parent 6208f51 commit f1bf8c9

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
@@ -64,6 +64,7 @@ constexpr size_t span_msg_q_min_size = 1000;
6464

6565
const char* const kNSOLID_GRPC_INSECURE = "NSOLID_GRPC_INSECURE";
6666
const char* const kNSOLID_GRPC_CERTS = "NSOLID_GRPC_CERTS";
67+
const char* const kNSOLID_GRPC_KEYLOG = "NSOLID_GRPC_KEYLOG";
6768

6869
const int MAX_AUTH_RETRIES = 20;
6970
const uint64_t auth_timer_interval = 500;
@@ -457,6 +458,15 @@ GrpcAgent::GrpcAgent(): hooks_init_(false),
457458
cacert_ += "\n";
458459
}
459460
}
461+
462+
auto keylog = per_process::system_environment->Get(kNSOLID_GRPC_KEYLOG);
463+
if (keylog.has_value() && !keylog.value().empty() && keylog.value() != "0") {
464+
tls_keylog_file_ = "./nsolid-tls-keylog-" +
465+
std::to_string(uv_os_getpid()) + ".log";
466+
uv_fs_t req;
467+
uv_fs_unlink(nullptr, &req, tls_keylog_file_.c_str(), nullptr);
468+
uv_fs_req_cleanup(&req);
469+
}
460470
}
461471

462472
GrpcAgent::~GrpcAgent() {
@@ -1049,11 +1059,16 @@ int GrpcAgent::config(const json& config) {
10491059
}
10501060
}
10511061

1052-
nsolid_service_stub_ = GrpcClient::MakeNSolidServiceStub(opts);
1062+
nsolid_service_stub_ =
1063+
GrpcClient::MakeNSolidServiceStub(opts, tls_keylog_file_);
1064+
10531065
// CommandStream needs to be created before the OTLP client to avoid
10541066
// a race condition with abseil mutexes.
10551067
reset_command_stream();
10561068

1069+
// Enable TLS keylog for the OTLP client
1070+
opts.credentials = GrpcClient::MakeCredentials(opts, tls_keylog_file_);
1071+
10571072
std::shared_ptr<OtlpGrpcClient> client =
10581073
OtlpGrpcClientFactory::Create(opts);
10591074

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
],
6667
'dependencies': [
6768
'../protobuf/protobuf.gyp:protobuf',
@@ -73,6 +74,7 @@
7374
'direct_dependent_settings': {
7475
'defines': [
7576
'ENABLE_ASYNC_EXPORT',
77+
'ENABLE_OTLP_GRPC_CREDENTIAL_PREVIEW',
7678
],
7779
'include_dirs': [
7880
'api/include',
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)