Skip to content

AwsCredentialsFile reports "profile not found" for profiles that are present, hiding the real parse error #2007

Description

@kubukoz

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

  1. split("=", 2), so values containing = survive intact.
  2. Skip or explicitly reject lines that contain no =, instead of indexing blindly.
    If rejecting, name the line number and content.
  3. 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.
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    AWS supportIssues related to AWS support

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions