source-cooperative / source-cooperative/data.source.coop

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

Open
#207 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
24
Forks
6
Avg merge
1h 32m
Merged PRs (30d)
1

Description

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 #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 structuredeploy/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}}.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with GHSA-2rjm-4mm8-gj29 and docs/plans/2026-06-02-federated-backend-auth-design.md, then trace registry.rs:66, src/source_api/types.rs, DataConnectionForm.tsx:196, and deploy/lib/data-proxy-construct.ts. Map the owner and account/product flow before selecting one independently tracked checkbox. Done requires the stated validation, subject, IAM, or policy behavior plus tests or staging verification where the issue calls for it.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, rust, typescript
Domain
backend-api-design, cloud, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.