feat(aws): add support for AWS IAM Identity Center (SSO)#622
Open
rr3khan wants to merge 7 commits into
Open
Conversation
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>
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.
scottisloud
reviewed
Jul 6, 2026
scottisloud
left a comment
Contributor
There was a problem hiding this comment.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 loginand exchanges it for short-lived role credentials viasso:GetRoleCredentials, supporting both the legacysso_start_urland consolidatedsso_sessionconfig forms.On top of that branch, this PR adds:
Optional: trueon 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.Local testing results
Tested against a live IAM Identity Center org with op 2.30.0 and a local plugin build:
op plugin init awsimports the SSO profile detected in~/.aws/config, andop 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.sso_sessionform, including a session whose name is the start URL itself (whataws configure ssowrites if you paste the URL at the session-name prompt).Two limitations observed, both requiring CLI-side changes rather than plugin changes:
plugin initdoes not yet honorOptional: 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.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 (GetCredentialTypeignorescredentialName) and F-6 (MarshalJSONreadsCredentials[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
Related Issue(s)
How To Test
The steps from #598 still apply:
~/.aws/config(either form) and runaws sso login --profile <name>.op plugin init aws, link an SSO Profile item (the importer will offer the profile it finds in~/.aws/config).op plugin run -- aws sts get-caller-identity --profile <name>prints the assumed-role ARN.~/.aws/sso/cache/<sha1>.jsonand re-run: the plugin surfaces "AWS SSO token is missing or expired; runaws 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.