Skip to content

Harden user-created data connections: federated-credential scoping and connection validation #207

Description

@alukach

Hardening work for user-created ("bring your own bucket") data connections, ahead of granting create_data_connections more broadly.

Threat model and analysis are in a private security advisory: GHSA-2rjm-4mm8-gj29. Please read it before picking up an item — the ordering below is not arbitrary, and the first item gates the value of several others.

Spans three repos. Checkboxes note where each lands.

Federated-credential scoping

  • Key the OIDC credential cache on {role_arn}|{subject} rather than the role ARN alone — AwsBackendAuth::resolve_aws. (upstream: developmentseed/multistore)
    Include the ARN as well as the subject: a connection's role_arn can change while its ID, and therefore its subject, stays fixed. | cannot occur in an ARN, so it is a safe delimiter. This restores the behaviour docs/plans/2026-06-02-federated-backend-auth-design.md step 7 already specifies.
    ⚠️ The same design doc calls for single-flight around AssumeRole. Do not implement it as an isolate-shared async mutex — see fix(deps): bump multistore to 0.7.2 to stop federated reads hanging #197. Refresh-ahead is safe.

  • Render the OIDC subject with scope derived from owner (this repo)

    • unowned (platform-managed) → scv1:conn:{id} — cardinality 1, no change from today
    • owned (account-created) → scv1:conn:{id}:product:{account}/{product}

    No subject_scope schema field — the contract is already documented on owner in source.coop/src/types/data-connection.ts. Uniform product-scope is explicitly ruled out by the design doc (line 180) on cache-cardinality grounds. Needs owner added to DataConnection in src/source_api/types.rs, and account/product threaded into apply_backend_auth; both are already in scope at registry.rs:66.

  • Fix the documented-vs-emitted subject mismatch. DataConnectionForm.tsx:196 tells customers to pin scv1:conn:{id}:*, but the proxy emits bare scv1:conn:{id}, which an IAM StringLike of scv1:conn:{id}:* does not match. Implementing the item above makes the existing UI text correct; until then anyone who followed it has a role the proxy cannot assume. (this repo + source.coop)

  • Condition the platform role's trust policy on subject structure — deploy/lib/data-proxy-construct.ts. Trial against live IAM in staging before relying on it as a boundary. (source.coop)

Connection validation

  • Constrain prefix_template and base_prefix for owned connections. Both are bare z.string() today. Force the canonical {{repository.account_id}}/{{repository.repository_id}}/ (or at minimum require the account_id token), and normalize base_prefix — reject leading /, .., and empty segments. (source.coop)
  • Reject platform-owned buckets and ARNs on non-admin connections. One isPlatformResource(details, authentication) predicate, called from both createDataConnection and updateDataConnection. Fails loudly at create time rather than opaquely at request time. (source.coop)
  • Freeze details.bucket, base_prefix, and authentication once a connection is referenced by a product mirror (or force re-verification on change). listProductsByConnectionId already exists for the delete guard. Without this, creation-time validation is bypassable by editing after approval. (source.coop)

Defence in depth

  • Correct the session-policy claim in deploy/lib/data-proxy-construct.ts:58-59, which describes a per-request STS session policy that is not implemented. Worth doing immediately and independently — it currently misleads anyone reasoning about the blast radius. (source.coop)
  • Send a per-request STS session policy scoped to arn:aws:s3:::{bucket}/{resolved_prefix}*, moving prefix confinement from application logic to IAM. Needs a Policy field upstream. (upstream + this repo)
  • Prove bucket ownership before a connection is usable — nonce-object challenge/response at {base_prefix}.source-coop-verification for all providers; for s3_web_identity_role, require the customer's trust policy to pin their per-connection subject and verify by attempting the assume-role at creation (depends on the subject-rendering item). (source.coop)
  • CloudTrail alarm on AssumeRoleWithWebIdentity against the platform role with an unexpected sub. (source.coop)

Known residual

Even with the above, all products in a platform-managed bucket share one bucket-wide credential, so cross-account prefix confinement there remains application logic rather than IAM. Candidate follow-up: give the platform connection account scope plus a session policy on {base_prefix}{account}/* — cardinality becomes ~per-active-account instead of per-product. Prerequisite: confirm the platform connection's prefix_template leads with {{repository.account_id}}.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions