Skip to content

Commit 8b66ac0

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 <[email protected]>
1 parent 6208f51 commit 8b66ac0

File tree

6 files changed

+136
-27
lines changed

6 files changed

+136
-27
lines changed

agents/grpc/src/grpc_agent.cc

Lines changed: 18 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,17 @@ GrpcAgent::GrpcAgent(): hooks_init_(false),
457458
cacert_ += "\n";
458459
}
459460
}
461+
462+
std::string keylog;
463+
if (per_process::system_environment->Get(kNSOLID_GRPC_KEYLOG).To(&keylog)) {
464+
if (!keylog.empty() && keylog != "0") {
465+
tls_keylog_file_ = "./nsolid-tls-keylog-" +
466+
std::to_string(uv_os_getpid()) + ".log";
467+
uv_fs_t req;
468+
uv_fs_unlink(nullptr, &req, tls_keylog_file_.c_str(), nullptr);
469+
uv_fs_req_cleanup(&req);
470+
}
471+
}
460472
}
461473

462474
GrpcAgent::~GrpcAgent() {
@@ -1049,11 +1061,16 @@ int GrpcAgent::config(const json& config) {
10491061
}
10501062
}
10511063

1052-
nsolid_service_stub_ = GrpcClient::MakeNSolidServiceStub(opts);
1064+
nsolid_service_stub_ =
1065+
GrpcClient::MakeNSolidServiceStub(opts, tls_keylog_file_);
1066+
10531067
// CommandStream needs to be created before the OTLP client to avoid
10541068
// a race condition with abseil mutexes.
10551069
reset_command_stream();
10561070

1071+
// Enable TLS keylog for the OTLP client
1072+
opts.credentials = GrpcClient::MakeCredentials(opts, tls_keylog_file_);
1073+
10571074
std::shared_ptr<OtlpGrpcClient> client =
10581075
OtlpGrpcClientFactory::Create(opts);
10591076

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)