Symptom
With a valid ~/.aws/credentials containing a well-formed [default] profile:
[default]
aws_access_key_id = AKIA...
aws_secret_access_key = ...
AwsEnvironment.default(...) with AWS_PROFILE=default fails with:
smithy4s.aws.AwsCredentialsFileException: Profile `default` was not found in the credentials file.
The profile is there. The message is wrong, and it points the user at the wrong
thing entirely — you go and re-check the file, which looks fine.
Reproduced on 0.19.12 and on series/0.19. Found while smoke-testing #1596; the
workaround was to bypass the file and export AWS_ACCESS_KEY_ID /
AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN instead (note that
AwsCredentialsProvider only consults fromEnv when AWS_PROFILE is unset,
so the file path can't be skipped while that variable is set).
Cause
Two separate problems compound in
modules/aws-http4s/src/smithy4s/aws/AwsCredentialsFile.scala.
1. Any parse failure is reported as "profile not found"
processFileLines wraps the whole parse in Either.catchNonFatal and maps every
throwable to a single generic exception. If parsing dies partway through, the
profiles after that point are simply absent from the resulting map, and
fromDisk then reports the requested profile as missing rather than surfacing
the parse failure. The real cause is discarded.
The cause is attached to the AwsCredentialsFileException for the parse case,
but the "not found" path constructs a fresh exception with no cause, so nothing
reaches the user.
2. inProfile throws on any line without =
val parts = head.split("=")
val key = parts(0).trim().toLowerCase
val value = parts(1).trim() // ArrayIndexOutOfBoundsException when there is no '='
Blank lines are filtered out beforehand (.filter(_.trim.nonEmpty)), so they are
safe. But anything else that isn't key = value throws — and, via (1), is
misreported. Candidates seen in real files:
- a bare word / stray token on its own line
- a key with no value and no
=
- non-comment decoration
Related: split("=") also truncates values that legitimately contain =
(session tokens are base64 and frequently contain = padding). split("=", 2)
is the correct form. This one silently corrupts credentials rather than throwing,
which is arguably worse — it would surface as an opaque signature mismatch from
AWS.
Suggested fix
split("=", 2), so values containing = survive intact.
- Skip or explicitly reject lines that contain no
=, instead of indexing blindly.
If rejecting, name the line number and content.
- Preserve the underlying cause when a profile lookup fails after a parse error —
or better, fail loudly at parse time instead of degrading to a lookup miss.
- Consider distinguishing "the file could not be parsed" from "the file parsed
but has no such profile" in the exception type.
Tests worth adding
AwsCredentialsFileTest currently covers well-formed input. Add cases for:
- a session token whose value contains
= (round-trips unchanged)
- a file with a malformed line before the requested profile (error mentions the
malformed line, not a missing profile)
- a profile that exists but is missing
aws_secret_access_key (already handled by
credentialsFromMap, but worth pinning alongside the above)
Found while working on #1596.
Symptom
With a valid
~/.aws/credentialscontaining a well-formed[default]profile:AwsEnvironment.default(...)withAWS_PROFILE=defaultfails with:The profile is there. The message is wrong, and it points the user at the wrong
thing entirely — you go and re-check the file, which looks fine.
Reproduced on 0.19.12 and on
series/0.19. Found while smoke-testing #1596; theworkaround was to bypass the file and export
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY/AWS_SESSION_TOKENinstead (note thatAwsCredentialsProvideronly consultsfromEnvwhenAWS_PROFILEis unset,so the file path can't be skipped while that variable is set).
Cause
Two separate problems compound in
modules/aws-http4s/src/smithy4s/aws/AwsCredentialsFile.scala.1. Any parse failure is reported as "profile not found"
processFileLineswraps the whole parse inEither.catchNonFataland maps everythrowable to a single generic exception. If parsing dies partway through, the
profiles after that point are simply absent from the resulting map, and
fromDiskthen reports the requested profile as missing rather than surfacingthe parse failure. The real cause is discarded.
The
causeis attached to theAwsCredentialsFileExceptionfor the parse case,but the "not found" path constructs a fresh exception with no cause, so nothing
reaches the user.
2.
inProfilethrows on any line without=Blank lines are filtered out beforehand (
.filter(_.trim.nonEmpty)), so they aresafe. But anything else that isn't
key = valuethrows — and, via (1), ismisreported. Candidates seen in real files:
=Related:
split("=")also truncates values that legitimately contain=(session tokens are base64 and frequently contain
=padding).split("=", 2)is the correct form. This one silently corrupts credentials rather than throwing,
which is arguably worse — it would surface as an opaque signature mismatch from
AWS.
Suggested fix
split("=", 2), so values containing=survive intact.=, instead of indexing blindly.If rejecting, name the line number and content.
or better, fail loudly at parse time instead of degrading to a lookup miss.
but has no such profile" in the exception type.
Tests worth adding
AwsCredentialsFileTestcurrently covers well-formed input. Add cases for:=(round-trips unchanged)malformed line, not a missing profile)
aws_secret_access_key(already handled bycredentialsFromMap, but worth pinning alongside the above)Found while working on #1596.