Skip to content

Commit b755481

Browse files
scoropezakrokoko
andauthored
fix(agent): validate AWS credentials before Docker build (#33)
Move credential resolution (explicit env vars, AWS CLI/SSO, ~/.aws mount) before the Docker image build so expired sessions and missing credentials fail immediately with actionable guidance. - Fix unbound variable crash with empty array under set -u - Remove dead MOUNT_AWS_DIR variable - Expand error message to list all three credential methods - Update local testing docs with credential resolution details, troubleshooting table, and Starlight admonitions Co-authored-by: Alain Krok <alkrok@amazon.com>
1 parent a53282d commit b755481

3 files changed

Lines changed: 132 additions & 38 deletions

File tree

agent/run.sh

Lines changed: 54 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,56 @@ if [[ -z "${AWS_REGION:-}" ]]; then
111111
exit 1
112112
fi
113113

114+
# ---------------------------------------------------------------------------
115+
# Resolve AWS credentials (before Docker build to fail fast)
116+
# ---------------------------------------------------------------------------
117+
# Store resolved credentials in variables so they can be applied to DOCKER_ARGS
118+
# after the image is built. This avoids a lengthy Docker build only to discover
119+
# that AWS credentials are missing or expired.
120+
AWS_CRED_MODE=""
121+
RESOLVED_KEY=""
122+
RESOLVED_SECRET=""
123+
RESOLVED_TOKEN=""
124+
125+
if [[ -n "${AWS_ACCESS_KEY_ID:-}" ]]; then
126+
AWS_CRED_MODE="explicit"
127+
RESOLVED_KEY="${AWS_ACCESS_KEY_ID}"
128+
RESOLVED_SECRET="${AWS_SECRET_ACCESS_KEY}"
129+
RESOLVED_TOKEN="${AWS_SESSION_TOKEN:-}"
130+
echo " AWS: using explicit credentials (AWS_ACCESS_KEY_ID)"
131+
elif command -v aws &>/dev/null; then
132+
# Resolve credentials from the AWS CLI (handles SSO, profiles, credential files).
133+
# This avoids the need to mount ~/.aws and replicate the full credential chain
134+
# inside the container — SSO tokens in particular don't resolve well there.
135+
echo " AWS: resolving credentials via AWS CLI${AWS_PROFILE:+ (profile '${AWS_PROFILE}')}..."
136+
EXPORT_CMD=(aws configure export-credentials --format process)
137+
[[ -n "${AWS_PROFILE:-}" ]] && EXPORT_CMD+=(--profile "${AWS_PROFILE}")
138+
139+
CREDS_JSON=$("${EXPORT_CMD[@]}" 2>/dev/null) || {
140+
echo "ERROR: Failed to resolve AWS credentials via AWS CLI." >&2
141+
echo " Possible fixes:" >&2
142+
echo " - Run 'aws sso login${AWS_PROFILE:+ --profile ${AWS_PROFILE}}' if using SSO" >&2
143+
echo " - Run 'aws configure${AWS_PROFILE:+ --profile ${AWS_PROFILE}}' to set up a profile" >&2
144+
echo " - Export AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY directly" >&2
145+
exit 1
146+
}
147+
148+
AWS_CRED_MODE="resolved"
149+
RESOLVED_KEY=$(echo "$CREDS_JSON" | python3 -c "import sys,json; c=json.load(sys.stdin); print(c['AccessKeyId'])")
150+
RESOLVED_SECRET=$(echo "$CREDS_JSON" | python3 -c "import sys,json; c=json.load(sys.stdin); print(c['SecretAccessKey'])")
151+
RESOLVED_TOKEN=$(echo "$CREDS_JSON" | python3 -c "import sys,json; c=json.load(sys.stdin); print(c.get('SessionToken',''))")
152+
echo " AWS: resolved temporary credentials (AccessKeyId: ${RESOLVED_KEY:0:8}...)"
153+
elif [[ -d "${HOME}/.aws" ]]; then
154+
AWS_CRED_MODE="mount"
155+
if [[ -n "${AWS_PROFILE:-}" ]]; then
156+
echo " AWS: mounting ~/.aws with profile '${AWS_PROFILE}' (SSO may not work)"
157+
else
158+
echo " AWS: mounting ~/.aws (using default profile)"
159+
fi
160+
else
161+
echo "WARNING: No AWS credentials detected. Set AWS_ACCESS_KEY_ID or AWS_PROFILE, or ensure ~/.aws exists." >&2
162+
fi
163+
114164
# ---------------------------------------------------------------------------
115165
# Build
116166
# ---------------------------------------------------------------------------
@@ -161,49 +211,17 @@ if [[ "$MODE" == "server" ]]; then
161211
DOCKER_ARGS+=(-p 8080:8080)
162212
fi
163213

164-
# AWS credentials: prefer explicit env vars, then resolve from profile/SSO
165-
if [[ -n "${AWS_ACCESS_KEY_ID:-}" ]]; then
166-
DOCKER_ARGS+=(
167-
-e "AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}"
168-
-e "AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}"
169-
)
170-
[[ -n "${AWS_SESSION_TOKEN:-}" ]] && DOCKER_ARGS+=(-e "AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN}")
171-
echo " AWS: using explicit credentials (AWS_ACCESS_KEY_ID)"
172-
elif command -v aws &>/dev/null; then
173-
# Resolve credentials from the AWS CLI (handles SSO, profiles, credential files).
174-
# This avoids the need to mount ~/.aws and replicate the full credential chain
175-
# inside the container — SSO tokens in particular don't resolve well there.
176-
PROFILE_ARG=()
177-
[[ -n "${AWS_PROFILE:-}" ]] && PROFILE_ARG=(--profile "${AWS_PROFILE}")
178-
179-
echo " AWS: resolving credentials via AWS CLI${AWS_PROFILE:+ (profile '${AWS_PROFILE}')}..."
180-
CREDS_JSON=$(aws configure export-credentials "${PROFILE_ARG[@]}" --format process 2>/dev/null) || {
181-
echo "ERROR: Failed to resolve AWS credentials. Make sure you are logged in:" >&2
182-
echo " aws sso login${AWS_PROFILE:+ --profile ${AWS_PROFILE}}" >&2
183-
exit 1
184-
}
185-
186-
RESOLVED_KEY=$(echo "$CREDS_JSON" | python3 -c "import sys,json; c=json.load(sys.stdin); print(c['AccessKeyId'])")
187-
RESOLVED_SECRET=$(echo "$CREDS_JSON" | python3 -c "import sys,json; c=json.load(sys.stdin); print(c['SecretAccessKey'])")
188-
RESOLVED_TOKEN=$(echo "$CREDS_JSON" | python3 -c "import sys,json; c=json.load(sys.stdin); print(c.get('SessionToken',''))")
189-
214+
# Apply previously resolved AWS credentials to DOCKER_ARGS
215+
if [[ "$AWS_CRED_MODE" == "explicit" || "$AWS_CRED_MODE" == "resolved" ]]; then
190216
DOCKER_ARGS+=(
191217
-e "AWS_ACCESS_KEY_ID=${RESOLVED_KEY}"
192218
-e "AWS_SECRET_ACCESS_KEY=${RESOLVED_SECRET}"
193219
)
194220
[[ -n "${RESOLVED_TOKEN}" ]] && DOCKER_ARGS+=(-e "AWS_SESSION_TOKEN=${RESOLVED_TOKEN}")
195-
echo " AWS: resolved temporary credentials (AccessKeyId: ${RESOLVED_KEY:0:8}...)"
196-
elif [[ -d "${HOME}/.aws" ]]; then
221+
elif [[ "$AWS_CRED_MODE" == "mount" ]]; then
197222
# Fallback: mount ~/.aws directly (works for static credential files, not SSO)
198223
DOCKER_ARGS+=(-v "${HOME}/.aws:/home/agent/.aws:ro")
199-
if [[ -n "${AWS_PROFILE:-}" ]]; then
200-
DOCKER_ARGS+=(-e "AWS_PROFILE=${AWS_PROFILE}")
201-
echo " AWS: mounting ~/.aws with profile '${AWS_PROFILE}' (SSO may not work)"
202-
else
203-
echo " AWS: mounting ~/.aws (using default profile)"
204-
fi
205-
else
206-
echo "WARNING: No AWS credentials detected. Set AWS_ACCESS_KEY_ID or AWS_PROFILE, or ensure ~/.aws exists." >&2
224+
[[ -n "${AWS_PROFILE:-}" ]] && DOCKER_ARGS+=(-e "AWS_PROFILE=${AWS_PROFILE}")
207225
fi
208226

209227
echo ""

docs/guides/DEVELOPER_GUIDE.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,10 @@ cd ..
256256

257257
Before deploying to AWS, you can build and run the agent Docker container locally. The `agent/run.sh` script handles building the image, resolving AWS credentials, and applying AgentCore-matching resource constraints (2 vCPU, 8 GB RAM) so the local environment closely mirrors production.
258258

259+
:::tip
260+
The script validates AWS credentials **before** starting the Docker build, so problems like an expired SSO session surface immediately — not after a lengthy image build.
261+
:::
262+
259263
#### Prerequisites
260264

261265
The `owner/repo` you pass to `run.sh` must match an onboarded Blueprint and be a repository your `GITHUB_TOKEN` can **push to and open PRs on** (same rules as **Repository preparation** at the start of this guide). If you have not changed the Blueprint, fork `awslabs/agent-plugins`, set **`repo`** to your fork, and use a PAT scoped to that fork—then pass the same **`owner/repo`** here.
@@ -267,7 +271,29 @@ export GITHUB_TOKEN="ghp_..." # Fine-grained PAT (see agent/README.md for re
267271
export AWS_REGION="us-east-1" # Region where Bedrock models are enabled
268272
```
269273

270-
For AWS credentials, either export `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` directly, or have the AWS CLI configured (the script will resolve credentials from your active profile or SSO session automatically).
274+
#### AWS credential resolution
275+
276+
The script resolves AWS credentials in priority order:
277+
278+
1. **Explicit environment variables** — If `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` are set, they are passed directly to the container. Include `AWS_SESSION_TOKEN` when using temporary credentials (e.g. from `aws sts assume-role`).
279+
280+
```bash
281+
export AWS_ACCESS_KEY_ID="AKIA..."
282+
export AWS_SECRET_ACCESS_KEY="..."
283+
export AWS_SESSION_TOKEN="..." # required for temporary credentials
284+
```
285+
286+
2. **AWS CLI resolution** — If the CLI is installed, the script runs `aws configure export-credentials` to resolve credentials from your active profile or SSO session. Set `AWS_PROFILE` to target a specific profile.
287+
288+
```bash
289+
export AWS_PROFILE="my-dev-profile" # optional — defaults to the CLI default profile
290+
```
291+
292+
3. **`~/.aws` directory mount** — If neither of the above is available but `~/.aws` exists, the directory is bind-mounted read-only into the container. This works for static credential files but **not for SSO tokens**, which don't resolve well inside the container.
293+
294+
:::caution
295+
If none of these methods succeeds, the script prints a warning and continues without AWS credentials. The container will start but any AWS API call (Bedrock, DynamoDB, etc.) will fail at runtime. Make sure at least one credential source is configured before running a real task.
296+
:::
271297

272298
#### Running a task locally
273299

@@ -303,6 +329,8 @@ curl -X POST http://localhost:8080/invocations \
303329
-d '{"input":{"prompt":"Fix the login bug","repo_url":"owner/repo"}}'
304330
```
305331

332+
In server mode, `repo_url`, `prompt`, and other task parameters can be sent via the `/invocations` JSON payload instead of environment variables.
333+
306334
#### Monitoring a running container
307335

308336
The container runs with a fixed name (`bgagent-run`). In a second terminal:
@@ -320,12 +348,22 @@ docker exec -it bgagent-run bash # shell into the container
320348
|---|---|---|
321349
| `ANTHROPIC_MODEL` | `us.anthropic.claude-sonnet-4-6` | Bedrock model ID |
322350
| `MAX_TURNS` | `100` | Max agent turns before stopping |
351+
| `MAX_BUDGET_USD` | | Cost ceiling for local batch runs (USD). Not used in production — see below |
323352
| `DRY_RUN` | | Set to `1` to validate config and print prompt without running the agent |
324353

325354
**Cost budget** is not configured here for production tasks: set **`max_budget_usd`** when creating a task (REST API, CLI `--max-budget`, or per-repo Blueprint). The orchestrator passes it in the runtime invocation payload. The optional env var `MAX_BUDGET_USD` applies only to **local batch** runs; see `agent/README.md`.
326355

327356
For the full list of environment variables and GitHub PAT permissions, see `agent/README.md`.
328357

358+
#### Troubleshooting
359+
360+
| Symptom | Cause | Fix |
361+
|---|---|---|
362+
| `ERROR: Failed to resolve AWS credentials via AWS CLI` | SSO session expired or profile misconfigured | Run `aws sso login --profile <your-profile>` if using SSO, or `aws configure` to set up a profile, or export `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY` directly |
363+
| `ERROR: GITHUB_TOKEN is not set` | Missing PAT | Export `GITHUB_TOKEN` (see `agent/README.md` for required scopes) |
364+
| `WARNING: No AWS credentials detected` | No env vars, no AWS CLI, no `~/.aws` directory | Configure one of the three credential methods above |
365+
| `WARNING: Image exceeds AgentCore 2 GB limit!` | Agent image too large for production | Reduce dependencies or use multi-stage Docker build |
366+
329367
### Deployment
330368

331369
Once your agent works locally, you can deploy it on AWS. A **full** `mise run //cdk:deploy` of this stack has been observed at **~572 seconds (~9.5 minutes)** total (CDK-reported *Total time*); expect variation by Region, account state, and whether container layers are already cached.

docs/src/content/docs/developer-guide/Installation.md

Lines changed: 39 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)