CtrlK
BlogDocsLog inGet started
Tessl Logo

aws-tenant-standup

Stand up or rebuild an AWS Shifter tenant end to end (teardown, bootstrap, secrets, image bakes, deploy via the real deploy.yml dispatch, health check, base-range smoke, and a POLARIS range walkthrough). AWS only. Use when asked to rebuild, redeploy, or stand up an AWS tenant such as proof or aws-dev from a clean state.

67

Quality

81%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

The canonical home for this skill is aws-tenant-standup in PaloAltoNetworks/shifter

SKILL.md
Quality
Evals
Security

AWS tenant standup and rebuild (AWS only)

This skill drives a full AWS Shifter tenant from a clean (or dirty) account to a healthy, verified tenant with a live POLARIS range. It exists so a fresh session does not repeat the deploy-docs archaeology and does not re-hit the fresh-environment gotchas already fixed on dev. GCP is out of scope.

Ground rules

  • All Shifter AWS resources live in us-east-2 unless the operator says otherwise.
  • The canonical GitHub repository is Brad-Edwards/shifter (see AGENTS.md).
  • Never add AI or Claude attribution to any commit, PR, issue, or file.
  • Do not merge branches. Merges are operator-driven. Fixes land as PRs to dev.
  • Run long-running Terraform and deploys in the background. A foreground tool timeout can kill a Terraform provider plugin mid-apply and corrupt progress.
  • Do not stack pushes to an environment branch while its CI or deploy is in flight.
  • Verify with the CLI before stating a fact. Do not fabricate on a failed step.
  • Fix root causes and PR them to dev. Do not jury-rig around a real bug.

Parameters

Read these from the invocation arguments if supplied (natural language or key=value pairs). Ask the operator for any that are missing before doing anything destructive.

  • profile: the AWS CLI profile to use (for example proof or aws-dev).
  • region: the AWS region. Default us-east-2; confirm rather than assume.
  • mode: one of
    • teardown: rip out everything, including AMIs, and re-bake fresh images.
    • preserve-amis: tear down the infrastructure but keep existing AMIs and skip the bake phase (faster restart when the images are still good).

Derive the environment from the profile and confirm it with the operator: proof maps to Terraform env proof and deploy input aws-proof; aws-dev or dev maps to Terraform env dev and deploy input aws-dev. The deploy.yml workflow_dispatch environment options are aws-dev and aws-proof.

Authoritative sources (read these first)

Read these before acting. They are the real process. Do not follow docs/technical/platform_infrastructure/manual-deployment.md as the primary path; it is a legacy fallback, not the deploy pipeline.

  • docs/dev/deploy-secrets.md: the single authoritative checklist of every secret a fresh AWS environment needs, plus the fresh-account bootstrap order.
  • docs/dev/aws-teardown-runbook.md: the teardown procedure and the tag-scoped account-recovery sweep.
  • docs/technical/dev/secrets.md: where runtime secrets live and how tfvars reach Terraform (real values come from TF_VARS_<ENV>_* GitHub secrets rendered to a gitignored local.auto.tfvars, not committed tfvars).
  • .github/workflows/deploy.yml: the real deploy path (workflow_dispatch with an environment input; the branch you dispatch from is the code that deploys).
  • .github/workflows/packer.yml: the image bake pipeline.

Phase 0: preflight

  1. Confirm the account identity and record the account id: aws --profile <profile> sts get-caller-identity.
  2. Confirm the local overlay exists for each stack: platform/terraform/environments/<env>/local.auto.tfvars (core), .../<env>/range/local.auto.tfvars, and .../<env>/portal/local.auto.tfvars. These are gitignored real values. If any is missing, the values must already be in the matching TF_VARS_<ENV>_* GitHub secret. Keep the overlay and the secret in step with scripts/sync-deploy-secrets.sh (it never prints contents). You may read and edit the local overlay; never paste its contents into chat, a PR, a commit, or an issue.
  3. Run the deploy preflight to confirm required secrets are present: python3 scripts/bootstrap/preflight.py (see deploy-secrets.md for the required set).

Phase 1: teardown

Follow docs/dev/aws-teardown-runbook.md. Key points learned in practice:

  • Destroy all three Terraform roots: portal, then range, then core (platform/terraform/environments/<env>/{portal,range,}). The portal root declares cloud_provider; pass -var="cloud_provider=aws" there. The range and core roots do not declare it, and Terraform 1.15+ errors on an undeclared -var, so do not pass it to those.
  • Empty versioned buckets (logs, ECR, Terraform state) before destroy, including all object versions and delete markers, or the destroy fails on non-empty buckets.
  • The account-recovery sweep is tag-scoped (Project=shifter plus the environment tag). Run it to catch anything Terraform missed.
  • If mode=preserve-amis: before teardown, capture the AMI ids you will reuse (ec2_ami_id, ctfd_ami_id in the portal overlay; the /shifter/ami/* SSM parameters; polaris-vm and polaris-dc; shifter/packer/dc-amis.json). Do not deregister those AMIs, and skip Phase 3.

Phase 2: bootstrap and secrets

  1. Bootstrap the fresh account bits (OIDC role, state bucket, global IAM): python3 scripts/bootstrap/deploy.py bootstrap --env <env>. This sets AWS_ROLE_ARN_<ENV> and TF_INFRA_STATE_BUCKET_<ENV>.
  2. Ensure the runners are registered per the runbook.
  3. Ensure the deploy secrets exist. Assume the same starting secrets as before. Push local overlays with scripts/sync-deploy-secrets.sh --env <env> (add --stack config --shifter-config <path> for the SHIFTER_CONFIG_* payload).

Phase 3: image bakes (skip when preserving AMIs)

Bake base images (kali, ubuntu, windows, dc) and the POLARIS images (polaris-vm, polaris-dc) via packer.yml.

  • Bakes need per-account PACKER_VERIFY_{SUBNET,SG,INSTANCE_PROFILE}_ID_<ENV> variables plus a <env>.pkrvars.hcl, both derived from the freshly rebuilt VPC. There is a post-build DNS validation gate.
  • After baking, update ec2_ami_id and ctfd_ami_id in the portal overlay, update shifter/packer/dc-amis.json, update the /shifter/ami/* SSM parameters, then re-sync the portal secret with sync-deploy-secrets.sh.
  • Known design issue: baking must not depend on a branch merge (tracked in issue #1680). If a bake seems to require merging first, stop and flag it.

Phase 4: deploy

Deploy via the real pipeline, dispatched from the code branch you want live (usually dev for a standard standup):

gh workflow run deploy.yml --repo Brad-Edwards/shifter \
  -f environment=aws-<env> --ref <code-branch>

The stages run Core, then Range, then Engine, then Platform. Monitor the run to a terminal state. On the first deploy of a fresh account, watch the portal migration step: if it fails because there is no in-service ASG instance yet, or because the runtime DB user does not exist, run the migration by hand over SSM as the DB master:

scripts/portal-deploy/deploy_portal.sh --migrate-only   # via SSM on the portal EC2

The entrypoint switches to the RDS-IAM runtime user (portal_runtime), which is created by a migration, so on a fresh DB the migrate must run with DB_IAM_AUTH_RUNTIME=false (the --migrate-only path sets this).

Phase 5: health verification

  • ALB target health: proof-portal-tg (portal app) and proof-portal-guacamole should be all healthy (substitute the env prefix).
  • ACM certificate validated for the portal domain.
  • Portal responds: /health/ returns 200 and / returns 200. If Cloudflare DNS is not yet pointed at the new ALB, verify against the raw ALB with a host header or curl --resolve <domain>:443:<alb-ip>.

Phase 6: base-range smoke

Run the post-deploy smoke against a base range to prove provisioning works end to end. See scripts/post_deploy_smoke/README.md for the entrypoint. This exercises subnet allocation, guest launch, and bootstrap. Do this before POLARIS to avoid range contention.

Phase 7: POLARIS range walkthrough

Provision a POLARIS range and confirm it reaches READY.

  1. Provision via the portal Django shell over SSM. Get the portal instance, docker exec the portal container, re-export the app environment from /proc/1/environ (the shell needs DJANGO_SECRET_KEY and friends), then call cms_create_range(user, 'polaris', {}, False).
  2. Poll the range status to READY or FAILED. The provisioner launches as an ECS task off the latest ACTIVE <env>-portal-pulumi-provisioner task definition.
  3. On READY, confirm: two guests running (kali attacker on polaris-vm, dc01 on polaris-dc), the DC promoted (boreas.local), the full container stack up on the kali host (17 containers including a14-kali, the Bedrock agent), and the agent verify checks pass (identity via credential_process plus regional STS, and a Bedrock invoke-model).

POLARIS Bedrock requirements:

  • The account must have Bedrock model access for the configured agent models. Verify with an actual bedrock-runtime invoke-model, not list-foundation-models (the latter shows region availability, not account access). See reference_proof_bedrock_model_access in operator notes: the proof account can invoke only sonnet-4-6; all haiku variants are Marketplace gated.
  • The aws_polaris_agent_* variables must be set in the portal overlay and the TF_VARS_<ENV>_PORTAL secret. Empty values make POLARIS fail at variable build. Point both the main and small models at a model the account can invoke.

Phase 8: report and DNS handoff

Report the outcome and the DNS records the operator must set in Cloudflare (DNS is external; the agent does not hold those credentials):

  • <env>.shifter.keplerops.com CNAME to the portal ALB DNS name.
  • polaris.keplerops.com A record to the CTFd EIP.

Note that a running POLARIS range costs money until it is destroyed. State clearly whether the range was left running.

Known pitfalls (already fixed on dev; verify, do not re-hit)

These were the fresh-environment failures found during the proof rebuild. They are fixed on dev. On a fresh account, confirm the fix is present rather than rediscovering the failure.

  • Provisioner image must copy the whole shared package, not a subset, or range provisioning fails with ModuleNotFoundError: shared.range_instantiation_policy (issue #1678). Broke all range provisioning in every environment.
  • The CI permissions boundary must carry the per-range POLARIS agent carve-out and the IAM read permissions the provider needs after role creation (iam:ListAttachedRolePolicies, iam:ListInstanceProfilesForRole) (issue #1683, fixes #1681).
  • First-deploy migrate race: the deploy waits for an in-service ASG instance before migrating (issue #1677). If it still fails, run the manual migrate from Phase 4.
  • POLARIS agent verify runs host-side because the a14-kali container has no aws CLI, and the container aws-config needs sts_regional_endpoints = regional plus an STS host pin (issue #1688).
  • Environment parity: a new import-time environment variable must be added to user_data.sh, the portal SSM parameters, and deploy_portal.sh common env, or a fresh AWS deploy fails at migrate.
  • Range guests can fail SSM when the VPC DNS resolver is not reachable; see the guest DNS operator note.

Progress and traceability

  • Track phases with a task list and give the operator running status. Do not run the whole thing silently in a subagent.
  • File an issue for every new bug found, fix the root cause, and PR it to dev.
  • Keep a durable scratch record of parameters, decisions, resource ids, and PRs so the run survives a context reset.
Repository
PaloAltoNetworks/shifter
Last updated
First committed

Canonical home

PaloAltoNetworks/shifter
In sync

since Sep 7, 2026

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.