feat(aws): add support for AWS IAM Identity Center (SSO)#622
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
left a comment
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.
|
After some internal discussion we want to do a bit more work to understand the implications of some of the bigger changes this plugin introduces. No showstoppers or blockers, just want to ensure that we are deliberate about introducing some new concepts, and some understanding of the changes that would need to at least eventually be made to |
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).One limitations observed requiring CLI-side changes rather than plugin changes:
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:
Tested with op 2.30.0 and a local build (
make aws/build) against a live IAM Identity Center org:~/.aws/configand runaws sso login --profile <name>.The login command passes through without provisioning, by design.
op plugin init aws. At "Choose a credential type", pick AWS SSO Profile andlink an SSO Profile item, then pick Stop choosing credentials — no Access Key item
is required. Note: the locate prompt is mislabeled "Locate your AWS Access Key"
(see limitation above); it still validates against the SSO Profile item.
aws sts get-caller-identity --profile <name>— prints the assumed-role ARN.To retest from a clean slate,
op plugin clear awsresets the plugin's saved credentialconfiguration.
Changelog
Authenticate the AWS CLI with AWS IAM Identity Center (SSO) profiles using cached SSO tokens.