--aws: export-credentials on the host and inject AWS_* env, instead of only mounting ~/.aws #586

Closed
opened 2026-07-03 20:34:34 +00:00 by coilysiren · 3 comments
Owner

Problem

ward's --aws (now the aws-guardfile capability, ward#578) bind-mounts the host ~/.aws into the container. That only works if creds live in ~/.aws files, and it forces the container to re-walk AWS's full credential chain (SSO login, profile/role resolution) - which the operator will not replicate in-container. Concretely: Kai's three host contexts all have working creds, but a warded container got NoCredentials, because the creds do not live purely in ~/.aws files. ward#579 made the empty-mount case warn, but the mount is still the wrong mechanism.

Fix: export-credentials on the host, inject env into the container

At launch, when the aws capability is on, ward should:

  1. Run aws configure export-credentials --format process on the host (AWS CLI v2). This resolves the whole chain - SSO, env, profile, assumed role, IMDS - and returns flat JSON { AccessKeyId, SecretAccessKey, SessionToken, Expiration }.
  2. Inject those as AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN env vars into docker run (-e).

The container gets working creds with zero chain replication, regardless of how the host authenticates. The creds are short-lived (session expiry) - more aligned with "no long-lived PAT in agent hands" than mounting raw ~/.aws.

This updates the aws capability's mechanism in ward's name->mechanism registry (ward#578) from "mount ~/.aws" to "export-and-inject".

Details

  • Primary + fallback: prefer export-inject; if export-credentials errors or AWS CLI v2 is absent, fall back to the existing ~/.aws mount + the #579 warning. Do not regress the file path.
  • Fix the #579 warning: it currently checks only "~/.aws files present". With export-inject, warn when export-credentials returns nothing/errors (the real "no creds" signal) - closing the gap the #579 engineer flagged (a host with env/SSO/role creds gets a false warning today).
  • Expiration: surface/log the returned Expiration. A very long-running container could outlive it - acceptable for now (provision runs are minutes); note it, do not solve re-export this pass.
  • Broker-forwarded dispatch: must run on the launching host where the chain resolves, and work for a broker-forwarded dispatch (the director-surface path), not just direct runs.
  • Windows: the broker host may be Windows (C:\Users\firem). export-credentials is cross-platform - ensure the invocation + JSON parse work on Windows.
  • Security: env vars show in docker inspect. Short-lived creds on the operator's own host - acceptable; a temp ro-mounted credentials file is an alternative if preferred, but env is the ask.

Payoff

--aws delivers creds for ANY host auth source, unblocks self-serve SSM rotation (infra#456), and makes the token-minting skill's --aws claim true. Closes the real gap behind ward#579.

ward#579 (empty-mount warning + the gap), ward#578 (aws capability -> mechanism registry, where this lands), infra#456 (scoop rotation blocked on this), token-minting skill.

## Problem ward's `--aws` (now the aws-guardfile capability, ward#578) bind-mounts the host `~/.aws` into the container. That only works if creds live in `~/.aws` **files**, and it forces the container to re-walk AWS's full credential chain (SSO login, profile/role resolution) - which the operator will not replicate in-container. Concretely: Kai's three host contexts all have working creds, but a warded container got `NoCredentials`, because the creds do not live purely in `~/.aws` files. ward#579 made the empty-mount case *warn*, but the mount is still the wrong mechanism. ## Fix: export-credentials on the host, inject env into the container At launch, when the aws capability is on, ward should: 1. Run `aws configure export-credentials --format process` on the **host** (AWS CLI v2). This resolves the **whole** chain - SSO, env, profile, assumed role, IMDS - and returns flat JSON `{ AccessKeyId, SecretAccessKey, SessionToken, Expiration }`. 2. Inject those as `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN` env vars into `docker run` (`-e`). The container gets working creds with **zero chain replication**, regardless of how the host authenticates. The creds are short-lived (session expiry) - **more** aligned with "no long-lived PAT in agent hands" than mounting raw `~/.aws`. This updates the aws capability's mechanism in ward's name->mechanism registry (ward#578) from "mount ~/.aws" to "export-and-inject". ## Details - **Primary + fallback:** prefer export-inject; if `export-credentials` errors or AWS CLI v2 is absent, fall back to the existing `~/.aws` mount + the #579 warning. Do not regress the file path. - **Fix the #579 warning:** it currently checks only "~/.aws files present". With export-inject, warn when `export-credentials` returns nothing/errors (the real "no creds" signal) - closing the gap the #579 engineer flagged (a host with env/SSO/role creds gets a false warning today). - **Expiration:** surface/log the returned `Expiration`. A very long-running container could outlive it - acceptable for now (provision runs are minutes); note it, do not solve re-export this pass. - **Broker-forwarded dispatch:** must run on the launching host where the chain resolves, and work for a broker-forwarded dispatch (the director-surface path), not just direct runs. - **Windows:** the broker host may be Windows (`C:\Users\firem`). `export-credentials` is cross-platform - ensure the invocation + JSON parse work on Windows. - **Security:** env vars show in `docker inspect`. Short-lived creds on the operator's own host - acceptable; a temp ro-mounted credentials file is an alternative if preferred, but env is the ask. ## Payoff `--aws` delivers creds for ANY host auth source, unblocks self-serve SSM rotation (infra#456), and makes the token-minting skill's `--aws` claim true. Closes the real gap behind ward#579. ## Related ward#579 (empty-mount warning + the gap), ward#578 (aws capability -> mechanism registry, where this lands), infra#456 (scoop rotation blocked on this), token-minting skill.
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-ward-586 on host KAI-DESKTOP-TOWER is carrying this issue (reserved 2026-07-03T20:35:26Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-ward-586` on host `KAI-DESKTOP-TOWER` is carrying this issue (reserved 2026-07-03T20:35:26Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. **Do not comment on or edit this issue to steer the run while it is reserved.** The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a **new issue, dispatched fresh** — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494). <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Owner

WARD-OUTCOME: done - aws capability now exports the host credential chain and injects AWS_* env at launch, ~/.aws mount kept as the fallback; landed on main as e46c9fd.

Retro: this one fit the existing seams cleanly, which was a pleasant surprise. The credential env-file plumbing (the same private 0600 --env-file the forgejo token and agent OAuth creds already ride) was exactly the right vehicle, so injection was mostly a matter of resolving awsExportedCreds host-side and appending EnvLines. The nicest accident: clearing AWSHome on a successful export made the #579 empty-mount warning correct for free - the existing maybeWarnAWSMount early-return now fires only on the true fallback path, so the false-warning gap closed without new branching.

Two things I had to reason about rather than copy. First, region: export-credentials returns no region, and dropping the mount removes ~/.aws/config as the region source, so I resolve and inject AWS_DEFAULT_REGION/AWS_REGION host-side or SSM would miss its region. That's beyond the literal three vars the issue named but necessary to not regress. Second, the broker-forwarded path: it needed no special handling because a forwarded engineer dispatch re-runs ward agent engineer --aws on the host broker, so the export naturally runs where the chain resolves - I just confirmed --aws forwards through appendBrokerContainerFlags.

Confidence: high on the pure logic (unit-tested: parse, envLines, expiry note, mount-drop) and the wiring (build/vet/tests/pre-commit all green). Lower on the parts I couldn't drive end-to-end from this ephemeral container - no real AWS CLI creds or host docker to watch an actual export-inject deliver working SSM. The Windows path is reasoned, not run.

The only real fight was the pre-commit doc/comment caps (80 lines / 4000 chars per doc, 2-line / 90-char comment blocks) - I split the mechanism detail into a new docs/agent-aws-creds.md and trimmed agent-capability.md to a pointer.

Follow-ups worth filing: (1) re-export for long-lived containers that outlive the session expiry, explicitly deferred here; (2) the --print/dry-run paths still render the ~/.aws mount since they don't run export-credentials (a static preview), which is defensible but slightly misleading.

WARD-OUTCOME: done - aws capability now exports the host credential chain and injects AWS_* env at launch, ~/.aws mount kept as the fallback; landed on main as e46c9fd. Retro: this one fit the existing seams cleanly, which was a pleasant surprise. The credential env-file plumbing (the same private 0600 --env-file the forgejo token and agent OAuth creds already ride) was exactly the right vehicle, so injection was mostly a matter of resolving `awsExportedCreds` host-side and appending EnvLines. The nicest accident: clearing `AWSHome` on a successful export made the #579 empty-mount warning correct for free - the existing `maybeWarnAWSMount` early-return now fires only on the true fallback path, so the false-warning gap closed without new branching. Two things I had to reason about rather than copy. First, region: export-credentials returns no region, and dropping the mount removes ~/.aws/config as the region source, so I resolve and inject AWS_DEFAULT_REGION/AWS_REGION host-side or SSM would miss its region. That's beyond the literal three vars the issue named but necessary to not regress. Second, the broker-forwarded path: it needed no special handling because a forwarded engineer dispatch re-runs `ward agent engineer --aws` on the host broker, so the export naturally runs where the chain resolves - I just confirmed --aws forwards through appendBrokerContainerFlags. Confidence: high on the pure logic (unit-tested: parse, envLines, expiry note, mount-drop) and the wiring (build/vet/tests/pre-commit all green). Lower on the parts I couldn't drive end-to-end from this ephemeral container - no real AWS CLI creds or host docker to watch an actual export-inject deliver working SSM. The Windows path is reasoned, not run. The only real fight was the pre-commit doc/comment caps (80 lines / 4000 chars per doc, 2-line / 90-char comment blocks) - I split the mechanism detail into a new docs/agent-aws-creds.md and trimmed agent-capability.md to a pointer. Follow-ups worth filing: (1) re-export for long-lived containers that outlive the session expiry, explicitly deferred here; (2) the --print/dry-run paths still render the ~/.aws mount since they don't run export-credentials (a static preview), which is defensible but slightly misleading.
Author
Owner

Not yet proven live - re-test after host upgrade

ward#586 landed the export-inject mechanism (aws configure export-credentials on the host, AWS_* env injected into the container, ~/.aws mount as fallback). But infra#456, dispatched AFTER this landed, STILL got NoCredentials in the container: ward ops aws sts get-caller-identity returned NoCredentials, with no ~/.aws, no IMDS, and no AWS_ACCESS_KEY_ID env.

So export-inject is not delivering on the dispatch host yet. Two candidates:

  1. The host ward has not upgraded to the release carrying ward#586 - the container downloads the host's ward version, so it is still running the mount-only path.
  2. The dispatch host (Windows, C:\Users\firem) resolves no AWS chain in the context the container launches from, so aws configure export-credentials returns nothing to inject.

Action: once the host ward is on a release at or past the ward#586 landing, re-dispatch any --aws run and check sts get-caller-identity. If still empty, check whether export-credentials resolves anything in the launcher's context on the Windows host. Until this is confirmed, self-serve SSM rotation (the token-minting skill's --aws claim) remains unproven.

Reference: infra#456 hit this wall twice (18:36 and the post-586 relaunch).

## Not yet proven live - re-test after host upgrade ward#586 landed the export-inject mechanism (aws configure export-credentials on the host, AWS_* env injected into the container, ~/.aws mount as fallback). But infra#456, dispatched AFTER this landed, STILL got NoCredentials in the container: ward ops aws sts get-caller-identity returned NoCredentials, with no ~/.aws, no IMDS, and no AWS_ACCESS_KEY_ID env. So export-inject is not delivering on the dispatch host yet. Two candidates: 1. The host ward has not upgraded to the release carrying ward#586 - the container downloads the host's ward version, so it is still running the mount-only path. 2. The dispatch host (Windows, C:\Users\firem) resolves no AWS chain in the context the container launches from, so `aws configure export-credentials` returns nothing to inject. Action: once the host ward is on a release at or past the ward#586 landing, re-dispatch any --aws run and check sts get-caller-identity. If still empty, check whether export-credentials resolves anything in the launcher's context on the Windows host. Until this is confirmed, self-serve SSM rotation (the token-minting skill's --aws claim) remains unproven. Reference: infra#456 hit this wall twice (18:36 and the post-586 relaunch).
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
coilyco-flight-deck/ward#586
No description provided.