Skip to content

Commit

Permalink
add eks manager
Browse files Browse the repository at this point in the history
  • Loading branch information
morga471 committed Sep 30, 2025
1 parent bc67bdc commit 27bea12
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 8 deletions.
2 changes: 1 addition & 1 deletion local-app/python-tools/gfl-resource-actions/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ dist:

# Run in dry-run mode
run-dry-run:
$(PYTHON) -m $(PACKAGE_NAME).cli --stop --dry-run --resource-type ecr --csv-output-dir ./ --csv-export --csv-per-account
$(PYTHON) -m $(PACKAGE_NAME).cli --stop --dry-run --resource-type all --csv-output-dir ./ --csv-export --csv-per-account

# Run to stop resources
run-stop:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def process_command(args: argparse.Namespace) -> None:
exclude_resources = []
if args.resource_type and args.resource_type != "all":
# Only include the specified resource type
for res_type in ["ec2", "rds", "eks", "emr", "ecr"]:
for res_type in ["ec2", "rds", "eks", "emr", "ecr", "eks_images"]:
if res_type != args.resource_type:
exclude_resources.append(res_type)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,7 @@ def _initialize_resource_dict(self) -> Dict[str, List[Dict[str, Any]]]:
"emr_clusters": [],
"ecr_images": [],
"ecr_old_images": [],
"eks_images": [],
}

def _discover_account_resources(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,9 @@
logger,
paginate_aws_response,
)
from aws_resource_management.managers.eks import EKSManager

# Try to import optional managers
try:
from aws_resource_management.managers import EKSManager
except ImportError:
EKSManager = None
try:
from aws_resource_management.managers import EMRManager
except ImportError:
Expand Down Expand Up @@ -112,6 +109,10 @@ def discover_resources(
)
return []

# Register new resource type
if resource_type == "eks_images":
discovery_method = self._discover_eks_images

# Use thread pool to speed up discovery across regions
resources = []
with concurrent.futures.ThreadPoolExecutor(
Expand Down Expand Up @@ -583,6 +584,36 @@ def _discover_ecr(
logger.error(f"Error discovering ECR repositories in {region}: {e}")
return []

def _discover_eks_images(
self, region: str, resource_ids: Optional[List[str]] = None
) -> List[Dict[str, Any]]:
"""
Discover EKS cluster images in a region.
Args:
region: AWS region
resource_ids: Not used (for interface compatibility)
Returns:
List of EKS image inventory dicts
"""
try:
manager = EKSManager(self.account_id, region)
# Use all known accounts for internal/external ECR detection
from aws_resource_management.utils.aws_core_utils import get_account_list

accounts = set(acc["account_id"] for acc in get_account_list())
images = manager.discover_cluster_images([region], accounts=accounts)
# Add account info
for img in images:
img["accountId"] = self.account_id
if self.account_name:
img["accountName"] = self.account_name
return images
except Exception as e:
logger.error(f"Error discovering EKS images in {region}: {e}")
return []


def discover_ecr_images(
credentials: Dict[str, str],
Expand Down Expand Up @@ -730,6 +761,7 @@ def _get_account_resources_impl(
"emr_clusters": [],
"ecr_images": [],
"ecr_old_images": [],
"eks_images": [], # New: EKS image inventory
}

try:
Expand Down Expand Up @@ -828,6 +860,19 @@ def _get_account_resources_impl(
else:
logger.debug("Skipping ECR discovery as per resource type filter")

# Discover EKS images - only if not excluded
if "eks_images" not in exclude_resources:
logger.info(
f"Discovering EKS cluster images for account {account_name} ({account_id}) in {len(regions)} regions"
)
try:
resources["eks_images"] = discovery.discover_resources("eks_images", regions)
except Exception as e:
logger.error(f"Error discovering EKS cluster images: {e}")
resources["eks_images"] = []
else:
logger.debug("Skipping EKS image inventory as per resource type filter")

# Add account information to all resources
for resource_type, resource_list in resources.items():
for resource in resource_list:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
EKS resource manager class.
"""

from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Optional, Set
import subprocess
import json
import tempfile
import os

from aws_resource_management.managers.base import ResourceManager
from aws_resource_management.utils.config_utils import get_config
Expand Down Expand Up @@ -553,7 +557,7 @@ def discover_clusters(self, regions: List[str]) -> List[Dict[str, Any]]:

for region in regions:
try:
eks_client = self.get_boto3_client("eks", region)
eks_client = self.create_client("eks", region)
if not eks_client:
logger.warning(f"Could not create EKS client in {region}")
continue
Expand Down Expand Up @@ -616,3 +620,188 @@ def discover_clusters(self, regions: List[str]) -> List[Dict[str, Any]]:
f"Found a total of {len(all_clusters)} EKS clusters across all regions"
)
return all_clusters

def discover_cluster_images(
self, regions: List[str], accounts: Optional[Set[str]] = None
) -> List[Dict[str, Any]]:
"""
Discover all container images used in EKS clusters across regions.
Args:
regions: List of AWS regions
accounts: Set of AWS account IDs (for internal/external ECR detection)
Returns:
List of image metadata dicts
"""
all_images = []
clusters = self.discover_clusters(regions)
account_id = self.account_id
if accounts is None:
accounts = set([account_id])
for cluster in clusters:
cluster_name = cluster["name"]
region = cluster["region"]
try:
kubeconfig_path = self._get_kubeconfig(cluster_name, region, account_id)
images = self._get_image_metadata_from_cluster(
kubeconfig_path, account_id, cluster_name, region, accounts
)
all_images.extend(images)
os.remove(kubeconfig_path)
except Exception as e:
logger.warning(f"Error processing cluster {cluster_name} in {region}: {e}")
logger.info(f"Discovered {len(all_images)} images across all EKS clusters.")
return all_images

def _get_kubeconfig(self, cluster_name: str, region: str, account_id: str) -> str:
"""
Generate a kubeconfig file for the given EKS cluster using boto3 and awscli.
Returns path to the kubeconfig file.
"""
eks = self.create_client("eks", region)
cluster_info = eks.describe_cluster(name=cluster_name)["cluster"]
# Use awscli to get token (assumes awscli is configured for this account)
token = self._get_eks_token(cluster_name, region)
kubeconfig = {
"apiVersion": "v1",
"kind": "Config",
"clusters": [{
"cluster": {
"server": cluster_info["endpoint"],
"certificate-authority-data": cluster_info["certificateAuthority"]["data"]
},
"name": cluster_name
}],
"contexts": [{
"context": {
"cluster": cluster_name,
"user": "eks-user"
},
"name": f"{cluster_name}-context"
}],
"current-context": f"{cluster_name}-context",
"users": [{
"name": "eks-user",
"user": {"token": token}
}]
}
fd, path = tempfile.mkstemp(prefix=f"kubeconfig-{account_id}-{cluster_name}-", suffix=".yaml")
with os.fdopen(fd, "w") as f:
json.dump(kubeconfig, f)
return path

def _get_eks_token(self, cluster_name: str, region: str) -> str:
"""
Get EKS authentication token using awscli.
"""
result = subprocess.check_output([
"aws", "eks", "get-token",
"--cluster-name", cluster_name,
"--region", region
])
token_json = json.loads(result)
return token_json["status"]["token"]

def _get_image_metadata_from_cluster(
self,
kubeconfig_path: str,
account_id: str,
cluster_name: str,
region: str,
accounts: Set[str],
) -> List[Dict[str, Any]]:
"""
Use kubectl to get pod/container image info from a cluster.
"""
try:
output = subprocess.check_output([
"kubectl", "--kubeconfig", kubeconfig_path,
"get", "pods", "--all-namespaces", "-o", "json"
])
data = json.loads(output)
images = []
for item in data.get("items", []):
ns = item["metadata"]["namespace"]
pod = item["metadata"]["name"]
for status in item.get("status", {}).get("containerStatuses", []):
image = status.get("image")
image_id = status.get("imageID")
digest = None
if image_id and "@" in image_id:
digest = image_id.split("@", 1)[1]
parsed = self._parse_image_metadata(image, accounts)
images.append({
"account_id": account_id,
"region": region,
"cluster_name": cluster_name,
"namespace": ns,
"pod": pod,
"image": image,
"digest": digest,
**parsed
})
return images
except subprocess.CalledProcessError as e:
logger.warning(f"kubectl error: {e}")
return []

def _parse_image_metadata(self, image: str, accounts: Set[str]) -> Dict[str, Any]:
"""
Parse image string to extract registry, repo, tag, ECR info, etc.
"""
result = {
"source": "Unknown",
"registry": None,
"repo": None,
"tag": None,
"ecr_account": None,
"ecr_region": None,
"ecr_type": "N/A"
}
if not image:
return result
if image.startswith("public.ecr.aws/"):
try:
parts = image.split("/")
result["registry"] = "public.ecr.aws"
result["repo"] = "/".join(parts[1:-1] + [parts[-1].split(":")[0]])
result["tag"] = parts[-1].split(":")[1] if ":" in parts[-1] else "latest"
result["source"] = "AWS Public ECR"
result["ecr_type"] = "external"
except Exception:
result["source"] = "AWS Public ECR (parse error)"
return result
if ".dkr.ecr." in image:
try:
parts = image.split(".dkr.ecr.")
ecr_account = parts[0]
ecr_region = parts[1].split(".")[0]
registry_and_path = image.split(".amazonaws.com/")[1]
repo_tag = registry_and_path.split(":")
result["registry"] = image.split("/")[0]
result["repo"] = repo_tag[0]
result["tag"] = repo_tag[1] if len(repo_tag) > 1 else "latest"
result["source"] = "AWS Private ECR"
result["ecr_account"] = ecr_account
result["ecr_region"] = ecr_region
if ecr_account in accounts:
result["ecr_type"] = "internal"
else:
result["ecr_type"] = "external"
except Exception:
result["source"] = "ECR (parse error)"
else:
try:
parts = image.split("/")
result["registry"] = parts[0] if "." in parts[0] else "docker.io"
repo_tag = parts[-1].split(":")
result["repo"] = "/".join(parts[1:-1] + [repo_tag[0]])
result["tag"] = repo_tag[1] if len(repo_tag) > 1 else "latest"
result["source"] = "Public or Other"
result["ecr_type"] = "external"
except Exception:
result["source"] = "Unknown"
result["ecr_type"] = "external"
return result
Original file line number Diff line number Diff line change
Expand Up @@ -297,3 +297,14 @@ def get_registry() -> ResourceRegistry:
ResourceRegistry instance
"""
return registry

RESOURCE_REGISTRY = [
{
"value": "eks_images",
"help": (
"EKS Cluster Images: Inventory all container images running in EKS clusters "
"across accounts/regions. Useful for vulnerability, supply chain, and compliance analysis. "
"Outputs image, registry, tag, ECR info, and cluster/pod context."
),
},
]

0 comments on commit 27bea12

Please sign in to comment.