Skip to content

feat(aws): add support for AWS IAM Identity Center (SSO)#622

Open
rr3khan wants to merge 7 commits into
1Password:mainfrom
rr3khan:rr3khan/feat/aws-sso-210
Open

feat(aws): add support for AWS IAM Identity Center (SSO)#622
rr3khan wants to merge 7 commits into
1Password:mainfrom
rr3khan:rr3khan/feat/aws-sso-210

Conversation

@rr3khan

@rr3khan rr3khan commented Jul 6, 2026

Copy link
Copy Markdown

Overview

Follow-up to #598, which added AWS IAM Identity Center (SSO) support to the AWS plugin but was closed. See the #598 description for the full design; in short, the plugin reads the access token cached by aws sso login and exchanges it for short-lived role credentials via sso:GetRoleCredentials, supporting both the legacy sso_start_url and consolidated sso_session config forms.

On top of that branch, this PR adds:

  • Optional: true on both credential usages (Access Key and SSO Profile) across all five executables, addressing the review concern that SSO-only users, who have no IAM access key at all, would otherwise be locked out.
  • e8accd8
  • fac5e1e

Local testing results

Tested against a live IAM Identity Center org with op 2.30.0 and a local plugin build:

  • Full SSO flow works: op plugin init aws imports the SSO profile detected in ~/.aws/config, and op plugin run -- aws sts get-caller-identity --profile <sso-profile> returns the assumed-role ARN. A repeat invocation is served from the plugin's encrypted credential cache without a round-trip to the SSO endpoint.
  • The importer handled the consolidated sso_session form, including a session whose name is the start URL itself (what aws configure sso writes if you paste the URL at the session-name prompt).
  • Both credential types coexist on the plugin: an Access Key item and an SSO Profile item linked side by side, with the right provisioner engaging per profile.

Two limitations observed, both requiring CLI-side changes rather than plugin changes:

  • op 2.30.0's plugin init does not yet honor Optional: it still requires linking an item for every credential type, so SSO-only users cannot complete init without an access key. The flag is declared now so a future init flow can honor it.
  • The init prompt labels every credential step "Locate your AWS Access Key", including the SSO Profile step. The structure validation runs against the correct credential type, only the label is wrong.

Not included here: the pre-existing SDK findings deferred in b90170b's commit message (F-2, F-6, F-7, F-9, F-15). Worth noting that F-2 (GetCredentialType ignores credentialName) and F-6 (MarshalJSON reads Credentials[0]) become reachable now that this is the first plugin to expose two credential types. Happy to pick those up in a follow-up PR.

Type of change

  • Improved an existing plugin

Related Issue(s)

How To Test

The steps from #598 still apply:

  1. Configure an SSO profile in ~/.aws/config (either form) and run aws sso login --profile <name>.
  2. op plugin init aws, link an SSO Profile item (the importer will offer the profile it finds in ~/.aws/config).
  3. op plugin run -- aws sts get-caller-identity --profile <name> prints the assumed-role ARN.
  4. Re-run within the credential TTL: the second invocation hits the plugin's encrypted cache.
  5. (Optional) Wipe ~/.aws/sso/cache/<sha1>.json and re-run: the plugin surfaces "AWS SSO token is missing or expired; run aws sso login --profile <name> and try again".

Note that on op 2.30.0 init will also require linking an Access Key item (see limitations above).

Changelog

Authenticate the AWS CLI with AWS IAM Identity Center (SSO) profiles using cached SSO tokens.

rjwhitworth and others added 5 commits May 9, 2026 02:57
Adds an SSO Profile credential type. The plugin reads the access token
cached by `aws sso login` from ~/.aws/sso/cache/<sha1>.json and exchanges
it for short-lived role credentials via sso:GetRoleCredentials. Supports
both the legacy `sso_start_url` form and the consolidated `sso_session`
form. Role credentials get cached in the plugin's encrypted store, so
subsequent invocations within the credential TTL skip the remote call.

Access Key and SSO Profile coexist on the same `aws` executable. Each
provisioner yields silently when the active profile is configured for
the other type, so users can link both items against the same plugin
and the right one runs per invocation.

The importer scans ~/.aws/config for both forms and surfaces a candidate
per SSO-bearing profile. cdk, eksctl, awslogs, and sam also accept the
SSO Profile credential.

`aws sso login`, `aws sso logout`, `aws configure sso`, `aws configure
sso-session`, and the read-only `aws configure list/list-profiles/get/
set` subcommands skip credential provisioning so they can run without
an active session.

SDK changes:
- Allow plugins to expose more than one credential type. The op runtime
  already accepts this; the validator was the only block.
- Downgrade "has at least 1 field that is secret" from error to warning
  so credential types whose secret lives in an external token cache
  (SSO, OAuth, gcloud) can be represented without a placeholder field.

aws-sdk-go-v2/credentials and aws-sdk-go-v2/service/sso were already on
the dependency graph via aws-vault/v7; both move from indirect to
direct.

Resolves: 1Password#210
Address findings from a pre-PR security review. Each fix is local to the
AWS SSO net-new code introduced in the previous commit; pre-existing SDK
bugs surfaced by the same review are deferred to a follow-up PR.

Importer (plugins/aws/sso_importer.go):
- Validate sso_start_url (https + non-empty host)
- Regex-check sso_account_id (12 digits) and sso_region
- Reject NUL bytes mid-value
- Refuse non-regular, symlinked, or non-owned AWS_CONFIG_FILE overrides
- Match SDK ~/ prefix convention (bare ~root no longer silently joined)
- Use ini.LoadSources directly with strict botocore-parity options:
  KeyValueDelimiters="=", IgnoreContinuation=true, Loose=true

Provisioner (plugins/aws/sso_provisioner.go):
- assertSSOTokenCacheSafe rejects symlinks, non-regular files, group/world
  readable modes, and files not owned by the current uid before passing
  the path to ssocreds.New
- translateSSORetrieveError whitelists known smithy codes (Unauthorized,
  Forbidden, ResourceNotFound, TooManyRequests); unknown codes get a
  generic plugin-controlled message so server-controlled error text
  doesn't reach user-visible output
- Wrap sso:GetRoleCredentials in context.WithTimeout(30s)

SDK validator (sdk/schema/credential_type.go):
- Replace the previous severity downgrade with an opt-in
  AllowsExternalSecretCache flag on CredentialType. The "must have at
  least one secret field" check is restored to Error severity globally;
  only the SSO Profile (whose bearer token lives in the AWS SDK's
  external cache) opts out

Tests (plugins/aws/sso_*_test.go):
- Hostile-input cases for the importer (non-HTTPS, file:// scheme, short
  account ID, malformed region, NUL byte, malformed section header,
  duplicate-section last-wins)
- Direct unit tests for assertSSOTokenCacheSafe and
  validateExternalConfigPath covering symlink, world-readable,
  directory, non-existent
- Smithy-error translation table with a token-leak guard that asserts no
  JSON-key-shaped or JWT-shaped fragment from a hostile server message
  ever appears in the translated user-visible error

Deferred to follow-up PR (pre-existing code, out of scope here):
- F-2: plugins/registry.go:50-60 GetCredentialType ignores credentialName
- F-6: sdk/schema/plugin.go:116-134 MarshalJSON truncates to Credentials[0]
- F-7: plugins/aws/cli_provisioner.go:28-53 strip-by-value mis-routing
- F-9: sdk/importer/helpers.go:41-53 SanitizeNameHint byte-truncation
- F-15: plugins/aws/sts_provisioner.go:399-405 log.SetOutput global state
Broaden sso_region validation so us-gov-* profiles import correctly, and
reuse ssoLoginCommand for UnauthorizedException so named profiles get the
right aws sso login hint.

Co-authored-by: Cursor <cursoragent@cursor.com>
@rr3khan rr3khan changed the title Rr3khan/feat/aws sso 210 feat(aws): add support for AWS IAM Identity Center (SSO) Jul 6, 2026
rr3khan added 2 commits July 6, 2026 18:58
The importer's region validation required a two-letter prefix, rejecting
eusc-de-east-1 (AWS European Sovereign Cloud, present in botocore's
endpoint data). Botocore itself does no region validation, so this check
exists only to reject values that could not be a region at all; loosen
the prefix to two-or-more letters and keep the charset and structure
anchors as the hostile-input defense. Same failure class as the earlier
GovCloud broadening (781ba27).
The original SSO guard inside GetTemporaryCredentialsProviderForProfile
was removed when top-level SSO detection moved to STSProvisioner.Provision.
That top-level check cannot see a role chain's source profile: aws-vault
keeps the source's sso_* fields in the nested SourceProfile config, so a
profile with role_arn + source_profile pointing at an SSO-configured
profile slipped past both provisioners' yield checks and fell through to
an access-key lookup, failing later with a misleading "no long lived
access key pair found" instead of a clear message.

Restore the check inside GetTemporaryCredentialsProviderForProfile, where
it is evaluated at every level of the source_profile recursion. When the
active profile itself is SSO-configured, Provision has already yielded to
the SSO Profile provisioner before this function is reached, so the new
error only fires for the unsupported chained case.
@rr3khan rr3khan marked this pull request as ready for review July 6, 2026 23:00
@scottisloud scottisloud self-requested a review July 6, 2026 23:04

@scottisloud scottisloud 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.

Overall things look great. Going to bring this to the team to talk about the potential changes required to op to accommodate certain use-cases and whether those changes are a pre-requisite to shipping this plugin, and for a second set of eyes on the new provisioner.

Nothing is immediately standing out to me, and I'm not inclined to worry about the use cases requiring changes to op necessarily, since this is a net-improvement overall.

Provisional approval, will have a second set of eyes on this before actual approval and merge.

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.

AWS - SSO authentication provider

3 participants