Kubernetes
Purpose
Discovers and analyzes Kubernetes resources inside EKS and self-managed clusters — nodes, pods, services, ingresses, Gateway API routes, RBAC, namespaces, and secret metadata. EKS clusters also include EKS-specific identity mappings and external OIDC provider configuration.
Prerequisites
- For EKS rows, the aws module is configured for the account containing your EKS cluster. EKS reuses the AWS IAM role to authenticate and build each cluster's kubeconfig.
- For EKS rows, you have permission to grant the
IAM role access to the cluster (either via EKS Access Entries or theSubImageScanRoleaws-authConfigMap). - For self-managed rows, the SubImage outpost Helm chart is installed in the cluster and the module row points at that outpost hostname. Self-managed clusters use the outpost ServiceAccount token; raw kubeconfig upload is not supported.
Add Clusters
In SubImage, go to Modules → Kubernetes and add one row per cluster.
- Choose EKS for AWS-managed clusters. Enter the cluster ARN in the format
arn:aws:eks:<region>:<account-id>:cluster/<cluster-name>. SubImage parses the account ID and region from the ARN. - For EKS clusters with private API endpoints, enter an outpost hostname on that row. The row-level hostname takes precedence over the module-level Tailscale outpost hostname.
- Choose Self-managed for on-premise or self-managed clusters reachable through a SubImage outpost. Enter a stable cluster name and the outpost hostname for that cluster.
Save the module and trigger a sync.
EKS AWS IAM Permissions
For EKS rows, SubImage uses the AWS scan role for two things:
- Build the kubeconfig via
eks:DescribeCluster. - Enrich EKS identity metadata via
eks:ListAccessEntries,eks:DescribeAccessEntry,eks:ListIdentityProviderConfigs,eks:DescribeIdentityProviderConfig.
If you deployed SubImageScanRole from the current AWS setup template, no changes are required — those permissions are included. If the role was deployed from an older StackSet or standalone stack, update that deployment from the current AWS setup template; editing the module row in SubImage does not update customer IAM. SecurityAudit already covers eks:DescribeCluster and eks:ListAccessEntries.
Only if you're extending an older scan role (that only has SecurityAudit attached), add this inline policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"eks:DescribeAccessEntry",
"eks:ListIdentityProviderConfigs",
"eks:DescribeIdentityProviderConfig"
],
"Resource": "*"
}
]
}EKS Cluster Access
Pick the method that matches your cluster's authentication mode.
Method 1 — EKS Access Entries (recommended, EKS 1.23+)
This is the preferred method for newer clusters.
Enable API access mode, if not already enabled:
aws eks update-cluster-config \ --name <cluster-name> \ --access-config authenticationMode=API_AND_CONFIG_MAPCreate an access entry for
:SubImageScanRoleaws eks create-access-entry \ --cluster-name <cluster-name> \ --principal-arn "arn:aws:iam::<YOUR_ACCOUNT_ID>:role/SubImageScanRole" \ --type STANDARD \ --username subimage-scanCreate the read-only ClusterRole:
kubectl apply -f - <<EOF apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: subimage-viewer rules: # Namespaces — list for namespace sync, get for kube-system cluster metadata - apiGroups: [""] resources: - namespaces verbs: ["get", "list"] # Core resources - apiGroups: [""] resources: - nodes - pods - services - serviceaccounts verbs: ["list"] # Secrets — Cartography stores metadata only, but list still returns payloads from the API server - apiGroups: [""] resources: - secrets verbs: ["list"] - apiGroups: ["rbac.authorization.k8s.io"] resources: - roles - rolebindings - clusterroles - clusterrolebindings verbs: ["list"] # Workload controllers — enable the WORKLOAD_PARENT chain from a pod up to its owning controller - apiGroups: ["apps"] resources: - deployments - replicasets - statefulsets - daemonsets verbs: ["list"] - apiGroups: ["batch"] resources: - jobs - cronjobs verbs: ["list"] - apiGroups: ["networking.k8s.io"] resources: - ingresses - networkpolicies verbs: ["list"] # Gateway API resources — optional, only useful when the Gateway API CRDs are installed - apiGroups: ["gateway.networking.k8s.io"] resources: - gateways - httproutes verbs: ["list"] # aws-auth identity mapping - apiGroups: [""] resources: - configmaps verbs: ["get"] EOFBind it:
kubectl create clusterrolebinding subimage-scan-viewer \ --clusterrole=subimage-viewer \ --user=subimage-scan
Terraform equivalent:
resource "aws_eks_access_entry" "subimage_scan" {
cluster_name = aws_eks_cluster.your_cluster.name
principal_arn = "arn:aws:iam::<YOUR_ACCOUNT_ID>:role/SubImageScanRole"
type = "STANDARD"
user_name = "subimage-scan"
}
resource "kubernetes_cluster_role" "subimage_viewer" {
metadata { name = "subimage-viewer" }
rule {
api_groups = [""]
resources = ["namespaces"]
verbs = ["get", "list"]
}
rule {
api_groups = [""]
resources = ["nodes", "pods", "services", "serviceaccounts"]
verbs = ["list"]
}
rule {
api_groups = [""]
resources = ["secrets"]
verbs = ["list"]
}
rule {
api_groups = ["rbac.authorization.k8s.io"]
resources = ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
verbs = ["list"]
}
rule {
api_groups = ["apps"]
resources = ["deployments", "replicasets", "statefulsets", "daemonsets"]
verbs = ["list"]
}
rule {
api_groups = ["batch"]
resources = ["jobs", "cronjobs"]
verbs = ["list"]
}
rule {
api_groups = ["networking.k8s.io"]
resources = ["ingresses", "networkpolicies"]
verbs = ["list"]
}
rule {
api_groups = ["gateway.networking.k8s.io"]
resources = ["gateways", "httproutes"]
verbs = ["list"]
}
rule {
api_groups = [""]
resources = ["configmaps"]
verbs = ["get"]
}
}
resource "kubernetes_cluster_role_binding" "subimage_scan_viewer" {
metadata { name = "subimage-scan-viewer" }
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "ClusterRole"
name = kubernetes_cluster_role.subimage_viewer.metadata[0].name
}
subject {
kind = "User"
name = "subimage-scan"
api_group = "rbac.authorization.k8s.io"
}
}Method 2 — aws-auth ConfigMap (traditional)
For clusters still on the ConfigMap-based access model.
Edit the
aws-authConfigMap inkube-system:kubectl edit configmap aws-auth -n kube-systemAdd the scan role to
mapRoles:apiVersion: v1 kind: ConfigMap metadata: name: aws-auth namespace: kube-system data: mapRoles: | # ... existing mappings ... - rolearn: arn:aws:iam::<YOUR_ACCOUNT_ID>:role/SubImageScanRoleusername: subimage-scanApply the same ClusterRole and ClusterRoleBinding shown in Method 1.
Terraform equivalent for the ConfigMap part:
resource "kubernetes_config_map" "aws_auth" {
metadata {
name = "aws-auth"
namespace = "kube-system"
}
data = {
mapRoles = yamlencode([
{
rolearn = "arn:aws:iam::<YOUR_ACCOUNT_ID>:role/SubImageScanRole"
username = "subimage-scan"
}
])
}
}Self-Managed Cluster Access
Self-managed rows do not use AWS IAM auth, EKS access entries, or the aws-auth ConfigMap. SubImage connects through the outpost and uses the outpost pod's Kubernetes ServiceAccount token.
- Install the SubImage outpost Helm chart inside the cluster you want to scan.
- Leave the outpost
proxyTargetset tohttps://kubernetes.default.svcwhen the outpost is scanning the same cluster where it runs. - Confirm the outpost ServiceAccount has read-only RBAC for Kubernetes resources. The Helm chart includes the expected RBAC; use the chart README for advanced customization.
- In Modules → Kubernetes, add a Self-managed row with:
- Cluster name: any stable name that identifies this cluster in SubImage, for example
production-us-east. - Outpost: the Tailscale hostname for the outpost, for example
.<TENANT_ID>-cluster-name-outpost
- Cluster name: any stable name that identifies this cluster in SubImage, for example
Troubleshooting
User "system:anonymous" cannot get resource...or Kubernetes401 Unauthorized— authentication problem. For EKS rows, verify the cluster's auth mode (aws eks describe-cluster --name <cluster-name> --query 'cluster.accessConfig'), the role ARN in your config, and that the access entry (oraws-authrow) references the same username as the ClusterRoleBinding (subimage-scan). For self-managed rows, verify the outpost is proxying to the Kubernetes API and the outpost ServiceAccount token is mounted.The config profile (tenant-account) could not be found— for EKS rows, the AWS account ID in the cluster ARN does not match any account in the AWS integration. Ensure the AWS module has synced successfully and includes the account.- Kubernetes
403 Forbidden— SubImage authenticated but RBAC blocks the request. Verify the ClusterRoleBinding exists and thesubimage-viewerClusterRole covers all resources listed above. - Outpost sync reaches
404 Not Found— the outpost is likely proxying to something other than the Kubernetes API server. For in-cluster Helm deployments, usehttps://kubernetes.default.svcas the proxy target. - Sync reports "Cluster API Server Unreachable": the outpost is running and reachable, but connecting to the Kubernetes API server timed out or was refused, so it returned its own error instead of the cluster's response. Check that no NetworkPolicy or egress rule blocks the outpost pod from reaching the API server, that cluster DNS resolves
kubernetes.default.svc, and that the API server is healthy and not overloaded. If the cluster is simply slow, raisePROXY_CONNECT_TIMEOUTorPROXY_READ_TIMEOUTon the outpost. The outpost log, available from the outposts settings page, records the underlying error. - Sync reports "Outpost Proxy Error": the outpost returned an error of its own rather than the cluster's response. Verify that the proxy target is a valid Kubernetes API URL (
https://kubernetes.default.svcfor in-cluster Helm installs). On outposts older than 1.2.0 this error cannot distinguish an unreachable API server from a failure inside the outpost; upgrade to outpost 1.2.0 or later (chart 1.4.0), which reports the cause. - AWS
AccessDeniedbefore the request reaches Kubernetes —is missing one of the EKS IAM permissions listed at the top. This is separate from Kubernetes RBAC.SubImageScanRole
Only read access is granted or required. SubImage stores secret metadata only (name, type) — but note that Kubernetes list on secrets can still return payloads from the API server.