Skip to content

Make LoginCredentialProvider token persistence best-effort#692

Merged
adam-fowler merged 1 commit into
soto-project:mainfrom
sebsto:fix/login-credential-write-failure
Jun 7, 2026
Merged

Make LoginCredentialProvider token persistence best-effort#692
adam-fowler merged 1 commit into
soto-project:mainfrom
sebsto:fix/login-credential-write-failure

Conversation

@sebsto

@sebsto sebsto commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #691.

Summary

  • LoginCredentialProvider.getCredential() was treating a failed disk write of the refreshed token as fatal, throwing away the perfectly valid credentials it just received from the refresh endpoint.
  • In sandboxed environments (SwiftPM command plugins, App Sandbox containers, etc.) where reads from ~/.aws/login/cache/ are allowed but writes are blocked, every refresh path failed with No credential provider found.
  • This change wraps tokenFileManager.saveToken(...) in a do/catch, logs at warning level, and returns the freshly-issued in-memory credentials regardless of whether the disk save succeeded. Next invocation will re-refresh — same behavior as today when the cached credentials legitimately expire.

Test plan

  • Added getCredentialsDespiteWriteFailure regression test that:
    • Seeds an expired token in a temp cache directory.
    • chmod 555s the directory to simulate a sandbox blocking writes.
    • Runs the refresh against a mock HTTP client returning fresh credentials.
    • Asserts the fresh credentials are returned even though the on-disk save failed.
  • swift test --filter LoginCredentialProviderTests — all 10 tests pass (9 existing + 1 new).
  • swift build — clean (one pre-existing NonBlockingFileIO deprecation warning, unrelated to this change).

When a refreshed token cannot be written to disk (e.g. inside a SwiftPM
plugin sandbox where writes outside the package directory are blocked),
the in-memory credentials returned by the refresh endpoint were being
discarded along with the write error. The chain then surfaced as
"No credential provider found" even though a valid credential had just
been fetched.

Wrap the saveToken call in do/catch and log at warning level, so the
caller still gets the freshly-issued credentials for the current
session and only re-refreshes on the next invocation.
@sebsto
sebsto requested review from 0xTim and adam-fowler as code owners June 4, 2026 10:39
@codecov

codecov Bot commented Jun 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 60.00000% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.11%. Comparing base (2c78153) to head (7aeea27).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...ore/Credential/Login/LoginCredentialProvider.swift 60.00% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #692      +/-   ##
==========================================
- Coverage   81.13%   81.11%   -0.03%     
==========================================
  Files          91       91              
  Lines        6580     6587       +7     
==========================================
+ Hits         5339     5343       +4     
- Misses       1241     1244       +3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@sebsto

sebsto commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

The Linux CI failure is in RotatingCredentialProviderTests.testAlwaysGetNewTokenIfTokenLifetimeForUseIsShorterThanLifetime — a timing-dependent test in RotatingCredentialProvider, which this PR does not touch (the change is isolated to LoginCredentialProvider's save-token error handling). macOS passed, only Linux Swift 6.3 failed (and 6.1/6.2 were cancelled by the matrix fail-fast). The test passes locally.

Looks like a flake — could you re-run the failed Linux jobs? I don't have permission to do it from my fork.

@adam-fowler
adam-fowler enabled auto-merge (squash) June 6, 2026 07:07
@adam-fowler
adam-fowler merged commit 53f8b90 into soto-project:main Jun 7, 2026
16 of 24 checks passed
sebsto added a commit to awslabs/swift-aws-lambda-runtime that referenced this pull request Jun 26, 2026
This PR delivers the v2 plugin system, replacing the legacy
single-purpose `archive` plugin with three focused SwiftPM command
plugins that cover the end-to-end developer experience:

- **`lambda-init`** — Scaffold a new Lambda function from a template
- **`lambda-build`** — Compile and package for Amazon Linux 2023 (via
Docker or Apple container)
- **`lambda-deploy`** — Deploy to AWS Lambda (create, update, or delete)

The legacy `archive` command is preserved as a deprecated passthrough to
`lambda-build`.

## Quick Start

```bash
# Create a new project
swift package init --type executable --name MyLambda

# Scaffold a Lambda function
swift package lambda-init --allow-writing-to-package-directory

# Build for Amazon Linux
swift package --allow-network-connections docker lambda-build

# Deploy to AWS
swift package --allow-network-connections all:443 lambda-deploy

# Delete when done
swift package --allow-network-connections all:443 lambda-deploy --delete
```

## Key Changes

### Architecture
- All plugins are thin wrappers that spawn a shared
`AWSLambdaPluginHelper` executable
- The helper dispatches to `Initializer`, `Builder`, or `Deployer` based
on `argv[1]`
- AWS API calls use generated service clients (Lambda, IAM, S3, STS)
built on SotoCore

### Builder (`lambda-build`)
- Default base image changed from `amazonlinux2` to `amazonlinux2023`
- AL2 blanket deprecation warning removed; targeted warning only when
AL2 explicitly chosen
- New `--cross-compile` option (replaces `--container-cli`): `docker`,
`container`, `swift-static-sdk`, `custom-sdk`
- Default binary stripping with `-Xlinker -s` and `--no-strip` opt-out
- `--output-directory` accepted as deprecated alias for `--output-path`
- Container CLI existence check with helpful install URLs

### Deployer (`lambda-deploy`)
- Creates/updates/deletes Lambda functions with full IAM role lifecycle
- Auto-creates IAM role with `AWSLambdaBasicExecutionRole`
- S3 staging for archives > 50 MB
- Function URL support with IAM auth (account-scoped, not
world-accessible)
- Auto-detects `FunctionURLRequest` usage in source code to enable URL
without explicit `--with-url`
- Ready-to-use invocation commands in deploy output

### Initializer (`lambda-init`)
- Detects the actual entry point file (supports both
`Sources/main.swift` and `Sources/<Name>/<Name>.swift`)
- Backs up existing file before overwriting

### Dependencies
- Added `soto-core` for AWS credential management, SigV4 signing, and
HTTP transport
- Removed vendored crypto/signer/HTTP code under `Vendored/`
- Generated AWS service clients committed to the repository
(maintainer-run generation script)

### Backward Compatibility
- `swift package archive` preserved as deprecated alias (emits warning,
delegates to `lambda-build`)
- `--container-cli` accepted as deprecated alias for `--cross-compile`
- `--output-directory` accepted as deprecated alias for `--output-path`
- All original `archive` CLI options continue to work

## Documentation
- Updated DocC articles, tutorials, and quick-setup guide
- Updated all example READMEs with new plugin commands
- SAM/CDK examples preserved with their respective deployment tools

## Known Limitations
- Static Linux SDK CI uses `--build-system native`. On Swift 6.4 the new
default build system (Swift Build) fails to statically link
`AWSLambdaPluginHelper`: SotoCore transitively pulls in two vendored
BoringSSL copies (swift-crypto's `CCryptoBoringSSL` and swift-nio-ssl's
`CNIOBoringSSL`), and Swift Build emits duplicate C++ symbols at link
time. The legacy build system links the same package cleanly, so the CI
static build is pinned to `--build-system native` until the upstream
issue is fixed: https://github.com/swiftlang/swift-build/issues/1485.
- The `nightly-main` (6.5-dev) Linux job is expected to fail. The
6.5-dev compiler crashes while compiling `AWSLambdaRuntimeTests` (a SIL
verification error in the `OwnershipModelEliminator` pass on a `Mutex`
captured by a nested `Task` inside a task group). This is a toolchain
bug, not a problem in this PR: the same code compiles on 6.1 through
6.4. Reported upstream with a minimal reproducer:
swiftlang/swift#90211. `nightly-main` tracks
an unreleased toolchain and is not a release target, so we are not
working around it here.
- SwiftPM prompts for network permission on first run (known [SPM issue
swiftlang/swift#9763](swiftlang/swift-package-manager#9763))
- The shared documentation soundness check is temporarily disabled
(`docs_check_enabled: false`). A regression in
`swiftlang/github-workflows` (#282) passes the `.spi.yml` documentation
target to `--target` with literal quotes, so the check fails with `no
target named '"AWSLambdaRuntime"'`. Pinning the workflow version does
not help because the reusable workflow always fetches its
`check-docs.sh` from `main`. Re-enable once the upstream fix lands:
swiftlang/github-workflows#290 (tracked in
swiftlang/github-workflows#291).

The SotoCore `LoginCredentialProvider` token-persistence issue in the
SwiftPM sandbox is now resolved upstream
([soto-core#692](soto-project/soto-core#692),
released in soto-core 7.14.0), and this PR already requires `from:
"7.14.0"`.

## Testing
- Unit tests for BuilderConfiguration and DeployerConfiguration argument
parsing
- Property-based tests for correctness properties (alias equivalence,
cross-compile round-trip, mutual exclusion, bucket naming, archive
threshold, AL2 warning logic)
- End-to-end integration test script (`scripts/integration-test.sh`)
- Manually tested: full create → invoke → update → delete lifecycle on
AWS

---------

Co-authored-by: Sebastien Stormacq <[email protected]>
Co-authored-by: Claude Opus 4.6 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

LoginCredentialProvider: token refresh fails in sandboxed environments when cache write is blocked

2 participants