Skip to content

A connected source rotates its credential in place: same source, same collection, verified by a read before it replaces the old — today a rotated key is a different source and a full re-ingest #1225

Open

Description

@antoinetoussaint-byte

Read from main at 877e8a28 on 2026-10-23, as phase 4 of obin-ai/handbook design/bucket-datasources/roadmap.md (PR obin-ai/handbook#364). Filed with the owner's go-ahead; not to start before #1211 step 3 lands (the credential path the service client settles is the one this rotates).

What we are trying to do

An organisation with a key-rotation policy rotates the access key of a connected bucket (or the token of any static-secret source) in place: same source, same collection, same grants, no re-ingest, no gap in sync.

What the code says

UpdateSourceRequest's own comment (datasource.proto, "UpdateSourceRequest changes what an already-connected source fetches, in place"): "Nothing else about a source is mutable here — the repository, the provider, the collection it writes into and its credential are each the identity of something a consumer already holds, and changing one of those is a different source." So a rotated key today means DeleteSource + AddSource: a new source id, a new collection or a re-bind, and a full re-send of the bucket under the daily reconcile. The only rotation that exists is the GitHub App installation's (datasource_installation_rotation_test.go), where the host mints the token itself.

What to build

  • RotateSourceCredential(org_id, source_id, credential) on DatasourceService — or a credential arm on UpdateSource if the contract's "identity" argument is overturned in the proposal; either way the source id and the collection do not change.
  • The new credential is verified by a read before it replaces the old (list one page of the prefix for a bucket; the equivalent probe per provider), encrypted at receipt through the same SecretCipher path, written as a new envelope under the same DatasourceConnectorSecretPurpose; the old envelope is retired, never overwritten in place, so a failed verification leaves the source exactly as it was.
  • A sync in flight finishes on the credential it started with; the next one reads the new envelope (the per-source resolver closure feat(accounts): the object-storage connector diffs the current listing against one the host retains #1213 introduced is the seam).
  • Audited as its own typed event (saas.datasource.source.credential_rotated, no material in the payload), and Datasource projects credential_rotated_at.
  • The kit: a "Rotate key" action on the source, refusal shown by field.

Tests before review

Rotation round-trip on real Postgres (new envelope readable, old retired); a failed probe leaves the old envelope live and the source ACTIVE; an in-flight sync completes on the old key while the next uses the new (fake connector); audit event emitted with no secret material; UpdateSource still refuses a credential if that arm is not taken.

🤖 Generated with Claude Code

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