Object storage
AWS S3 integration
Ahena works with the S3 buckets your app stores files in: it keeps Block Public Access on and default encryption set, creates missing buckets, sets CORS for browser uploads and turns on versioning. It reads bucket settings only; it never lists, reads or writes objects. Your app talks to S3 directly through the generated storage code. Ahena isn't in the path. S3 is its own provider in Ahena, separate from SES.
What Ahena inspects
Read-only: inspecting and Doctor never change anything at AWS S3.
- The credentials, through STS
GetCallerIdentity(account id and ARN). - Every bucket and its Region, and for each: CORS rules, Block Public Access, versioning, default encryption, lifecycle rule count, whether website hosting is configured, and Object Ownership.
- Whether the bucket policy makes the bucket public, through
GetBucketPolicyStatus. The policy itself is never read. - A field the credentials may not read shows as
not allowed (s3:…), naming the action; the rest still show.
What Ahena configures
You declare what you want in ahena.config.ts; ahena plan shows each change with its classification before anything is applied.
| Change | Classification | Notes |
|---|---|---|
| Create the bucket | CONFIRMATION_REQUIRED | AWS doesn't charge for an empty general-purpose bucket, so this isn't classed billable; the cost notice says what is billed later. Created with Block Public Access on, ACLs disabled and SSE-S3, each read back. An empty bucket costs nothing; stored objects, requests and transfer are billed by AWS, as the cost notice says. |
| Turn Block Public Access fully on | CONFIRMATION_REQUIRED | Not SAFE: anything public through a bucket policy or ACL stops being reachable. |
| Set default encryption SSE-S3 | CONFIRMATION_REQUIRED | Only when the bucket has none; SSE-KMS is left in place. |
| Enable versioning | CONFIRMATION_REQUIRED | It can later be suspended but never turned off, and old versions are billed until a lifecycle rule expires them. |
| Set CORS | CONFIRMATION_REQUIRED | Ahena's rules carry ahena- IDs; rules you wrote are kept. |
Refused outright:
- Turning Block Public Access off, public bucket policies or ACLs, and website hosting that needs public access.
- Deleting buckets or objects, and listing or reading objects.
- Suspending versioning, replacing SSE-KMS with SSE-S3, and
*or localhost origins in production CORS.
Generated code: src/ahena/storage/index.ts (storage.upload, download, remove, uploadUrl, downloadUrl, raw, the same interface as the Cloudflare R2 adapter), src/ahena/providers/aws-s3.ts and .env.example.aws-s3.
How Ahena verifies
After every write, Ahena reads AWS S3 back. Each change ends in one of these states, and the plan's outcome says why:
- VERIFIED: Ahena read the provider back and saw the desired state. The CLI shows ✓ only for VERIFIED.
- APPLIED_UNVERIFIED: The provider accepted the change, but the re-read doesn't show it yet (after bounded polling), or the re-read failed.
Each apply step re-reads first and writes only if still needed, with x-amz-expected-bucket-owner so a write can't reach another account's bucket. Ahena then plans again: an empty plan is the read-back.
Bucket listings follow continuation tokens to the end; Ahena never concludes a bucket is missing from part of a list.
If a request may have reached AWS S3 but the answer was lost, Ahena re-reads before deciding: done, safe to retry, or “check before retrying”.
What Doctor diagnoses
Key checks from ahena doctor. Each finding says why it matters, where, the impact and whether Ahena can fix it. The full list is in the AWS S3 docs.
| Check | What it means |
|---|---|
aws-s3.credential | STS accepts the credentials. |
aws-s3.bucket / .region | The bucket exists, is this account's, and is in the declared Region. |
aws-s3.bucket.public | FAIL when the bucket policy makes the bucket public. |
aws-s3.bucket.public_access_block | All four Block Public Access settings on; a plannable fix when not. |
aws-s3.bucket.encryption / .versioning / .cors | Default encryption set, versioning on in production, declared CORS rules present. |
aws-s3.bucket.lifecycle / .website / .ownership | Lifecycle rule count, website hosting presence, and ACLs disabled. |
More on findings, health and continuous checks: Doctor.
Approvals
AWS S3 changes here are classified CONFIRMATION_REQUIRED. Every change is planned and shown as a diff first. Ahena itself enforces who can approve it:
- SAFE changes are applied without asking.
- In production, every other change needs approval in the Ahena dashboard, signed in, by an admin.
- In other environments, CONFIRMATION_REQUIRED changes are approved in the CLI; BILLABLE and DESTRUCTIVE changes always need the dashboard.
- MANUAL steps are never applied by Ahena.
--yes and --allow-billable don't replace a dashboard approval. See approvals for how it works and why the browser.
Manual steps
These are MANUAL: Ahena never does them. It lists them with the exact steps when they apply.
- Create a dedicated IAM user with the minimal policy from the docs (Ahena doesn't change IAM).
- A public bucket policy: remove the public statement in the S3 console, then let Ahena turn Block Public Access on.
- ACLs enabled: set Object Ownership to Bucket owner enforced in the S3 console.
Credentials and permissions
| Name | Secret | Notes |
|---|---|---|
AWS_ACCESS_KEY_ID | Yes | Access key of a dedicated IAM user for Ahena (AKIA…). |
AWS_SECRET_ACCESS_KEY | Yes | Shown once when the access key is created. |
AWS_SESSION_TOKEN | Yes | Optional, for temporary credentials only. |
AWS_REGION | No | Where Ahena calls STS and the default Region of your buckets. One of the 17 Regions enabled by default. |
Least privilege
s3:ListAllMyBuckets, plus bucket-scopeds3:ListBucket,s3:CreateBucket, and get/put for CORS, Block Public Access, versioning and encryption, and get for policy status, lifecycle, website and Object Ownership. No object, policy or ACL permissions.- Authentication only: AWS can't report a principal's permissions without
iam:SimulatePrincipalPolicy, so a missing action shows in Doctor and apply, by name.
Each credential Ahena stores gets its own key and is envelope-encrypted, bound to the environment. See security.
Workflow example
Connect, check, plan, then apply. Placeholders (…) stand for your own values.
AWS_ACCESS_KEY_ID=… AWS_SECRET_ACCESS_KEY=… ahena connect aws-s3 -e production --set AWS_REGION=us-east-1Connect with a dedicated IAM user's key.
ahena doctor -e productionCheck public access, encryption, versioning and CORS.
ahena plan -e productionSee the bucket changes.
ahena apply -e productionApprove and apply; Ahena reads each change back.
ahena generate aws-s3 -e productionWrite the storage adapter.
Verification status
Available in beta. Every capability is validated by Ahena's automated provider contract and conformance tests; verification against the real service is next.
How AWS S3 is tested
- AWS S3 is tested against a simulated version of the S3 and STS APIs (XML responses and AWS error codes), including pagination, permission errors, the read-back after each write and idempotent re-apply.
- Request signing (SigV4, WebCrypto) is tested against AWS's published SigV4 test suite and the S3 documentation's example.
- It hasn't been run against real AWS yet. The generated storage code is typechecked against the real AWS SDK.
The providers overview explains Ahena's testing methodology and what has been verified for every provider.
Limitations
- Opt-in Regions, GovCloud and China aren't supported.
- Account-level Block Public Access isn't read: its endpoint includes the account id, so it can't be declared in advance.
- Directory buckets (S3 Express One Zone) and SSE-KMS configuration aren't supported.
Disconnecting
ahena disconnect aws-s3 removes the stored credentials; deactivate and delete the access key in IAM. Buckets and objects are untouched.