EKS Reconnaissance — DevOps Agent Port
Overview
This skill discovers and documents the current state of an EKS cluster and its environment. It connects via AWS control-plane APIs and the Kubernetes API, runs detection across up to 11 modules, and produces a facts-only report: a markdown fact report (primary) plus a secondary YAML facts snapshot for inspection/export.
Recon answers the question: "What is this cluster, right now?" It reports facts only — it never scores, rates, plans, or recommends. Reporting a fact that also matters to another skill (upgrade, security, cost, design) is expected and fine; the dividing line is discovery-of-facts (here) vs. verdict/score/artifact (route elsewhere).
Execution model — fully autonomous. This skill runs autonomously with no interactive prompts. It proceeds through discovery and detection without pausing for user input. When the target cluster is ambiguous (multiple clusters, none named), it assesses all discovered clusters. When a non-recoverable error occurs (API permission failure, no clusters found), it logs the error in the report and terminates per the Step 0 decision table.
Prerequisites
Required IAM Permissions (Agent Space Role)
A ready-to-use IAM policy document is available at references/iam-policy.json — attach it directly to your Agent Space execution role. It grants read-only AWS control-plane access (EKS/EC2/ELB/IAM/Logs Describe/List/Get). It intentionally does not grant eks:AccessKubernetesApi — Kubernetes-API authentication is handled by the access entry below, not by IAM.
| Service | Actions (read-only) | Purpose |
|---|---|---|
| EKS | ListClusters, DescribeCluster, ListNodegroups, DescribeNodegroup, ListAddons, DescribeAddon, DescribeAddonVersions, ListFargateProfiles, DescribeFargateProfile, ListAccessEntries, ListPodIdentityAssociations, DescribePodIdentityAssociation, ListInsights, DescribeInsight, ListIdentityProviderConfigs | Cluster config, compute inventory, add-ons, access model, EKS insights |
| EC2 | DescribeInstances, DescribeVpcs, DescribeSubnets, DescribeSecurityGroups, DescribeNetworkInterfaces, DescribeLaunchTemplates | Nodes, VPC/subnet topology, security groups, ENIs |
| ELB | elasticloadbalancing:Describe* | Load balancer inventory (ALB/NLB), target groups |
| IAM | ListRoles, GetRole, ListOpenIDConnectProviders, ListPolicies, GetPolicyVersion | IRSA/Pod Identity roles, OIDC providers |
| CloudWatch Logs | logs:DescribeLogGroups | Control-plane log group presence (observability facts) |
Kubernetes API Access (via Agent Space Access Entry)
Kubernetes-API facts (nodes, CRDs, workloads, RBAC, StorageClasses, etc.) are read through an EKS Access Entry that binds the Agent Space role to the AWS-managed AmazonAIOpsAssistantPolicy cluster-access policy at cluster scope. This is provisioned by devops-agent/setup.sh (or manually — see the project README "EKS Access Setup").
- The cluster's
authenticationModemust includeAPI(i.e.APIorAPI_AND_CONFIG_MAP). ACONFIG_MAP-only cluster cannot be reached via the access entry. - The access entry (not an IAM action) provides the K8s-API authentication; the
AmazonAIOpsAssistantPolicyprovides the authorization (RBAC). - What
AmazonAIOpsAssistantPolicyactually authorizes (read-only get/list): built-in API groups only — core (pods,services,nodes,namespaces,events,persistentvolumes,persistentvolumeclaims,configmaps),apps(deployments/replicasets/statefulsets/daemonsets),batch(jobs/cronjobs),events.k8s.io,networking.k8s.io(ingresses/ingressclasses),storage.k8s.io(storageclasses), andmetrics.k8s.io. It grants NO CustomResourceDefinition groups (and notapiextensions.k8s.io). - Consequence for CRD-based facts: the managed policy alone does not authorize reading Karpenter (
karpenter.sh,karpenter.k8s.aws), Auto ModeNodeClass(eks.amazonaws.com),TargetGroupBinding(elbv2.k8s.aws/eks.amazonaws.com),VolumeSnapshotClass(snapshot.storage.k8s.io),VerticalPodAutoscaler(autoscaling.k8s.io), Kyverno/Gatekeeper, Calico/Cilium, service-mesh, ArgoCD/Flux, Crossplane/ACK, or Velero CRDs. Under a plainAmazonAIOpsAssistantPolicy-only association those reads return403 Forbidden. To capture CRD-based facts, bind the Agent Space role to a supplementary read-only ClusterRole grantingget/liston the relevant CRD groups (or associate a broader access policy). Absent that, the CRD-dependent sub-facts are reported asunconfirmedper the hedge below — never asfalse/count: 0.
Availability hedge. When the access entry is absent (or
authenticationModeexcludesAPI), the skill degrades gracefully to AWS-control-plane-only facts — it still reports everything reachable via the EKS/EC2/ELB/IAM APIs. Each K8s-API-dependent module or sub-fact that cannot be read — whether because the access entry is missing,authenticationModeexcludesAPI, or the RBAC (see above) does not authorize that resource (e.g. a CRD group) — is recorded asunavailable/unconfirmedin the report's Coverage section, never asfalse/count: 0.
When to Use
Activate when the goal involves:
- Discovering or documenting an EKS cluster's current state — "what am I running?", "tell me about my setup"
- Inventory before an upgrade, migration, or architecture change
- Targeted current-state questions — "what version am I on?", "am I using Karpenter?"
- Supplying raw cluster facts as input to another workflow
Out of scope — route elsewhere (these produce judgments/artifacts, not facts):
- Upgrade readiness scoring / deprecated-API blocking →
eks-upgrade-check. Recon discovers what version you're on; it does not assess whether you are ready to move. - Operational audits with GREEN/AMBER/RED maturity ratings →
eks-operation-review. Recon inventories what exists; it does not rate maturity. - Architecture design documents or Mermaid diagrams →
eks-design. Recon discovers current state; it does not produce design artifacts. - Creating or modifying cluster resources (this skill is strictly read-only).
Reconnaissance Workflow
Error output format (used by the Step 0 hard-stops):
## Recon Error — <one-line reason> **Condition:** <which check failed> **What was found:** <observed state> **Recommendation:** <remediation guidance for next run>
Optional prior report: the caller may supply an earlier recon YAML artifact as
accessible report content or an attachment. Load
report-comparison.md when supplied. Use the
artifact's attachment filename, asset name, or inline content as its source label;
read it before generating outputs and do not assume local file or
shell access. For multi-cluster discovery, compare only the cluster matching the
prior artifact's ARN and explicitly mark other clusters as having no matching prior.
If no discovered cluster matches, report the comparison as unavailable. Without a
prior report, omit the Changes since section.
Step 0: Pre-flight — Cluster Discovery and Validation
Action 1 — Discover clusters. Use the EKS ListClusters API to discover available clusters in the target region.
| Condition | Action |
|---|---|
| API call fails (auth/permission error) | Abort with error — log "Cannot access EKS. The agent role requires eks:ListClusters for the configured region." and terminate. |
| Zero clusters returned | Abort with error — log "No EKS clusters found in this region." and terminate. |
| Exactly one cluster found, none named in request | Proceed — state which cluster was auto-selected. |
| Multiple clusters found, one named in request | Proceed — use the named cluster. |
| Multiple clusters found, none named in request | Proceed — recon all discovered clusters. Note in the report that no specific cluster was targeted, so all clusters in the region are included. |
Action 2 — Describe the selected cluster. Use DescribeCluster. Extract name, Kubernetes version, platform version, region, status, account ID, authenticationMode. This populates the shared cluster: and cluster_detail: blocks (references/cluster-basics.md).
| Cluster Status | Action |
|---|---|
ACTIVE | Proceed |
CREATING / UPDATING / DELETING | Skip cluster — log "Cluster <name> is in <status> state; skipping." If it is the only cluster, terminate with error report. |
FAILED | Skip cluster — log "Cluster <name> is in FAILED state." If it is the only cluster, terminate with error report. |
Action 3 — Probe Kubernetes API reachability. Attempt one lightweight K8s-API read (e.g. list nodes). If it fails (access entry absent, authenticationMode excludes API, or 401/403), do not abort — set a k8s_api_available: false flag, continue with AWS-control-plane-only modules, and record every K8s-dependent module/sub-fact as unavailable/unconfirmed in Coverage.
Action 4 — Load references/cluster-basics.md first, then proceed to module selection. cluster-basics is always loaded first by every module — it provides the shared cluster context all other modules depend on.
Step 1: Select and Run Modules
Determine which modules to run from user intent using the routing table below. For a targeted query, load only the matching reference(s). For full recon, load all 11 references. Before running a module, read its reference file — the references carry detection order, edge cases, and the canonical output schema; skipping them produces shallow results.
Each reference describes detection declaratively as capability blocks (AWS API calls, and "Via Kubernetes API" blocks for K8s resources). There is no Agent tool and no subagents in this environment — module isolation is achieved by loading one reference at a time, not by spawning subagents.
Step 2: Generate the Report
Produce both artifacts (see Report Output below). The markdown fact report is the primary deliverable; the YAML is a secondary facts snapshot. When a prior report was supplied, append the comparison defined in report-comparison.md.
How to Use the References
references/cluster-basics.md is always loaded first. Load additional references only for the modules the request needs.
| Intent / when to use | Reference file |
|---|---|
| Always first — name, region, version, platform version, endpoint, support type, zonal shift, encryption config, health | cluster-basics.md |
| Karpenter, nodes, scaling, Auto Mode, node groups, Fargate, self-managed | compute.md |
| VPC, ingress, CNI, service mesh, load balancer, connectivity | networking.md |
| IAM, IRSA, Pod Identity, RBAC, policies, encryption, secrets, webhooks | security.md |
| EKS-managed add-ons, Helm releases, plugins, "what's installed?" | addons.md |
| Logging, metrics, monitoring, Container Insights, Prometheus | observability.md |
| Deployments, pods, services, ingresses, "what's running?" | workloads.md |
| PVCs, EBS, EFS, StorageClasses, CSI drivers, volumes, snapshots, backup tooling | storage.md |
| Terraform, CloudFormation, CDK, eksctl, Pulumi, Crossplane, "how is it managed?" | iac.md |
| GitHub Actions, GitLab CI, Jenkins, ArgoCD, Flux, GitOps, pipelines | cicd.md |
| EKS-reported upgrade-readiness & configuration insights (raw findings) | cluster-insights.md |
For full recon, load all rows. For a targeted query (e.g. "am I using Karpenter?"), load only cluster-basics.md + the matching row (compute.md).
Report Output
Produce two artifacts. The markdown fact report is the primary deliverable (rendered inline to the user); the YAML is a secondary facts snapshot for inspection/export. The agent generates both directly — no external conversion tools or scripts.
Facts only. Both artifacts report what the cluster is. Never emit a verdict, score, readiness rating, or recommendation. A "Notable facts" line states an observation ("2 subnets have < 16 free IPs"); it never says "you should" or "this is a risk".
Use one actual UTC clock reading for both filenames, the Markdown _generated
header (labelled UTC), and report.generated_at. No caller prefix is supported.
If named destinations can be checked, a collision writes neither artifact and
returns an error without prompting; current facts remain visible inline. If the
service cannot inspect destinations, do not claim collision checks were performed.
Reports can contain cluster ARNs, account/resource identifiers and infrastructure details. Review and redact before sharing.
Markdown fact report (primary)
- Filename:
EKS-Recon-{cluster}-{YYYY-MM-DD}-{HHMM}.md
Render this structure, including only modules that actually ran:
markdown# EKS Recon — <cluster> (<region>) _generated <timestamp> · source: AWS API + K8s API · modules: N/N_ ## Cluster summary | Fact | Value | |------|-------| | version / platform | 1.31 / eks.5 | | support type | STANDARD | | status / created | ACTIVE / 2024-09-10 | | authentication mode | API_AND_CONFIG_MAP | | compute strategy | Karpenter + 1 MNG | | IaC | Terraform (high confidence) | | CI/CD | GitHub Actions + ArgoCD | ## Per-module facts ### Compute <small fact table per module — one H3 per RUN module> ### Networking ... ## Notable facts <flat bullets: co-occurring or absent facts, stated neutrally, ZERO verdicts> - authentication_mode = API_AND_CONFIG_MAP; aws-auth ConfigMap present - compute.strategy = Karpenter; security.iam_for_pods.irsa.detected = false - 2 of 4 cluster subnets report < 16 available IPs ## Coverage <modules that could not run + reason, AND sub-facts that ran but could not be confirmed> - storage: unavailable (reason: K8s API access entry absent — AWS-API-only mode) - addons.helm_releases: unconfirmed (reason: Helm release Secrets not readable) — reported as `unconfirmed`, not `count: 0`
Keep every absence claim within the successfully inspected scope. State that scope with the claim, including in summaries and Notable facts; Coverage caveats do not qualify broader wording elsewhere.
Machine YAML artifact
- Filename:
EKS-Recon-{cluster}-{YYYY-MM-DD}-{HHMM}.yaml
The YAML is a secondary, point-in-time facts snapshot, not a versioned API. Workflows may use its facts as context; that intended use does not establish a versioned schema hand-off. Preserve the canonical reference shapes for inspection, export and existing workflow guidance.
Assembly rules (inline and subagent execution):
- Use cluster-basics for the shared
cluster:block; deduplicate matching module blocks. For the same target, preserve module facts and record differing values and their sources in Markdown Coverage rather than silently overwriting them. Compare identity using target context (such as cluster ARN); a cosmetic endpoint difference such as a trailing/is not another cluster. If a module identifies another target cluster, retain its key as{unavailable: true, reason: "cluster context mismatch"}. - On full recon, collect
cluster_detail:directly from cluster-basics, which has no subagent. Targeted queries may omit unrelated detail. - If a selected module returns no usable result, retain its key with
unavailable: trueand a shortreasonin either mode. Partial reads retain known facts alongside Coverage gaps. Unselected modules are out of scope. - For partial modules, retain observed facts and every schema-defined object/key,
even when all its values are unreadable: use
ingresses: {count: null, list: null}, notingresses: null. Set unreadable leaf values tonulland put reasons in Markdown Coverage. Use another representation only when the module reference explicitly prescribes it for that field and failure condition (for example, Helm ornode_subnets); do not invent generic{unconfirmed, reason}wrappers. Carry each incomplete/denied read intoreport.unconfirmed_paths, even when its canonical value isnull. For partial inventories, record the entire inventory path. Collect gaps alongside facts from every module result; never infer complete coverage from a successful module response. A GitOpsstatus: unconfirmedrecordscicd.gitops, not just the status leaf.
Assemble one top-level key per module, each emitted verbatim in the canonical shape defined by that module's references/<module>.md "## Output Schema", plus the shared cluster: and cluster_detail: blocks (from references/cluster-basics.md). Unavailable modules become <module>: {unavailable: true, reason: "<short reason>"}.
Include a report metadata block alongside the canonical module blocks in every
new YAML artifact. Capture generated_at from the actual UTC clock once for this
report (reuse the filename's clock reading where applicable). Set schema_revision
to eks-recon/2026-10-31, identifying the fact definitions used here, not a promised
versioned API. Update this marker when fact types, value sets or meanings change,
and document affected comparison boundaries in the comparison reference.
unconfirmed_paths lists canonical dot-separated field paths whose reads were incomplete or denied;
list the enclosing inventory path if only part of an inventory was collected.
Use an empty list only when no collected sub-facts are unconfirmed. Omitted modules
and explicit unavailable/unconfirmed markers remain authoritative coverage gaps.
This metadata is not a recon module and is excluded from module counts and deltas.
yamlreport: schema_revision: "eks-recon/2026-10-31" generated_at: "2026-10-29T08:30:00Z" # actual report time, not this example unconfirmed_paths: [] # e.g. [security.authentication.access_entries]
The reference Output Schemas are the single source of truth for each module's shape — do not hand-copy a schema. The fragment below shows only the envelope; the authoritative per-module shape lives in each reference.
yaml# EKS Reconnaissance Report — illustrative envelope only. # Canonical shape per module = references/<module>.md "## Output Schema". report: # metadata defined above; not a module schema_revision: "eks-recon/2026-10-31" generated_at: "2026-10-29T08:30:00Z" # replace with actual report time unconfirmed_paths: [] cluster: # shape: references/cluster-basics.md "## Shared Cluster Block" name: my-cluster region: us-west-2 version: "1.31" platform_version: eks.5 status: ACTIVE cluster_detail: # shape: references/cluster-basics.md "## Cluster Detail (full recon)" upgrade_policy: {support_type: STANDARD} compute: # shape: references/compute.md "## Output Schema" strategy: Karpenter networking: { ... } # shape: references/networking.md storage: unavailable: true reason: "K8s API access entry absent — AWS-API-only mode"
Facts-Only Guardrails
- Report facts, never judgments. No scores, ratings, readiness verdicts, risk labels, or recommendations. "Notable facts" states observations; it never says "you should" or "this is a risk".
- Do NOT hardcode or guess cluster names. Always discover clusters via ListClusters first (Step 0).
- Do NOT retry a failed API call more than once. If it fails twice, record the gap in Coverage and continue.
- Always read the relevant reference before running a module. References carry detection order, edge cases, and the canonical output schema.
- Distinguish absence from unconfirmed. A fact that could not be checked is
unconfirmed/unavailable(with a reason), nevercount: 0orfalse. - Never omit a schema key. Use
nullwhere a fact was not detected — preserve the full canonical shape from each reference.
This skill is provided as sample code for educational and demonstration purposes only. Findings are point-in-time facts and should be validated before acting on them. See the project's README and LICENSE for full terms.


