4b6817381b
Benchmark image — build + push to ECR (any adapter) / build + push (push) Waiting to run
CI / quality (ubuntu-latest) (push) Waiting to run
CI / test (tools-runtime) (push) Waiting to run
CI / test (e2e-general) (push) Waiting to run
CI / test (cli-runtime) (push) Waiting to run
CI / test (e2e-provider-and-openclaw) (push) Waiting to run
CI / test (integrations-and-misc) (push) Waiting to run
CI / coverage-report (push) Blocked by required conditions
CI / test-kubernetes (push) Waiting to run
CI / should-run-thorough (push) Waiting to run
CI / test-thorough (cloudwatch-demo) (push) Blocked by required conditions
CI / test-thorough (flink-ecs) (push) Blocked by required conditions
CI / test-thorough (upstream-lambda) (push) Blocked by required conditions
CI / test-thorough (prefect-ecs-fargate) (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Blocked by required conditions
Release / publish-release (push) Blocked by required conditions
Release / publish-main-release (push) Blocked by required conditions
Release / prepare (push) Waiting to run
Release / verify (push) Blocked by required conditions
Release / build-python-dist (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Blocked by required conditions
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Waiting to run
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
174 lines
6.6 KiB
Python
174 lines
6.6 KiB
Python
"""Kubernetes client for EKS — builds in-memory config using STS presigned token.
|
|
|
|
Programmatic equivalent of `aws eks get-token` — no kubectl binary required.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import base64
|
|
import logging
|
|
import os
|
|
import tempfile
|
|
import weakref
|
|
from typing import Any
|
|
|
|
import boto3
|
|
import botocore.auth
|
|
import botocore.awsrequest
|
|
import botocore.credentials
|
|
import botocore.session
|
|
from kubernetes import client as k8s_client
|
|
|
|
from config.constants import OPENSRE_TMP_DIR, ensure_opensre_tmp_dir
|
|
from integrations.eks.utils import stored_credentials_to_aws_creds
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _assume_role(role_arn: str, external_id: str, session_name: str) -> dict[str, Any]:
|
|
logger.info("[eks] Assuming role: %s (external_id=%s)", role_arn, external_id or "none")
|
|
sts = boto3.client("sts")
|
|
kwargs: dict = {"RoleArn": role_arn, "RoleSessionName": session_name}
|
|
if external_id:
|
|
kwargs["ExternalId"] = external_id
|
|
creds: dict[str, Any] = sts.assume_role(**kwargs)["Credentials"]
|
|
logger.info("[eks] AssumeRole OK — AccessKeyId prefix: %s", creds["AccessKeyId"][:8])
|
|
return creds
|
|
|
|
|
|
def _generate_eks_token(cluster_name: str, assumed_creds: dict[str, Any], region: str) -> str:
|
|
"""Generate EKS bearer token equivalent to `aws eks get-token`.
|
|
|
|
Builds a SigV4-signed presigned URL for STS GetCallerIdentity with the
|
|
x-k8s-aws-id header included in the canonical request before signing.
|
|
This matches exactly what aws-iam-authenticator and `aws eks get-token` do.
|
|
"""
|
|
creds = botocore.credentials.Credentials(
|
|
access_key=assumed_creds["AccessKeyId"],
|
|
secret_key=assumed_creds["SecretAccessKey"],
|
|
token=assumed_creds["SessionToken"] or None,
|
|
)
|
|
|
|
sts_url = f"https://sts.{region}.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15"
|
|
request = botocore.awsrequest.AWSRequest(
|
|
method="GET",
|
|
url=sts_url,
|
|
headers={"x-k8s-aws-id": cluster_name},
|
|
)
|
|
|
|
signer = botocore.auth.SigV4QueryAuth(creds, "sts", region, expires=60)
|
|
signer.add_auth(request)
|
|
|
|
signed_url = request.url
|
|
if signed_url is None:
|
|
msg = "Failed to generate presigned STS URL for EKS token"
|
|
logger.error("[eks] %s", msg)
|
|
raise RuntimeError(msg)
|
|
|
|
token = "k8s-aws-v1." + base64.urlsafe_b64encode(signed_url.encode()).decode().rstrip("=")
|
|
logger.info("[eks] Kubernetes auth material generated for cluster=%s", cluster_name)
|
|
return token
|
|
|
|
|
|
def _delete_temp_cert(path: str) -> None:
|
|
try:
|
|
os.unlink(path)
|
|
except FileNotFoundError:
|
|
return
|
|
|
|
|
|
def build_k8s_clients(
|
|
cluster_name: str,
|
|
role_arn: str,
|
|
external_id: str,
|
|
region: str,
|
|
credentials: dict[str, Any] | None = None,
|
|
) -> tuple[k8s_client.CoreV1Api, k8s_client.AppsV1Api]:
|
|
"""Assume role, describe cluster, build in-memory Kubernetes API clients.
|
|
|
|
Credential resolution priority:
|
|
1. Explicit `credentials` kwarg (stored AWS integration IAM user creds).
|
|
2. `role_arn` → STS AssumeRole (existing behaviour).
|
|
3. Ambient botocore chain (env AK/SK, shared config, instance profile / IRSA).
|
|
|
|
Returns (CoreV1Api, AppsV1Api) ready to query pods, events, nodes, deployments.
|
|
No kubeconfig file is written to disk.
|
|
"""
|
|
stored = stored_credentials_to_aws_creds(credentials)
|
|
if stored is not None:
|
|
# Explicit stored-integration credentials path (highest priority).
|
|
# Matches the catalog + resolve_integrations flow: the AWS integration
|
|
# is configured with IAM user creds (access_key_id + secret_access_key),
|
|
# possibly with a session_token, and no role_arn. Previously these
|
|
# silently fell through to the ambient botocore chain which missed
|
|
# the stored values entirely when ambient creds were also set but
|
|
# pointed elsewhere. The shared ``stored_credentials_to_aws_creds``
|
|
# helper keeps the normalization rules (empty session_token → None,
|
|
# both required keys present) in sync with ``EKSClient``.
|
|
logger.info("[eks] Using explicit stored-integration AWS credentials")
|
|
assumed = stored
|
|
elif role_arn:
|
|
assumed = _assume_role(role_arn, external_id, "TracerEKSK8sInvestigation")
|
|
else:
|
|
# No role_arn and no explicit creds: fall back to ambient AWS
|
|
# credentials (env AK/SK, shared config profile, or instance profile
|
|
# / IRSA). Preserves the #724 fallback behaviour.
|
|
logger.info("[eks] No role_arn or explicit credentials; using ambient AWS credentials")
|
|
sess = botocore.session.get_session()
|
|
ambient = sess.get_credentials()
|
|
if ambient is None:
|
|
msg = "No AWS credentials available for EKS investigation"
|
|
logger.error("[eks] %s", msg)
|
|
raise RuntimeError(msg)
|
|
frozen = ambient.get_frozen_credentials()
|
|
assumed = {
|
|
"AccessKeyId": frozen.access_key,
|
|
"SecretAccessKey": frozen.secret_key,
|
|
"SessionToken": frozen.token or None,
|
|
}
|
|
|
|
logger.info("[eks] Describing cluster: %s in region %s", cluster_name, region)
|
|
eks = boto3.client(
|
|
"eks",
|
|
region_name=region,
|
|
aws_access_key_id=assumed["AccessKeyId"],
|
|
aws_secret_access_key=assumed["SecretAccessKey"],
|
|
aws_session_token=assumed["SessionToken"] or None,
|
|
)
|
|
cluster_info = eks.describe_cluster(name=cluster_name)["cluster"]
|
|
endpoint = cluster_info["endpoint"]
|
|
status = cluster_info.get("status")
|
|
k8s_version = cluster_info.get("version")
|
|
ca_data = cluster_info["certificateAuthority"]["data"]
|
|
logger.info(
|
|
"[eks] Cluster %s — status=%s version=%s endpoint=%s",
|
|
cluster_name,
|
|
status,
|
|
k8s_version,
|
|
endpoint,
|
|
)
|
|
|
|
ca_bytes = base64.b64decode(ca_data)
|
|
ensure_opensre_tmp_dir()
|
|
with tempfile.NamedTemporaryFile(delete=False, suffix=".crt", dir=OPENSRE_TMP_DIR) as ca_file:
|
|
ca_file.write(ca_bytes)
|
|
ca_file.flush()
|
|
ca_path = ca_file.name
|
|
logger.info("[eks] CA cert written to %s", ca_path)
|
|
|
|
token = _generate_eks_token(cluster_name, assumed, region)
|
|
|
|
configuration = k8s_client.Configuration()
|
|
configuration.host = endpoint
|
|
configuration.ssl_ca_cert = ca_path
|
|
configuration.api_key = {"authorization": f"Bearer {token}"}
|
|
|
|
logger.info("[eks] K8s client built — host=%s", endpoint)
|
|
try:
|
|
api_client = k8s_client.ApiClient(configuration)
|
|
except Exception:
|
|
_delete_temp_cert(ca_path)
|
|
raise
|
|
api_client._ca_cert_cleanup = weakref.finalize(api_client, _delete_temp_cert, ca_path)
|
|
return k8s_client.CoreV1Api(api_client), k8s_client.AppsV1Api(api_client)
|