Skip to content
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- `opentelemetry-sdk-extension-aws`: Fix `AwsEksResourceDetector` on clusters using the EKS Access Entries API mode where the `aws-auth` ConfigMap is absent; `_is_eks` now decodes the pod service-account JWT `iss` claim instead of querying the Kubernetes API.
([#4414](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4414))
- `opentelemetry-docker-tests`: Replace deprecated `SpanAttributes` from `opentelemetry.semconv.trace` with `opentelemetry.semconv._incubating.attributes`
([#4339](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4339))
- `opentelemetry-instrumentation-confluent-kafka`: Skip `recv` span creation when `poll()` returns no message or `consume()` returns an empty list, avoiding empty spans on idle polls
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import base64
import json
import logging
import os
import re
import ssl
from urllib.request import Request, urlopen

Expand All @@ -32,6 +34,10 @@

_TOKEN_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/token"
_CERT_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
_EKS_OIDC_RE = re.compile(
r"^https://oidc\.eks\.[^.]+\.amazonaws\.com(?:\.cn)?/id/[A-F0-9]{32}$",
re.ASCII,
)
Comment thread
MikeGoldsmith marked this conversation as resolved.


def _aws_http_request(method, path, cred_value):
Expand Down Expand Up @@ -60,11 +66,18 @@ def _get_k8s_cred_value():


def _is_eks(cred_value):
return _aws_http_request(
_GET_METHOD,
"/api/v1/namespaces/kube-system/configmaps/aws-auth",
cred_value,
)
parts = cred_value.removeprefix("Bearer ").split(".")
if len(parts) != 3:
return False
try:
seg = parts[1]
payload = json.loads(
base64.urlsafe_b64decode(seg + "=" * (-len(seg) % 4))
)
except ValueError as exception:
logger.error("Failed to parse JWT for EKS detection: %s", exception)
return False
Comment thread
alimx07 marked this conversation as resolved.
return bool(_EKS_OIDC_RE.match(payload.get("iss", "")))


def _get_cluster_info(cred_value):
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.

import base64
import json
import unittest
from collections import OrderedDict
from unittest.mock import mock_open, patch
Expand All @@ -25,6 +27,17 @@
ResourceAttributes,
)


def _bearer_jwt(payload: dict) -> str:
header = base64.urlsafe_b64encode(b'{"alg":"RS256"}').rstrip(b"=").decode()
body = (
base64.urlsafe_b64encode(json.dumps(payload).encode())
.rstrip(b"=")
.decode()
)
return f"Bearer {header}.{body}.fakesig"


MockEksResourceAttributes = {
ResourceAttributes.CLOUD_PROVIDER: CloudProviderValues.AWS.value,
ResourceAttributes.CLOUD_PLATFORM: CloudPlatformValues.AWS_EKS.value,
Expand Down Expand Up @@ -138,3 +151,98 @@ def test_if_no_eks_paths_should_not_raise(
AwsEksResourceDetector(raise_on_error=True).detect()
except RuntimeError:
self.fail("Should not raise")

@patch(
"opentelemetry.sdk.extension.aws.resource.eks._get_k8s_cred_value",
return_value=_bearer_jwt(
{
"iss": "https://oidc.eks.eu-west-2.amazonaws.com/id/A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4"
}
),
)
@patch(
"opentelemetry.sdk.extension.aws.resource.eks._is_k8s",
return_value=True,
)
@patch(
"opentelemetry.sdk.extension.aws.resource.eks._get_cluster_info",
return_value=f"""{{
"data": {{
"cluster.name": "{MockEksResourceAttributes[ResourceAttributes.K8S_CLUSTER_NAME]}"
}}
}}
""",
)
@patch(
"builtins.open",
new_callable=mock_open,
read_data=f"14:name=systemd:/docker/{MockEksResourceAttributes[ResourceAttributes.CONTAINER_ID]}\n",
)
def test_eks_oidc_jwt_detected(
self,
mock_open_function,
mock_get_cluster_info,
mock_is_k8s,
mock_get_k8s_cred_value,
):
actual = AwsEksResourceDetector().detect()
self.assertEqual(
actual.attributes.get(ResourceAttributes.CLOUD_PLATFORM),
CloudPlatformValues.AWS_EKS.value,
)

@patch(
"opentelemetry.sdk.extension.aws.resource.eks._get_k8s_cred_value",
return_value=_bearer_jwt({"iss": "https://accounts.google.com"}),
)
@patch(
"opentelemetry.sdk.extension.aws.resource.eks._is_k8s",
return_value=True,
)
def test_non_eks_jwt_returns_empty(
self, mock_is_k8s, mock_get_k8s_cred_value
):
actual = AwsEksResourceDetector().detect()
self.assertEqual(actual.attributes, {})

@patch(
"opentelemetry.sdk.extension.aws.resource.eks._get_k8s_cred_value",
return_value=_bearer_jwt({"iss": "https://wrong.jwt.com"}),
)
@patch(
"opentelemetry.sdk.extension.aws.resource.eks._is_k8s",
return_value=True,
)
def test_non_eks_jwt_should_raise(
self, mock_is_k8s, mock_get_k8s_cred_value
):
with self.assertRaises(RuntimeError):
AwsEksResourceDetector(raise_on_error=True).detect()

@patch(
"opentelemetry.sdk.extension.aws.resource.eks._get_k8s_cred_value",
return_value="Bearer notajwt.otel",
)
@patch(
"opentelemetry.sdk.extension.aws.resource.eks._is_k8s",
return_value=True,
)
def test_is_eks_wrong_parts_count_should_raise(
self, mock_is_k8s, mock_get_k8s_cred_value
):
with self.assertRaises(RuntimeError):
AwsEksResourceDetector(raise_on_error=True).detect()

@patch(
"opentelemetry.sdk.extension.aws.resource.eks._get_k8s_cred_value",
return_value="Bearer header.eyJpc3MiOg.fakesig",
)
@patch(
"opentelemetry.sdk.extension.aws.resource.eks._is_k8s",
return_value=True,
)
def test_is_eks_invalid_json_payload_should_raise(
self, mock_is_k8s, mock_get_k8s_cred_value
):
with self.assertRaises(RuntimeError):
AwsEksResourceDetector(raise_on_error=True).detect()