Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Client Key and Certificate File Support for All OTLP Exporters #4116

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
39bc279
Implement client key file and client certificate file for all otlp ex…
chittalpatel Dec 14, 2023
6bef4e6
Update CHANGELOG.md
chittalpatel Dec 15, 2023
975b64c
Merge branch 'main' into 2991-client-key-and-certificate-for-otlp-exp…
srikanthccv Dec 31, 2023
5c7e9a3
Merge remote-tracking branch 'origin/main' into 2991-client-key-and-c…
chittalpatel Jan 5, 2024
df4c502
Merge branch 'main' into 2991-client-key-and-certificate-for-otlp-exp…
xrmx Apr 8, 2024
5425755
Merge branch '2991-client-key-and-certificate-for-otlp-exporters' of …
sandy2008 Aug 11, 2024
e0b9ac7
Update changelog and add logging to _load_credentials.
sandy2008 Aug 11, 2024
10ba43d
Change CHANGELOG.md PR to #4116
sandy2008 Aug 11, 2024
deba1ab
Merge branch 'main' into 2991-client-key-and-certificate-for-otlp-exp…
sandy2008 Aug 12, 2024
e59453d
refactor(otlp exporter): refactor client certificate and key handling
sandy2008 Aug 14, 2024
d254a53
refactor(otlp exporter): refactor client certificate and key handling
sandy2008 Aug 14, 2024
b7b5a2f
Merge branch 'main' into 2991-client-key-and-certificate-for-otlp-exp…
sandy2008 Aug 14, 2024
c87f715
test: Add coverage for OTLP exporters without client certificates
sandy2008 Aug 15, 2024
4f4942f
test: Add coverage for OTLP exporters without client certificates
sandy2008 Aug 15, 2024
de377b5
Merge branch '2991-client-key-and-certificate-for-otlp-exporters' of …
sandy2008 Aug 15, 2024
b16967a
test: Add coverage for OTLP exporters without client certificates
sandy2008 Aug 15, 2024
bfcea4e
fix: fix linting for test_otlp_trace_exporter
sandy2008 Aug 15, 2024
3b62fd5
fix: fix linting for test_otlp_trace_exporter
sandy2008 Aug 15, 2024
13c8963
test: add test case for OTLP log exporter with only certificate set
sandy2008 Aug 16, 2024
2feed0d
refactor: handle FileNotFoundError within _read_file function
sandy2008 Aug 16, 2024
b0901a5
Merge branch 'main' into 2991-client-key-and-certificate-for-otlp-exp…
sandy2008 Aug 16, 2024
9e1e80d
refactor: handle FileNotFoundError within _read_file function
sandy2008 Aug 16, 2024
cc0166a
Merge branch '2991-client-key-and-certificate-for-otlp-exporters' of …
sandy2008 Aug 16, 2024
5eeb62a
refactor: handle FileNotFoundError within _read_file function
sandy2008 Aug 16, 2024
d0f48d4
fix: fix linting for test_otlp_trace_exporter
sandy2008 Aug 16, 2024
89376ec
fix: fix linting for test_otlp_trace_exporter
sandy2008 Aug 16, 2024
c48d4ee
fix: fix linting for test_otlp_trace_exporter
sandy2008 Aug 18, 2024
e426963
Merge branch 'main' into 2991-client-key-and-certificate-for-otlp-exp…
lzchen Aug 19, 2024
5204b06
refactor: update `_read_file` function to make `file_path` mandatory
sandy2008 Aug 19, 2024
7d8decb
Merge branch '2991-client-key-and-certificate-for-otlp-exporters' of …
sandy2008 Aug 19, 2024
651cc7d
refactor: standardize handling of client certificate and key file ass…
sandy2008 Aug 20, 2024
19f2836
refactor: standardize handling of client certificate and key file ass…
sandy2008 Aug 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#4103](https://github.com/open-telemetry/opentelemetry-python/pull/4103))
- Update semantic conventions to version 1.27.0
([#4104](https://github.com/open-telemetry/opentelemetry-python/pull/4104))
- Implement Client Key and Certificate File Support for All OTLP Exporters
([#4116](https://github.com/open-telemetry/opentelemetry-python/pull/4116))

## Version 1.26.0/0.47b0 (2024-07-25)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@

from opentelemetry.sdk.environment_variables import (
OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE,
OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE,
OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY,
OTEL_EXPORTER_OTLP_LOGS_COMPRESSION,
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
OTEL_EXPORTER_OTLP_LOGS_HEADERS,
Expand Down Expand Up @@ -71,7 +73,10 @@ def __init__(
and environ.get(OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE) is not None
):
credentials = _get_credentials(
credentials, OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE
credentials,
OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE,
OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY,
OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE,
)

environ_timeout = environ.get(OTEL_EXPORTER_OTLP_LOGS_TIMEOUT)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@
)
from opentelemetry.proto.resource.v1.resource_pb2 import Resource # noqa: F401
from opentelemetry.sdk.environment_variables import (
OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE,
OTEL_EXPORTER_OTLP_CLIENT_KEY,
OTEL_EXPORTER_OTLP_CERTIFICATE,
OTEL_EXPORTER_OTLP_COMPRESSION,
OTEL_EXPORTER_OTLP_ENDPOINT,
Expand Down Expand Up @@ -118,22 +120,55 @@ def get_resource_data(
return _get_resource_data(sdk_resource_scope_data, resource_class, name)


def _load_credential_from_file(filepath) -> ChannelCredentials:
def _read_file(file_path: str) -> Optional[bytes]:
try:
with open(filepath, "rb") as creds_file:
credential = creds_file.read()
return ssl_channel_credentials(credential)
except FileNotFoundError:
logger.exception("Failed to read credential file")
with open(file_path, "rb") as file:
return file.read()
except FileNotFoundError as e:
logger.exception(
f"Failed to read file: {e.filename}. Please check if the file exists and is accessible."
)
return None
sandy2008 marked this conversation as resolved.
Show resolved Hide resolved


def _get_credentials(creds, environ_key):
def _load_credentials(
certificate_file: Optional[str],
client_key_file: Optional[str],
client_certificate_file: Optional[str],
) -> Optional[ChannelCredentials]:
root_certificates = (
_read_file(certificate_file) if certificate_file else None
)
private_key = _read_file(client_key_file) if client_key_file else None
certificate_chain = (
_read_file(client_certificate_file)
if client_certificate_file
else None
)

return ssl_channel_credentials(
root_certificates=root_certificates,
private_key=private_key,
certificate_chain=certificate_chain,
)


def _get_credentials(
creds: Optional[ChannelCredentials],
certificate_file_env_key: str,
client_key_file_env_key: str,
client_certificate_file_env_key: str,
) -> ChannelCredentials:
if creds is not None:
return creds
creds_env = environ.get(environ_key)
if creds_env:
return _load_credential_from_file(creds_env)

certificate_file = environ.get(certificate_file_env_key)
if certificate_file:
client_key_file = environ.get(client_key_file_env_key)
client_certificate_file = environ.get(client_certificate_file_env_key)
return _load_credentials(
certificate_file, client_key_file, client_certificate_file
)
return ssl_channel_credentials()


Expand Down Expand Up @@ -214,7 +249,10 @@ def __init__(
)
else:
credentials = _get_credentials(
credentials, OTEL_EXPORTER_OTLP_CERTIFICATE
credentials,
OTEL_EXPORTER_OTLP_CERTIFICATE,
OTEL_EXPORTER_OTLP_CLIENT_KEY,
OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE,
)
self._client = self._stub(
secure_channel(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
from opentelemetry.proto.metrics.v1 import metrics_pb2 as pb2 # noqa: F401
from opentelemetry.sdk.environment_variables import (
OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE,
OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE,
OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY,
OTEL_EXPORTER_OTLP_METRICS_COMPRESSION,
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
OTEL_EXPORTER_OTLP_METRICS_HEADERS,
Expand Down Expand Up @@ -113,7 +115,10 @@ def __init__(
and environ.get(OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE) is not None
):
credentials = _get_credentials(
credentials, OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE
credentials,
OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE,
OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY,
OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE,
)

environ_timeout = environ.get(OTEL_EXPORTER_OTLP_METRICS_TIMEOUT)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
)
from opentelemetry.proto.trace.v1.trace_pb2 import Status # noqa: F401
from opentelemetry.sdk.environment_variables import (
OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE,
OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY,
OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE,
OTEL_EXPORTER_OTLP_TRACES_COMPRESSION,
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
Expand Down Expand Up @@ -105,7 +107,10 @@ def __init__(
and environ.get(OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE) is not None
):
credentials = _get_credentials(
credentials, OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE
credentials,
OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE,
OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY,
OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE,
)

environ_timeout = environ.get(OTEL_EXPORTER_OTLP_TRACES_TIMEOUT)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=too-many-lines

import time
from concurrent.futures import ThreadPoolExecutor
from os.path import dirname
Expand Down Expand Up @@ -53,6 +55,8 @@
from opentelemetry.sdk._logs.export import LogExportResult
from opentelemetry.sdk.environment_variables import (
OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE,
OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE,
OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY,
OTEL_EXPORTER_OTLP_LOGS_COMPRESSION,
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
OTEL_EXPORTER_OTLP_LOGS_HEADERS,
Expand Down Expand Up @@ -206,12 +210,40 @@ def test_exporting(self):
# pylint: disable=protected-access
self.assertEqual(self.exporter._exporting, "logs")

@patch.dict(
"os.environ",
{
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: "logs:4317",
OTEL_EXPORTER_OTLP_LOGS_HEADERS: " key1=value1,KEY2 = VALUE=2",
OTEL_EXPORTER_OTLP_LOGS_TIMEOUT: "10",
OTEL_EXPORTER_OTLP_LOGS_COMPRESSION: "gzip",
},
)
@patch(
"opentelemetry.exporter.otlp.proto.grpc.exporter.OTLPExporterMixin.__init__"
)
def test_env_variables(self, mock_exporter_mixin):
OTLPLogExporter()

self.assertTrue(len(mock_exporter_mixin.call_args_list) == 1)
_, kwargs = mock_exporter_mixin.call_args_list[0]
self.assertEqual(kwargs["endpoint"], "logs:4317")
self.assertEqual(kwargs["headers"], " key1=value1,KEY2 = VALUE=2")
self.assertEqual(kwargs["timeout"], 10)
self.assertEqual(kwargs["compression"], Compression.Gzip)
self.assertIsNone(kwargs["credentials"])

# Create a new test method specifically for client certificates
@patch.dict(
"os.environ",
{
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: "logs:4317",
OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE: THIS_DIR
+ "/../fixtures/test.cert",
OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE: THIS_DIR
sandy2008 marked this conversation as resolved.
Show resolved Hide resolved
+ "/../fixtures/test-client-cert.pem",
OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY: THIS_DIR
sandy2008 marked this conversation as resolved.
Show resolved Hide resolved
+ "/../fixtures/test-client-key.pem",
OTEL_EXPORTER_OTLP_LOGS_HEADERS: " key1=value1,KEY2 = VALUE=2",
OTEL_EXPORTER_OTLP_LOGS_TIMEOUT: "10",
OTEL_EXPORTER_OTLP_LOGS_COMPRESSION: "gzip",
Expand All @@ -220,7 +252,7 @@ def test_exporting(self):
@patch(
"opentelemetry.exporter.otlp.proto.grpc.exporter.OTLPExporterMixin.__init__"
)
def test_env_variables(self, mock_exporter_mixin):
def test_env_variables_with_client_certificates(self, mock_exporter_mixin):
OTLPLogExporter()

self.assertTrue(len(mock_exporter_mixin.call_args_list) == 1)
Expand All @@ -232,6 +264,37 @@ def test_env_variables(self, mock_exporter_mixin):
self.assertIsNotNone(kwargs["credentials"])
self.assertIsInstance(kwargs["credentials"], ChannelCredentials)

@patch.dict(
"os.environ",
{
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: "logs:4317",
OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE: THIS_DIR
+ "/../fixtures/test.cert",
OTEL_EXPORTER_OTLP_LOGS_HEADERS: " key1=value1,KEY2 = VALUE=2",
OTEL_EXPORTER_OTLP_LOGS_TIMEOUT: "10",
OTEL_EXPORTER_OTLP_LOGS_COMPRESSION: "gzip",
},
)
@patch(
"opentelemetry.exporter.otlp.proto.grpc.exporter.OTLPExporterMixin.__init__"
)
@patch("logging.Logger.error")
def test_env_variables_with_only_certificate(
self, mock_logger_error, mock_exporter_mixin
):
OTLPLogExporter()

self.assertTrue(len(mock_exporter_mixin.call_args_list) == 1)
_, kwargs = mock_exporter_mixin.call_args_list[0]
self.assertEqual(kwargs["endpoint"], "logs:4317")
self.assertEqual(kwargs["headers"], " key1=value1,KEY2 = VALUE=2")
self.assertEqual(kwargs["timeout"], 10)
self.assertEqual(kwargs["compression"], Compression.Gzip)
self.assertIsNotNone(kwargs["credentials"])
self.assertIsInstance(kwargs["credentials"], ChannelCredentials)

mock_logger_error.assert_not_called()

@patch(
"opentelemetry.exporter.otlp.proto.grpc.exporter.ssl_channel_credentials"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=too-many-lines

import threading
from concurrent.futures import ThreadPoolExecutor

Expand Down Expand Up @@ -45,6 +47,8 @@
from opentelemetry.sdk.environment_variables import (
OTEL_EXPORTER_OTLP_COMPRESSION,
OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE,
OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE,
OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY,
OTEL_EXPORTER_OTLP_METRICS_COMPRESSION,
OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION,
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
Expand Down Expand Up @@ -217,12 +221,40 @@ def test_preferred_temporality(self):
AggregationTemporality.CUMULATIVE,
)

@patch.dict(
"os.environ",
{
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: "collector:4317",
OTEL_EXPORTER_OTLP_METRICS_HEADERS: " key1=value1,KEY2 = value=2",
OTEL_EXPORTER_OTLP_METRICS_TIMEOUT: "10",
OTEL_EXPORTER_OTLP_METRICS_COMPRESSION: "gzip",
},
)
@patch(
"opentelemetry.exporter.otlp.proto.grpc.exporter.OTLPExporterMixin.__init__"
)
def test_env_variables(self, mock_exporter_mixin):
OTLPMetricExporter()

self.assertTrue(len(mock_exporter_mixin.call_args_list) == 1)
_, kwargs = mock_exporter_mixin.call_args_list[0]

self.assertEqual(kwargs["endpoint"], "collector:4317")
self.assertEqual(kwargs["headers"], " key1=value1,KEY2 = value=2")
self.assertEqual(kwargs["timeout"], 10)
self.assertEqual(kwargs["compression"], Compression.Gzip)
self.assertIsNone(kwargs["credentials"])

@patch.dict(
"os.environ",
{
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: "collector:4317",
OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE: THIS_DIR
+ "/fixtures/test.cert",
OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE: THIS_DIR
sandy2008 marked this conversation as resolved.
Show resolved Hide resolved
+ "/fixtures/test-client-cert.pem",
OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY: THIS_DIR
+ "/fixtures/test-client-key.pem",
OTEL_EXPORTER_OTLP_METRICS_HEADERS: " key1=value1,KEY2 = value=2",
OTEL_EXPORTER_OTLP_METRICS_TIMEOUT: "10",
OTEL_EXPORTER_OTLP_METRICS_COMPRESSION: "gzip",
Expand All @@ -231,7 +263,7 @@ def test_preferred_temporality(self):
@patch(
"opentelemetry.exporter.otlp.proto.grpc.exporter.OTLPExporterMixin.__init__"
)
def test_env_variables(self, mock_exporter_mixin):
def test_env_variables_with_client_certificates(self, mock_exporter_mixin):
OTLPMetricExporter()

self.assertTrue(len(mock_exporter_mixin.call_args_list) == 1)
Expand All @@ -244,6 +276,37 @@ def test_env_variables(self, mock_exporter_mixin):
self.assertIsNotNone(kwargs["credentials"])
self.assertIsInstance(kwargs["credentials"], ChannelCredentials)

@patch.dict(
"os.environ",
{
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: "collector:4317",
OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE: THIS_DIR
+ "/fixtures/test.cert",
OTEL_EXPORTER_OTLP_METRICS_HEADERS: " key1=value1,KEY2 = value=2",
OTEL_EXPORTER_OTLP_METRICS_TIMEOUT: "10",
OTEL_EXPORTER_OTLP_METRICS_COMPRESSION: "gzip",
},
)
@patch(
"opentelemetry.exporter.otlp.proto.grpc.exporter.OTLPExporterMixin.__init__"
)
@patch("logging.Logger.error")
def test_env_variables_with_only_certificate(
self, mock_logger_error, mock_exporter_mixin
):
OTLPMetricExporter()

self.assertTrue(len(mock_exporter_mixin.call_args_list) == 1)
_, kwargs = mock_exporter_mixin.call_args_list[0]
self.assertEqual(kwargs["endpoint"], "collector:4317")
self.assertEqual(kwargs["headers"], " key1=value1,KEY2 = value=2")
self.assertEqual(kwargs["timeout"], 10)
self.assertEqual(kwargs["compression"], Compression.Gzip)
self.assertIsNotNone(kwargs["credentials"])
self.assertIsInstance(kwargs["credentials"], ChannelCredentials)

mock_logger_error.assert_not_called()

@patch(
"opentelemetry.exporter.otlp.proto.grpc.exporter.ssl_channel_credentials"
)
Expand Down
Loading