Skip to content

Add AWS Console browser sign-in for S3 - #18331

Draft
whatever60 wants to merge 7 commits into
iterate-ch:masterfrom
whatever60:feature/aws-console-signin
Draft

Add AWS Console browser sign-in for S3#18331
whatever60 wants to merge 7 commits into
iterate-ch:masterfrom
whatever60:feature/aws-console-signin

Conversation

@whatever60

@whatever60 whatever60 commented Jul 30, 2026

Copy link
Copy Markdown

Summary

Add a bundled Amazon S3 (AWS Console Sign-In) profile with native browser authentication. It requires neither AWS CLI nor long-lived access keys.

  • Run the AWS Sign-In authorization-code flow with PKCE and DPoP.
  • Keep short-lived S3 access credentials in memory only.
  • Persist only the rotating refresh token and DPoP private key through Cyberduck's existing secure password store.
  • Refresh credentials automatically for the AWS browser-session lifetime, then reopen the browser when sign-in is required again.
  • Display and pin the selected AWS account and IAM user or role to the bookmark.
  • Return a successful loopback callback page after browser authentication.

Motivation

The existing S3 profile expects access keys, while the IAM Identity Center flow requires organization-side Identity Center configuration. AWS Console Sign-In lets a user authenticate with an existing AWS Console identity without creating persistent access keys, installing AWS CLI, or having IAM Identity Center access.

Implementation notes

  • A dedicated S3 profile selects the native sign-in credential strategy without changing existing S3 authentication modes.
  • Authorization uses an ephemeral loopback port bound to 127.0.0.1, exact state validation, PKCE S256, and ES256 DPoP.
  • The loopback listener is bound before the authorization URL is generated, avoiding a close/rebind race.
  • AWS access key ID, secret access key, and session token are never written to a bookmark or password store.
  • The initial ID token pins the AWS account and normalized IAM user or role. A later sign-in with another identity is rejected for that bookmark.
  • The callback response is completed before the application resumes, so the browser shows Login successful instead of a refused loopback connection.

Validation

  • mvn -pl oauth,s3 -am -DskipTests package: all 8 reactor modules passed on macOS and Windows.
  • Focused native sign-in tests: 3 passed on macOS and Windows.
  • Broader OAuth/S3 reactor suite: 49 S3 tests passed on macOS; 52 S3 tests passed in an elevated Windows 11 ARM64 VM. Three Docker-only integration tests were excluded.
  • Packaged macOS and Windows GUI smoke tests: native browser sign-in, identity pinning, live listing of 59 S3 buckets, full app exit/relaunch, and reconnect without reopening the browser all passed.
  • Password-store audit confirmed that temporary AWS access credentials are not persisted; only the refresh token and DPoP key are retained using Cyberduck's standard secure-storage path.
  • git diff --check origin/master...HEAD passed.

Copilot AI review requested due to automatic review settings July 30, 2026 05:33
@cla-assistant

cla-assistant Bot commented Jul 30, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@cla-assistant

cla-assistant Bot commented Jul 30, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@whatever60
whatever60 marked this pull request as ready for review July 30, 2026 05:38
@whatever60
whatever60 requested a review from a team as a code owner July 30, 2026 05:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new “Amazon S3 (AWS Console Sign-In)” connection profile that authenticates via AWS CLI v2 browser sign-in, exporting short-lived S3 credentials into memory and pinning the AWS identity to the bookmark. It also updates macOS and Windows UI logic so password/keychain-save controls are disabled for protocols that don’t use stored credentials.

Changes:

  • Add s3-login protocol + bundled profile, and implement AWSConsoleLoginCredentialsStrategy that runs aws login / aws configure export-credentials and pins identity via sts get-caller-identity.
  • Wire the new credentials strategy into S3Session and avoid redundant STS identity calls for the new protocol.
  • Update macOS/Windows bookmark & connection controllers and LoginOptions so keychain-save controls are disabled for credentialless protocols (and add tests for LoginOptions + the new AWS strategy).

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
windows/src/main/csharp/ch/cyberduck/ui/controller/ConnectionController.cs Moves save-password UI state initialization into Update() so it stays in sync with protocol/options.
windows/src/main/csharp/ch/cyberduck/ui/controller/BookmarkController.cs Sets credentials “saved” flag on protocol change based on updated login options.
osx/src/main/java/ch/cyberduck/ui/cocoa/controller/ConnectionController.java Updates keychain checkbox state via bookmark observer to reflect current options/credentials.
osx/src/main/java/ch/cyberduck/ui/cocoa/controller/BookmarkController.java Sets credentials “saved” flag on protocol change based on updated login options.
core/src/main/java/ch/cyberduck/core/LoginOptions.java Recomputes keychain and save based on protocol capabilities + preference.
core/src/test/java/ch/cyberduck/core/LoginOptionsTest.java Adds coverage for LoginOptions.configure() keychain/save behavior.
s3/src/main/java/ch/cyberduck/core/signin/AWSConsoleLoginCredentialsStrategy.java New AWS CLI browser sign-in credentials strategy with in-memory token cache + identity pinning.
s3/src/test/java/ch/cyberduck/core/signin/AWSConsoleLoginCredentialsStrategyTest.java Tests for export/login behavior, memory caching, error detail hygiene, and identity pinning.
s3/src/main/java/ch/cyberduck/core/s3/S3Session.java Uses the new strategy for s3-login and skips STS identity call already handled by CLI strategy.
s3/src/main/java/ch/cyberduck/core/s3/S3LoginProtocol.java New protocol identifier s3-login and disables CredentialsConfigurator.
profiles/default/Amazon S3 (AWS Console Sign-In).cyberduckprofile Bundled profile definition for the new s3-login protocol and region list.
osx/build.xml Sets JVM process launch mechanism to FORK in the packaged macOS runtime args.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread windows/src/main/csharp/ch/cyberduck/ui/controller/BookmarkController.cs Outdated
Comment thread osx/src/main/java/ch/cyberduck/ui/cocoa/controller/BookmarkController.java Outdated
Copilot AI review requested due to automatic review settings July 30, 2026 05:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

@dkocher dkocher added the s3 AWS S3 Protocol Implementation label Jul 30, 2026

@dkocher dkocher left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your submission! Not sure we want to have this integration relying on command line tool invocations generally. We worked to remove the dependency on AWS CLI with #13377.

Comment thread osx/build.xml Outdated
Comment thread s3/src/main/java/ch/cyberduck/core/s3/S3Session.java Outdated
Comment thread s3/src/main/java/ch/cyberduck/core/s3/S3Session.java Outdated
Comment thread windows/src/main/csharp/ch/cyberduck/ui/controller/BookmarkController.cs Outdated
Comment thread osx/src/main/java/ch/cyberduck/ui/cocoa/controller/BookmarkController.java Outdated
Comment thread core/src/main/java/ch/cyberduck/core/LoginOptions.java Outdated
@dkocher
dkocher marked this pull request as draft August 1, 2026 12:51
Copilot AI review requested due to automatic review settings August 2, 2026 08:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Suppressed comments (1)

s3/src/main/java/ch/cyberduck/core/signin/AWSConsoleLoginCredentialsStrategy.java:163

  • parse treats missing/blank SessionToken the same as other parse failures and throws the generic “AWS CLI 2.32 or later is required” message. When the CLI exports long‑lived access keys (no session token), the failure reason is different and the current message is misleading for users who do have a new CLI but used the wrong profile/credential type.

Consider distinguishing the "permanent credentials" case with a specific error detail (and optionally an explicit message for expired credentials) so users know they must use browser sign-in / short‑lived session credentials.

            final String accessKey = value.path("AccessKeyId").asText();
            final String secretKey = value.path("SecretAccessKey").asText();
            final String sessionToken = value.path("SessionToken").asText();
            final long expiration = OffsetDateTime.parse(value.path("Expiration").asText()).toInstant().toEpochMilli();
            if(StringUtils.isAnyBlank(accessKey, secretKey, sessionToken) || expiration <= System.currentTimeMillis()) {
                throw failure();
            }

@whatever60
whatever60 force-pushed the feature/aws-console-signin branch from 9584466 to a226a1f Compare August 2, 2026 23:01
Copilot AI review requested due to automatic review settings August 2, 2026 23:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

s3/src/main/java/ch/cyberduck/core/signin/AWSConsoleLoginCredentialsStrategy.java:215

  • The expiry calculation subtracts a fixed 5 minute buffer from expiresIn; if the server ever returns expiresIn <= 300s, the computed expiry will be <= now and the tokens will be immediately expired, potentially causing an authorization/refresh loop. Clamp the buffer so the expiry is always in the future (at least by a small amount).
        return new TemporaryAccessTokens(accessKey, secretKey, sessionToken,
                System.currentTimeMillis() + expires * 1000L - 5L * 60L * 1000L);

oauth/src/main/java/ch/cyberduck/core/oauth/LoopbackOAuth2AuthorizationCodeProvider.java:75

  • The state listener registered with OAuth2TokenListenerRegistry is only removed on a successful notify(). If the flow errors before notify() or the user cancels/timeout occurs, the listener remains in the global registry map, causing a memory leak and potential unexpected callbacks later. Consider adding an explicit unregister/remove API on OAuth2TokenListenerRegistry and calling it from a finally block here (and in other prompt implementations) when the flow terminates without a notify().
        final CountDownLatch signal = new CountDownLatch(1);
        final AtomicReference<String> authenticationCode = new AtomicReference<>();
        OAuth2TokenListenerRegistry.get().register(expectedState, code -> {
            if(StringUtils.isBlank(code)) {
                signal.countDown();
            }
            else {
                authenticationCode.set(code);
            }
        });

Copilot AI review requested due to automatic review settings August 3, 2026 07:36
@whatever60
whatever60 marked this pull request as ready for review August 3, 2026 07:40
@whatever60
whatever60 requested a review from dkocher August 3, 2026 07:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (2)

s3/src/main/java/ch/cyberduck/core/signin/AWSConsoleLoginCredentialsStrategy.java:288

  • validateIdentity(host, idToken) decodes the JWT without any sanity checks on the sub format. If AWS ever returns an unexpected subject (or the token is corrupted), this can pin an invalid identity value into the bookmark and later block logins. Add minimal validation that sub is a well‑formed AWS ARN and that the extracted account id is 12-digit numeric before accepting it.
    static void validateIdentity(final Host host, final String idToken) throws LoginFailureException {
        try {
            final String arn = JWT.decode(idToken).getSubject();
            validateIdentity(host, StringUtils.substringBetween(arn, "::", ":"), arn);
        }

oauth/src/main/java/ch/cyberduck/core/oauth/LoopbackOAuth2AuthorizationCodeProvider.java:61

  • LoopbackOAuth2AuthorizationCodeProvider now has a new overload (Function-based URL builder), dynamic loopback binding, and a different callback response path (200 "Login successful" vs 302). There are oauth module tests for other providers, but none that exercise this updated loopback flow, making regressions in state validation/redirect handling hard to catch.
    public String prompt(final Host bookmark, final LoginCallback prompt,
                         final Function<String, String> authorizationCodeUrl, final String state) throws BackgroundException {
        return this.prompt(bookmark, prompt, authorizationCodeUrl, null, state);
    }

@whatever60

Copy link
Copy Markdown
Author

Hi David, thank you for the detailed review. I now have a better understanding of this repo and its design principles. I agree that removing CLI is the right call that makes feature impls more consistent and self-contained.

I have removed the direct AWS CLI invocation and replaced it with native AWS auth flow using PKCE and DPoP, by reusing Cyberduck's existing components like loopback OAuth, S3 credential strategy, connection profile, and secure pw store.

I also removed the unrelated UI, keychain, and process launch changes, and now select the auth strategy through a connection profile property rather than concrete protocol class checks.

This PR is ready for another review. Thanks again and I would appreciate another look.

Yiming

@whatever60
whatever60 requested a lite review from Copilot August 7, 2026 20:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (1)

oauth/src/main/java/ch/cyberduck/core/oauth/LoopbackOAuth2AuthorizationCodeProvider.java:101

  • The loopback callback currently treats a request as accepted as long as the state matches and the listener is notified, even if the code parameter is missing/blank. In that case the handler will still return the “Login successful” page (when using the ephemeral 127.0.0.1 redirect), but the application will later treat the login as canceled because authenticationCode is null. This is inconsistent UX and makes it harder to diagnose callback failures/denials.

Consider distinguishing “listener notified” from “successful authorization code received”, and only show the success page when code is non-blank (still notifying the listener so the waiting thread can continue).

                    final boolean accepted = StringUtils.equals(expectedState, state) && OAuth2TokenListenerRegistry.get().notify(state, code);
                    try {
                        if(!accepted) {
                            exchange.sendResponseHeaders(400, 0);
                        }

@dkocher dkocher added this to the 9.6 milestone Aug 17, 2026
@dkocher
dkocher marked this pull request as draft August 18, 2026 06:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

s3 AWS S3 Protocol Implementation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants