Skip to content

Commit

Permalink
feat: Add support for mutual TLS. (#1556)
Browse files Browse the repository at this point in the history
Add support for specifying client certificate and key for mutual TLS when exporting.

Co-authored-by: Ariel Valentin <arielvalentin@users.noreply.github.com>
Co-authored-by: Matthew Wear <matthew.wear@gmail.com>
  • Loading branch information
3 people authored Aug 12, 2024
1 parent 9415f74 commit d9324e0
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 28 deletions.
9 changes: 6 additions & 3 deletions exporter/otlp-grpc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,12 @@ For additional examples, see the [examples on github][examples-github].

The collector exporter can be configured explicitly in code, or via environment variables as shown above. The configuration parameters, environment variables, and defaults are shown below.

| Parameter | Environment variable | Default |
| ------------------- | -------------------------------------------- | ----------------------------------- |
| `endpoint:` | `OTEL_EXPORTER_OTLP_ENDPOINT` | `"http://localhost:4318/v1/traces"` |
| Parameter | Environment variable | Default |
| ------------------------- | -------------------------------------------- | ----------------------------------- |
| `endpoint:` | `OTEL_EXPORTER_OTLP_ENDPOINT` | `"http://localhost:4318/v1/traces"` |
| `certificate_file:` | `OTEL_EXPORTER_OTLP_CERTIFICATE` | |
| `client_certificate_file` | `OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE` | |
| `client_key_file` | `OTEL_EXPORTER_OTLP_CLIENT_KEY` | |

## How can I get involved?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,26 @@ class TraceExporter

def initialize(endpoint: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_ENDPOINT', 'OTEL_EXPORTER_OTLP_ENDPOINT', default: 'http://localhost:4317/v1/traces'),
timeout: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_TIMEOUT', 'OTEL_EXPORTER_OTLP_TIMEOUT', default: 10),
certificate_file: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE', 'OTEL_EXPORTER_OTLP_CERTIFICATE'),
client_certificate_file: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE', 'OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE'),
client_key_file: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY', 'OTEL_EXPORTER_OTLP_CLIENT_KEY'),
metrics_reporter: nil)
raise ArgumentError, "invalid url for OTLP::Exporter #{endpoint}" unless OpenTelemetry::Common::Utilities.valid_url?(endpoint)

uri = URI(endpoint)

creds = if !client_key_file.nil? && !client_certificate_file.nil?
# Permits constructing with nil root cert.
::GRPC::Core::ChannelCredentials.new(certificate_file, client_key_file, client_certificate_file)
elsif !certificate_file.nil?
::GRPC::Core::ChannelCredentials.new(certificate_file)
else
:this_channel_is_insecure
end

@client = Opentelemetry::Proto::Collector::Trace::V1::TraceService::Stub.new(
"#{uri.host}:#{uri.port}",
:this_channel_is_insecure
creds
)

@timeout = timeout.to_f
Expand Down
20 changes: 11 additions & 9 deletions exporter/otlp-http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,17 @@ For additional examples, see the [examples on github][examples-github].

The collector exporter can be configured explicitly in code, or via environment variables as shown above. The configuration parameters, environment variables, and defaults are shown below.

| Parameter | Environment variable | Default |
| ------------------- | -------------------------------------------- | ----------------------------------- |
| `endpoint:` | `OTEL_EXPORTER_OTLP_ENDPOINT` | `"http://localhost:4318/v1/traces"` |
| `certificate_file: `| `OTEL_EXPORTER_OTLP_CERTIFICATE` | |
| `headers:` | `OTEL_EXPORTER_OTLP_HEADERS` | |
| `compression:` | `OTEL_EXPORTER_OTLP_COMPRESSION` | `"gzip"` |
| `timeout:` | `OTEL_EXPORTER_OTLP_TIMEOUT` | `10` |
| `ssl_verify_mode:` | `OTEL_RUBY_EXPORTER_OTLP_SSL_VERIFY_PEER` or | `OpenSSL::SSL:VERIFY_PEER` |
| | `OTEL_RUBY_EXPORTER_OTLP_SSL_VERIFY_NONE` | |
| Parameter | Environment variable | Default |
|---------------------------|----------------------------------------------|-------------------------------------|
| `endpoint:` | `OTEL_EXPORTER_OTLP_ENDPOINT` | `"http://localhost:4318/v1/traces"` |
| `certificate_file:` | `OTEL_EXPORTER_OTLP_CERTIFICATE` | |
| `client_certificate_file` | `OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE` | |
| `client_key_file` | `OTEL_EXPORTER_OTLP_CLIENT_KEY` | |
| `headers:` | `OTEL_EXPORTER_OTLP_HEADERS` | |
| `compression:` | `OTEL_EXPORTER_OTLP_COMPRESSION` | `"gzip"` |
| `timeout:` | `OTEL_EXPORTER_OTLP_TIMEOUT` | `10` |
| `ssl_verify_mode:` | `OTEL_RUBY_EXPORTER_OTLP_SSL_VERIFY_PEER` or | `OpenSSL::SSL:VERIFY_PEER` |
| | `OTEL_RUBY_EXPORTER_OTLP_SSL_VERIFY_NONE` | |

`ssl_verify_mode:` parameter values should be flags for server certificate verification: `OpenSSL::SSL:VERIFY_PEER` and `OpenSSL::SSL:VERIFY_NONE` are acceptable. These values can also be set using the appropriately named environment variables as shown where `VERIFY_PEER` will take precedence over `VERIFY_NONE`. Please see [the Net::HTTP docs](https://ruby-doc.org/stdlib-2.5.1/libdoc/net/http/rdoc/Net/HTTP.html#verify_mode) for more information about these flags.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class TraceExporter # rubocop:disable Metrics/ClassLength

def initialize(endpoint: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_ENDPOINT', 'OTEL_EXPORTER_OTLP_ENDPOINT', default: 'http://localhost:4318/v1/traces'),
certificate_file: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE', 'OTEL_EXPORTER_OTLP_CERTIFICATE'),
client_certificate_file: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE', 'OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE'),
client_key_file: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY', 'OTEL_EXPORTER_OTLP_CLIENT_KEY'),
ssl_verify_mode: fetch_ssl_verify_mode,
headers: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_HEADERS', 'OTEL_EXPORTER_OTLP_HEADERS', default: {}),
compression: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_COMPRESSION', 'OTEL_EXPORTER_OTLP_COMPRESSION', default: 'gzip'),
Expand All @@ -47,7 +49,7 @@ def initialize(endpoint: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPOR
URI(endpoint)
end

@http = http_connection(@uri, ssl_verify_mode, certificate_file)
@http = http_connection(@uri, ssl_verify_mode, certificate_file, client_certificate_file, client_key_file)

@path = @uri.path
@headers = case headers
Expand Down Expand Up @@ -107,11 +109,13 @@ def fetch_ssl_verify_mode
end
end

def http_connection(uri, ssl_verify_mode, certificate_file)
def http_connection(uri, ssl_verify_mode, certificate_file, client_certificate_file, client_key_file)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'
http.verify_mode = ssl_verify_mode
http.ca_file = certificate_file unless certificate_file.nil?
http.cert = OpenSSL::X509::Certificate.new(File.read(client_certificate_file)) unless client_certificate_file.nil?
http.key = OpenSSL::PKey::RSA.new(File.read(client_key_file)) unless client_key_file.nil?
http.keep_alive_timeout = KEEP_ALIVE_TIMEOUT
http
end
Expand Down
20 changes: 11 additions & 9 deletions exporter/otlp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,17 @@ For additional examples, see the [examples on github][examples-github].

The collector exporter can be configured explicitly in code, or via environment variables as shown above. The configuration parameters, environment variables, and defaults are shown below.

| Parameter | Environment variable | Default |
| ------------------- | -------------------------------------------- | ----------------------------------- |
| `endpoint:` | `OTEL_EXPORTER_OTLP_ENDPOINT` | `"http://localhost:4318/v1/traces"` |
| `certificate_file: `| `OTEL_EXPORTER_OTLP_CERTIFICATE` | |
| `headers:` | `OTEL_EXPORTER_OTLP_HEADERS` | |
| `compression:` | `OTEL_EXPORTER_OTLP_COMPRESSION` | `"gzip"` |
| `timeout:` | `OTEL_EXPORTER_OTLP_TIMEOUT` | `10` |
| `ssl_verify_mode:` | `OTEL_RUBY_EXPORTER_OTLP_SSL_VERIFY_PEER` or | `OpenSSL::SSL:VERIFY_PEER` |
| | `OTEL_RUBY_EXPORTER_OTLP_SSL_VERIFY_NONE` | |
| Parameter | Environment variable | Default |
|---------------------------| -------------------------------------------- | ----------------------------------- |
| `endpoint:` | `OTEL_EXPORTER_OTLP_ENDPOINT` | `"http://localhost:4318/v1/traces"` |
| `certificate_file:` | `OTEL_EXPORTER_OTLP_CERTIFICATE` | |
| `client_certificate_file` | `OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE` | |
| `client_key_file` | `OTEL_EXPORTER_OTLP_CLIENT_KEY` | |
| `headers:` | `OTEL_EXPORTER_OTLP_HEADERS` | |
| `compression:` | `OTEL_EXPORTER_OTLP_COMPRESSION` | `"gzip"` |
| `timeout:` | `OTEL_EXPORTER_OTLP_TIMEOUT` | `10` |
| `ssl_verify_mode:` | `OTEL_RUBY_EXPORTER_OTLP_SSL_VERIFY_PEER` or | `OpenSSL::SSL:VERIFY_PEER` |
| | `OTEL_RUBY_EXPORTER_OTLP_SSL_VERIFY_NONE` | |

`ssl_verify_mode:` parameter values should be flags for server certificate verification: `OpenSSL::SSL:VERIFY_PEER` and `OpenSSL::SSL:VERIFY_NONE` are acceptable. These values can also be set using the appropriately named environment variables as shown where `VERIFY_PEER` will take precedence over `VERIFY_NONE`. Please see [the Net::HTTP docs](https://ruby-doc.org/stdlib-2.7.6/libdoc/net/http/rdoc/Net/HTTP.html#verify_mode) for more information about these flags.

Expand Down
8 changes: 6 additions & 2 deletions exporter/otlp/lib/opentelemetry/exporter/otlp/exporter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ def self.ssl_verify_mode

def initialize(endpoint: nil,
certificate_file: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE', 'OTEL_EXPORTER_OTLP_CERTIFICATE'),
client_certificate_file: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE', 'OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE'),
client_key_file: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY', 'OTEL_EXPORTER_OTLP_CLIENT_KEY'),
ssl_verify_mode: Exporter.ssl_verify_mode,
headers: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_HEADERS', 'OTEL_EXPORTER_OTLP_HEADERS', default: {}),
compression: OpenTelemetry::Common::Utilities.config_opt('OTEL_EXPORTER_OTLP_TRACES_COMPRESSION', 'OTEL_EXPORTER_OTLP_COMPRESSION', default: 'gzip'),
Expand All @@ -57,7 +59,7 @@ def initialize(endpoint: nil,

raise ArgumentError, "unsupported compression key #{compression}" unless compression.nil? || %w[gzip none].include?(compression)

@http = http_connection(@uri, ssl_verify_mode, certificate_file)
@http = http_connection(@uri, ssl_verify_mode, certificate_file, client_certificate_file, client_key_file)

@path = @uri.path
@headers = prepare_headers(headers)
Expand Down Expand Up @@ -102,11 +104,13 @@ def shutdown(timeout: nil)

private

def http_connection(uri, ssl_verify_mode, certificate_file)
def http_connection(uri, ssl_verify_mode, certificate_file, client_certificate_file, client_key_file)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'
http.verify_mode = ssl_verify_mode
http.ca_file = certificate_file unless certificate_file.nil?
http.cert = OpenSSL::X509::Certificate.new(File.read(client_certificate_file)) unless client_certificate_file.nil?
http.key = OpenSSL::PKey::RSA.new(File.read(client_key_file)) unless client_key_file.nil?
http.keep_alive_timeout = KEEP_ALIVE_TIMEOUT
http
end
Expand Down
22 changes: 20 additions & 2 deletions exporter/otlp/test/opentelemetry/exporter/otlp/exporter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
FAILURE = OpenTelemetry::SDK::Trace::Export::FAILURE
VERSION = OpenTelemetry::Exporter::OTLP::VERSION
DEFAULT_USER_AGENT = OpenTelemetry::Exporter::OTLP::Exporter::DEFAULT_USER_AGENT
CLIENT_CERT_A_PATH = File.dirname(__FILE__) + '/mtls-client-a.pem'
CLIENT_CERT_A = OpenSSL::X509::Certificate.new(File.read(CLIENT_CERT_A_PATH))
CLIENT_KEY_A = OpenSSL::PKey::RSA.new(File.read(CLIENT_CERT_A_PATH))
CLIENT_CERT_B_PATH = File.dirname(__FILE__) + '/mtls-client-b.pem'
CLIENT_CERT_B = OpenSSL::X509::Certificate.new(File.read(CLIENT_CERT_B_PATH))
CLIENT_KEY_B = OpenSSL::PKey::RSA.new(File.read(CLIENT_CERT_B_PATH))

describe '#initialize' do
it 'initializes with defaults' do
Expand All @@ -23,6 +29,8 @@
_(exp.instance_variable_get(:@compression)).must_equal 'gzip'
http = exp.instance_variable_get(:@http)
_(http.ca_file).must_be_nil
_(http.cert).must_be_nil
_(http.key).must_be_nil
_(http.use_ssl?).must_equal false
_(http.address).must_equal 'localhost'
_(http.verify_mode).must_equal OpenSSL::SSL::VERIFY_PEER
Expand Down Expand Up @@ -76,7 +84,9 @@

it 'sets parameters from the environment' do
exp = OpenTelemetry::TestHelpers.with_env('OTEL_EXPORTER_OTLP_ENDPOINT' => 'https://localhost:1234',
'OTEL_EXPORTER_OTLP_CERTIFICATE' => '/foo/bar',
'OTEL_EXPORTER_OTLP_CERTIFICATE' => '/foo/bar/cacert',
'OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE' => CLIENT_CERT_A_PATH,
'OTEL_EXPORTER_OTLP_CLIENT_KEY' => CLIENT_CERT_A_PATH,
'OTEL_EXPORTER_OTLP_HEADERS' => 'a=b,c=d',
'OTEL_EXPORTER_OTLP_COMPRESSION' => 'gzip',
'OTEL_RUBY_EXPORTER_OTLP_SSL_VERIFY_NONE' => 'true',
Expand All @@ -88,7 +98,9 @@
_(exp.instance_variable_get(:@path)).must_equal '/v1/traces'
_(exp.instance_variable_get(:@compression)).must_equal 'gzip'
http = exp.instance_variable_get(:@http)
_(http.ca_file).must_equal '/foo/bar'
_(http.ca_file).must_equal '/foo/bar/cacert'
_(http.cert).must_equal CLIENT_CERT_A
_(http.key.params).must_equal CLIENT_KEY_A.params
_(http.use_ssl?).must_equal true
_(http.address).must_equal 'localhost'
_(http.verify_mode).must_equal OpenSSL::SSL::VERIFY_NONE
Expand All @@ -98,12 +110,16 @@
it 'prefers explicit parameters rather than the environment' do
exp = OpenTelemetry::TestHelpers.with_env('OTEL_EXPORTER_OTLP_ENDPOINT' => 'https://localhost:1234',
'OTEL_EXPORTER_OTLP_CERTIFICATE' => '/foo/bar',
'OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE' => CLIENT_CERT_A_PATH,
'OTEL_EXPORTER_OTLP_CLIENT_KEY' => CLIENT_CERT_A_PATH,
'OTEL_EXPORTER_OTLP_HEADERS' => 'a:b,c:d',
'OTEL_EXPORTER_OTLP_COMPRESSION' => 'flate',
'OTEL_RUBY_EXPORTER_OTLP_SSL_VERIFY_PEER' => 'true',
'OTEL_EXPORTER_OTLP_TIMEOUT' => '11') do
OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint: 'http://localhost:4321',
certificate_file: '/baz',
client_certificate_file: CLIENT_CERT_B_PATH,
client_key_file: CLIENT_CERT_B_PATH,
headers: { 'x' => 'y' },
compression: 'gzip',
ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE,
Expand All @@ -115,6 +131,8 @@
_(exp.instance_variable_get(:@compression)).must_equal 'gzip'
http = exp.instance_variable_get(:@http)
_(http.ca_file).must_equal '/baz'
_(http.cert).must_equal CLIENT_CERT_B
_(http.key.params).must_equal CLIENT_KEY_B.params
_(http.use_ssl?).must_equal false
_(http.verify_mode).must_equal OpenSSL::SSL::VERIFY_NONE
_(http.address).must_equal 'localhost'
Expand Down
Loading

0 comments on commit d9324e0

Please sign in to comment.