Skip to content

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

Open
rjwhitworth wants to merge 2 commits into1Password:mainfrom
rjwhitworth:feat/aws-sso-210
Open

feat(aws): add support for AWS IAM Identity Center (SSO)#598
rjwhitworth wants to merge 2 commits into1Password:mainfrom
rjwhitworth:feat/aws-sso-210

Conversation

@rjwhitworth
Copy link
Copy Markdown

@rjwhitworth rjwhitworth commented May 9, 2026

Overview

Adds AWS IAM Identity Center (SSO) authentication to the AWS plugin so users with SSO-only AWS access can authenticate the AWS CLI via 1Password. 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. Both the legacy sso_start_url form and the consolidated sso_session form are supported. 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 plugin: each provisioner yields silently when the active profile is configured for the other type, so users can link both items against the same plugin without conflict. The importer scans ~/.aws/config for both forms and surfaces a candidate per SSO-bearing profile. The cdk, eksctl, awslogs, and sam executables 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.

Two SDK changes:

  • Plugins are now allowed to expose more than one credential type. The op runtime already accepts this; the validator was the only block.
  • A new AllowsExternalSecretCache flag on CredentialType lets a credential opt out of the "must have at least one secret field" check. Only the SSO Profile uses it (its bearer token lives in the AWS SDK's external cache, not in the vault item). Every other credential continues to fail validation if no secret field is declared.

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

Security hardening

The second commit on this branch addresses findings from a pre-PR adversarial security review:

  • Importer (plugins/aws/sso_importer.go): validates sso_start_url (https, non-empty host), sso_account_id (12-digit regex), and sso_region. Rejects NUL bytes mid-value. Refuses non-regular, symlinked, or non-owned AWS_CONFIG_FILE overrides. Uses strict botocore-parity INI options: KeyValueDelimiters="=", IgnoreContinuation=true, Loose=true.
  • Provisioner (plugins/aws/sso_provisioner.go): asserts ~/.aws/sso/cache/<sha1>.json is a regular non-symlink file owned by the current uid with no group/world read bits before passing the path to ssocreds.New. AWS SSO API errors flow through a smithy code whitelist; unknown codes get a generic plugin-controlled message so server-controlled error text doesn't reach user-visible output. The sso:GetRoleCredentials call runs under a 30-second context.WithTimeout.
  • The AllowsExternalSecretCache flag (above) replaces an earlier severity downgrade so the secret-field check stays an Error for the rest of the catalogue.

Type of change

  • Improved an existing plugin

Related Issue(s)

How To Test

  1. Configure an SSO profile in ~/.aws/config (either form). Legacy:

    [profile my-sso]
    sso_start_url = https://your-org.awsapps.com/start
    sso_region = us-east-1
    sso_account_id = 123456789012
    sso_role_name = AdministratorAccess
    region = us-east-1
    

    Or consolidated:

    [sso-session corp]
    sso_start_url = https://your-org.awsapps.com/start
    sso_region = us-east-1
    
    [profile my-sso]
    sso_session = corp
    sso_account_id = 123456789012
    sso_role_name = AdministratorAccess
    region = us-east-1
    
  2. aws sso login --profile my-sso (populates ~/.aws/sso/cache/<sha1>.json).

  3. op plugin init aws, pick SSO Profile, accept the auto-detected candidate.

  4. op plugin run -- aws sts get-caller-identity --profile my-sso — prints the assumed-role ARN.

  5. Re-run within the credential TTL — the second invocation hits the plugin's encrypted cache (no SSO endpoint round-trip).

  6. (Optional) Wipe ~/.aws/sso/cache/<sha1>.json and re-run — the plugin should surface: AWS SSO token is missing or expired; run \aws sso login --profile my-sso` and try again`.

  7. (Optional, cache-file safety) chmod 644 ~/.aws/sso/cache/<sha1>.json and re-run on a cache-miss — the plugin should refuse with AWS SSO token cache "..." is group/world readable (mode 644); chmod 600 it and re-run. Restore chmod 600 afterwards.

Changelog

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

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
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

1 participant