package aws-eio
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
On This Page
Eio-native AWS SigV4 request signing, credential resolution, and HTTP transport
Install
dune-project
Dependency
Authors
Maintainers
Sources
v0.1.0.tar.gz
md5=b89e6609f8ce49f8830328c1abe81cca
sha512=41499e9faf2b080d447453f43f9eed48bdfecf5f334d8d17dcb7251b1476512b783b05c6601f1f564dad8dc3e8ee73d38e65214292af0a2938f8594f8415d162
doc/CHANGES.html
Changes
0.1.0
- Initial standalone OPAM package:
Aws_sigv4(AWS Signature Version 4 request signing, validated against AWS's own published conformance suite — 37/37 cases),Aws_credentials(static keys, EKS IRSAAssumeRoleWithWebIdentity, ECS/Fargate container credentials, EC2 IMDSv2, and an explicitEnv_chain), andAws_http(retrying HTTP transport with SigV4 signing). Aws_httpbuilds and sends its own HTTP/1.1 requests rather than usingcohttp-eio'sClient, which derives the wire request line viaUri.path_and_query— a more permissive percent-encoding rule than SigV4 requires (! * ' ( ) : @ $ , +are left unescaped byUri, but SigV4'sUriEncode()requires them escaped). A request signed one way and sent another fails AWS's signature check.Aws_http.wire_resourcereusesAws_sigv4's own encoders directly for the wire request line, so what's signed and what's sent are identical by construction.- Retry classification covers both status-based retries (429, 5xx) and DynamoDB -style throttling signaled via HTTP 400 with an
x-amzn-errortypeheader (ThrottlingException,ProvisionedThroughputExceededException, etc.) — not just status codes. Jitter uses a self-seededRandom.State.t, not the globalRandommodule (which is deterministic across fresh processes). - IMDSv2 calls use a short (1s), fail-fast timeout — that endpoint is SSRF-adjacent.
- Fixed (post-tag, same 0.1.0 line, found by independent review): requests with a body never sent
Content-Length, so a spec-compliant server (RFC 7230 3.3.2/3.3.3) read them as having no body at all — bytes went out on the wire but weren't attributed to the request. Broke every body-carrying call, including this package's own EKS IRSA bootstrap (AssumeRoleWithWebIdentity). Fixed inwrite_request(added defensively for every caller) andsigned_request(added to the signed header set too, matching AWS's own conformance-suite convention for POST-with-body requests). Response reading also now correctly treatsHEADresponses and 1xx/204/304 status codes as always bodyless per RFC 7230 3.3.3 rule 1, instead of falling back to a full-timeout read-until-close for them.cohttp-eioremoved from the library's runtime dependencies (it was never actually used outside a comment — moved to test-only, where the mock server intest_aws_http.mlstill needs it). - Fixed (round 2 of independent review): retry classification only checked the
x-amzn-errortyperesponse header for a throttling exception type; AWS's restJson1 protocol spec requires clients to also accept it from a body field named__typeorcode, since only the header is required on the server side — a throttling response from a service/proxy that omits the header silently wasn't retried. Now falls back to the response body.signed_requesthad a window beforerequest_once's own exception handling (e.g.amz_date_nowfailing on a pathological clock) where an exception would raise straight out of a function documented as never raising — wrapped in the same catch-and-convert pattern, withEio.Cancel.Cancelleddeliberately excluded (always re-raised, never swallowed into anError, matching the rule this author'sobs-eiodocuments for its own backend calls; the same fix applied torequest_once, which had the same gap already). README corrected: theAws_credentials.sourcecode sample now matches the.mli's actual namedstatictype instead of an inlined record. - Fixed (round 3 of independent review — nit):
Aws_error.Signature_errorwas declared and documented but never actually constructed anywhere.signed_request's pre-request setup (deriving the timestamp, computing the SigV4 signature) is now split into its own function (build_signed_headers) with its own exception boundary, reporting failures there asSignature_error— categorically distinct fromNetwork_error, which now only covers the actual HTTP I/O. - Fixed (round 4 of independent review — blocker): every real HTTPS call this package makes (
signed_request, andAws_credentials.resolve_web_identity's EKS IRSA bootstrap) failed at runtime with "The default generator is not yet initialized" —tls-eio's handshake needsMirage_crypto_rng.default_generatorseeded, and nothing in this package (or its dependency graph) ever did that. Every prior test used plain HTTP against local mock servers, so this had zero coverage through three review rounds; found by the first test that actually attempted a real TLS handshake. Fixed inAws_tls: the samelazythat builds the client TLS wrapper now also callsMirage_crypto_rng_unix.use_default ()first (one-shot, idempotent, and only paid by callers who actually touch HTTPS — pure-SigV4-signing use of this package never triggers it). New test (test_aws_tls.ml) performs a real local TLS handshake against a self-signed cert (checked in undertest/tls_fixtures/, generated withopenssl, not trusted by the system CA bundle) and asserts the failure is a certificate-trust failure, not the RNG-not-initialized error — verified to actually fail without the fix before being committed. Also (nit): documented thatsigned_request's?portonly affects the TCP connection, not the signed/sentHostheader (correct for real AWS traffic, worth knowing for anything else). - Fixed (round 5 of independent review — should-fix): the round-4 fix used a bare
Stdlib.Lazy.tto computeAws_tls's TLS wrapper once.Lazy's own.mlistates plainly that concurrentLazy.forcefrom multiple OCaml 5 domains has "unspecified" behavior and can raiseCamlinternalLazy.Undefined— reproduced by an independent reviewer (two domains racing to force the same lazy, 8/8 failures in their environment). Any caller spawning multiple domains (e.g. a parallel S3/DynamoDB worker pool) whose first-ever HTTPS calls landed close together in time could hit this. Fixed with double-checked locking over anAtomic.tcache instead of a barelazy—Atomic's cross-domain safety is guaranteed by the stdlib, unlikeLazy's. New test exercisesAws_tls.https_for_uriunder real concurrent-domain contention (an explicit spin-barrier forces every domain to reach the call at the same instant, not just spawned close together) against the fixed code. - Fixed (round 6 of independent review — should-fix, on the round-5 test itself): the round-5 concurrency test ran after an earlier test that already called
Aws_tls.https_for_urionce, warmingdefault_https_wrapper_cachetoSomebefore the "concurrent domains" test's race even started — every domain was hitting the lock-free fast path on an already-populated cache, proving nothing about the actual first-use contention the test was named for. An independent reviewer confirmed this concretely by forcing the cache to stay cold going into the domain race and reproducing the originalCamlinternalLazy.Undefinedcrash 5/5 times against the real code — in an environment where round 5's own fix could not reproduce it at all. The library code (round 5's double-checked-locking fix) was independently re-verified as correct; only the test needed fixing. Fixed by resetting the cache toNoneimmediately before the race, making the test self-contained regardless of execution order. - Extracted (post-tag):
Aws_tlsmoved out to the standalonehttps-eiopackage. The exact same TLS wrapper (rounds 4–6 above, plus the CA-bundle logic) turned out to also be duplicated byte-for-byte in obs-loki-eio'sObs_loki_tls, obs-prometheus-eio'sObs_prometheus_tls, and Sun's in-treeKafka_service_tls— the round-4 RNG-seeding bug had to be manually ported across all four.Aws_tlsis deleted;aws_http.mlnow depends onhttps-eiodirectly.https-eioalso replaces the hand-rolled, Linux/macOS-only CA-bundle path list with the maintainedca-certspackage. The TLS regression tests (real handshake, concurrent-domain cache race) moved tohttps-eio's own test suite. - Proven live (post-tag): a real
STS GetCallerIdentitycall, signed by this package, was accepted by real AWS (test/test_aws_live.ml, gated byAWS_EIO_LIVE=1). First confirmation this package is correct against the actual service, not just internally consistent against AWS's conformance-suite vectors and local mock servers. - API change (post-tag, found while designing
s3-eio):requestandsigned_requestnow return response headers on success —(int * (string * string) list * string, Aws_error.t) result, not(int * string, Aws_error.t) result.read_responsealways parsed headers internally (used for retry classification) but discarded them before returning to the caller; a client needingContent-Length/ETag/Last-Modifiedfrom an HTTPHEADresponse — the entire point ofHEAD— had no way to get them. Error responses are unchanged (Http_error of int * string, no headers) to keep this a narrow, low-risk addition rather than touching the signing/retry logic at all. Every existing caller (Aws_credentials's STS/IMDS/container-credential calls, this package's own tests) updated to the 3-tuple; a new regression test (test_response_headers_are_returned) pins the contract down. - API change (post-tag):
Aws_sigv4.request(the type) renamed toAws_sigv4.signing_request— it shared a bare name withAws_http.request(the function) in a sibling module of the same package, an ambiguity in the same class the localstsvariable →to_signrename already fixed elsewhere inAws_sigv4. No external package referencedAws_sigv4directly at the time of this rename. Also renamed internally:canonical_hdrs→canonical_headers_, a localu→parsed_uriinAws_http.request, and 4 test helpers narrowed from the fullEio.Stdenv.tto just thenetcapability they actually use.
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
On This Page