AWS Kali attack box support - #418
Conversation
Provision an optional tagged Kali host on AWS so scoring and SSM workflows can discover it without inventory or public ingress. Bootstrap the SSM agent and required tooling from the official Marketplace image.
There was a problem hiding this comment.
Pull request overview
This PR adds optional AWS Kali “attack box” provisioning and updates the CLI/provider discovery logic so the attack box (and other AWS instances) can be found via tags instead of relying solely on the Ansible inventory.
Changes:
- Introduces a new Terraform module and Terragrunt units for provisioning an optional Kali attack box on AWS, bootstrapped for SSM access and scoring tooling.
- Updates AWS discovery to scope instances by
Project+Environmenttags and preserves instance tags for role-based selection. - Enhances CLI workflows:
infra --with-kali,doctorchecks for Session Manager plugin,ssm connectcan resolve out-of-inventory hosts, and scoring can auto-discover the attack box.
Reviewed changes
Copilot reviewed 29 out of 29 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| modules/terraform-aws-kali/versions.tf | Defines Terraform/provider constraints for the new AWS Kali module. |
| modules/terraform-aws-kali/variables.tf | Adds inputs for attack box deployment, AMI selection, and tagging. |
| modules/terraform-aws-kali/user_data.sh.tpl | Bootstraps SSM agent and scoring tools on first boot. |
| modules/terraform-aws-kali/README.md | Documents module behavior, tagging, and public IP guidance. |
| modules/terraform-aws-kali/outputs.tf | Exposes instance identifiers, IPs, SG, and selected AMI. |
| modules/terraform-aws-kali/main.tf | Composes the instance-factory module to deploy/tag the Kali instance. |
| modules/terraform-aws-instance-factory/main.tf | Allows public IP association independent of SSM enablement. |
| infra/goad-deployment/test/us-east-2/kali/terragrunt.hcl | Adds optional AWS Kali unit (test env) gated by env var. |
| infra/goad-deployment/test/env.hcl | Adds kali_instance_type env setting (test). |
| infra/goad-deployment/staging/us-west-1/kali/terragrunt.hcl | Adds optional AWS Kali unit (staging env) gated by env var. |
| infra/goad-deployment/staging/env.hcl | Adds kali_instance_type env setting (staging). |
| docs/scoring.md | Documents auto-discovery behavior for the AWS attack box. |
| docs/mkdocs/docs/providers/aws.md | Documents Session Manager plugin + Marketplace subscription + connect flow. |
| docs/mkdocs/docs/cli-reference.md | Documents dreadgoad infra --with-kali. |
| cli/internal/provider/provider.go | Adds tag support to provider instances + role-based selection helper. |
| cli/internal/provider/provider_test.go | Tests role-based selection helper. |
| cli/internal/provider/factory.go | Extends constructor options with AWS profile support. |
| cli/internal/doctor/checks.go | Adds Session Manager plugin check for AWS. |
| cli/internal/azure/provider.go | Preserves instance tags when converting to provider instances. |
| cli/internal/aws/provider.go | Plumbs AWS profile into client creation; preserves tags. |
| cli/internal/aws/provider_test.go | Tests that AWS provider conversion preserves tags. |
| cli/internal/aws/ec2.go | Switches discovery filters to Project + Environment; captures tags. |
| cli/internal/aws/ec2_test.go | Tests the new discovery filter behavior. |
| cli/cmd/ssm.go | Adds out-of-inventory resolution + attack-box alias; excludes attack box from PowerShell fan-out. |
| cli/cmd/ssm_test.go | Tests ssm connect resolution and attack-box exclusion. |
| cli/cmd/score.go | Adds AWS attack-box auto-discovery for live verification; refactors region/profile resolution. |
| cli/cmd/score_test.go | Tests AWS region/profile resolution behavior. |
| cli/cmd/infra_cmd.go | Adds AWS --with-kali wiring and ensures destroy cleans up existing Kali unit. |
| cli/cmd/env_cmd.go | Adds kali_instance_type to generated env.hcl templates. |
Suppressed comments (1)
cli/cmd/ssm.go:218
resolveSSMHostcurrently drops errors fromFindInstanceByHostnameand from the role-basedDiscoverInstanceslookup. If AWS discovery fails (e.g., credentials/permissions), the user will get a misleading "host not found" message instead of the real API error. Also, for theattack-boxalias it’s cleaner to skip the hostname lookup entirely and go straight to role discovery.
if inst, err := prov.FindInstanceByHostname(ctx, env, hostName); err == nil && inst.ID != "" {
return inst, nil
}
// Also accept the stable role name so callers do not need to know whether
// the attack box resource is named "kali", "attacker", or something else.
if strings.EqualFold(hostName, "attack-box") || strings.EqualFold(hostName, "attackbox") {
instances, err := prov.DiscoverInstances(ctx, env)
if err == nil {
if inst := provider.FindInstanceByRole(instances, "AttackBox"); inst != nil {
return inst, nil
}
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
AWS attack boxes use Systems Manager for access, so exposing a public-IP option adds unnecessary scope and weakens the intended private deployment model.
Preserve inventory fallback while reporting parse failures, and return AWS discovery errors instead of masking credential or permission problems as missing hosts.
|
Also addressed Copilot’s additional review-body finding in |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 28 changed files in this pull request and generated no new comments.
Suppressed comments (2)
cli/cmd/ssm.go:228
- The discovery fallback can return a stopped instance because the provider's
FindInstanceByHostnamecontract includes instances in any state.ssm connectwill then pass that ID tostart-session, which can only connect to a running managed instance. Reject a non-running discovered target here so users get an actionable error instead of a failed Session Manager invocation.
inst, err := prov.FindInstanceByHostname(ctx, env, hostName)
if err != nil {
return nil, fmt.Errorf("discover AWS host %q: %w", hostName, err)
}
if inst != nil && inst.ID != "" {
return inst, nil
cli/internal/aws/ec2.go:129
- This breaks the documented external-infrastructure discovery contract:
docs/mkdocs/docs/providers/external-infrastructure.md:34-56tells users that a matchingNametag is sufficient, but these filters now silently exclude those instances unless they also haveProject=DreadGOADandEnvironment=<env>. Update that guide and migration requirements as part of this change (or retain a compatibility path), otherwiseinventory syncand the new out-of-inventory lookup stop working for configurations that satisfy the current documentation.
func discoveryFilters(env string, states []string) []types.Filter {
filters := []types.Filter{
{Name: Ptr("tag:Project"), Values: []string{"DreadGOAD"}},
{Name: Ptr("tag:Environment"), Values: []string{env}},
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 28 changed files in this pull request and generated no new comments.
Suppressed comments (1)
cli/cmd/ssm.go:185
- The fallback discovery uses
prov, which was constructed fromcfg.ResolveRegion()before the inventory is parsed, while the session later usesResolveRegionWithInventory(inv). When the inventory's deployed region differs from stale/fallback config, inventory hosts still connect correctly, butattack-boxand other out-of-inventory names query EC2 in the wrong region and report that the host is missing. Resolve the authoritative region before constructing the AWS provider, and use that same region for both discovery andStartInteractiveShell.
target, err := resolveSSMHost(ctx, prov, cfg.Env, inv, args[0])
Track the cloud-init template as executable and commit the generated Terraform module documentation expected by the repository hooks.
Match the repository shfmt configuration so pre-commit does not rewrite the cloud-init template in CI.
|
Confirmed and fixed in |
|
Confirmed and fixed the three remaining Copilot review-body findings in |
Brings in 32 commits, including #423 CLI deployment recovery hardening, which independently implemented several findings from the stand-up audit: --from-playbook resume, pointer-based retry overrides so --max-retries 0 means zero, a structural flag-forwarding test for the provision command, and a doctor failure message that no longer advertises --skip-doctor. Conflicts and how they were settled: up.go -- main rewrote the resume path around provisionFailure and upResumeOptions; kept all of it, and re-applied the Azure module fix (d4d23a5) on top, since main's runUpInfraApply still omitted --with-bastion/--with-controller and would have reintroduced the break. up_test.go -- both sides added the file. No function names collide, so this is the union: main's 11 provision/resume tests plus the 6 infra and Azure-module tests, on a merged import block. provider.go, aws/ec2.go, aws/provider.go, azure/provider.go -- both sides added fields to Instance. Kept Account/Group and main's Tags. main extracted discoverInstances, so the Account population moved into that helper rather than staying in the old inline body. .gitignore -- kept the console build artifacts and the ADCS zip entries; main tracks the template sources but not the generated archives. #418 gave Kali an AWS path, so --with-kali is no longer Azure-only and the up flag help drops that qualifier. Co-Authored-By: Claude <noreply@anthropic.com>
Added
dreadgoad infra plan/apply/destroy --with-kali.dreadgoad ssm connect attack-box.Changed
ProjectandEnvironmenttags and preserves provider tags.dreadgoad doctornow checks for the AWS Session Manager plugin.Fixed
Notes