Skip to content

Provider aliases: define templated ref coordinates per provider #289

Description

@domenkozar

Problem

A secret-level ref is currently resolved once and passed unchanged to every provider in the route. The coordinate names are shared, but their semantics are provider-native:

  • Bitwarden Password Manager uses item + field as an item and one of its fields.
  • keyring uses item + field as service and account.
  • dotenv has no representation for field.
  • convention layouts may derive a completely different name from {project}, {profile}, and {key}.

This makes a route portable only when every provider happens to interpret the same concrete coordinates compatibly. It also means provider overrides and imports cannot independently address their source and destination. See #266.

Per-secret provider-scoped refs could express every address, but would repeat the same layout across every declaration. Most stores instead follow one provider-specific naming rule.

Proposal

Allow a leaf provider alias to declare a templated native ref/address. The template compiles SecretSpec's logical coordinates into the native coordinates for that alias:

[providers]
bw_prod = {
  uri = "bw://team@production",
  ref = {
    item = "{project}-{profile}",
    field = "{key}"
  }
}

local = "keyring://"

[profiles.production.defaults]
providers = ["bw_prod", "local"]

For API_KEY in project payments, profile production:

  • bw_prod resolves { item = "payments-production", field = "API_KEY" };
  • local, which has no template, uses keyring's ordinary convention address.

The exact field name (ref, ref_template, or address) is open for design. The important property is that the template belongs to a provider alias, not to the secret or the provider scheme globally.

This would also make migration direction explicit without silently dropping coordinates:

[providers]
source_bw = {
  uri = "bw://team@production",
  ref = { item = "{project}-{profile}", field = "{key}" }
}
target = "dotenv://.env"
secretspec import source_bw

The source alias would use its native template while the manifest's target route would use its own alias/convention address. The reverse migration would resolve the two sides independently in the opposite direction.

Semantics to define

  1. Templates may interpolate at least {project}, {profile}, and {key} in any supported coordinate: item, field, vault, section, or version.
  2. Each leaf in a fallback chain resolves its own address. A coordinate supported by one provider is never forwarded automatically to another provider.
  3. --provider <alias> retains that alias's template. A literal provider URI has no alias template and uses the provider's convention address.
  4. Cached routes keep resolving the authoritative leaf through its template and the cache leaf through its own template/convention. The route wrapper itself should not own one ambiguous template.
  5. import resolves source and destination addresses independently. Same-entry checks, reads, writes, verification, deletion, audit records, and diagnostics use the appropriate resolved address.
  6. Expanded coordinates still go through the provider's normal supported-coordinate and writability checks; templates must not weaken fail-closed behavior.
  7. Unknown placeholders, missing required item, and empty/whitespace expansions fail during configuration/planning before provider I/O.
  8. Deduplication uses the expanded provider/address pair, and audit events record the expanded native address without secret values.

Precedence and compatibility

Existing bare aliases and provider tables remain unchanged.

The main precedence question is how an existing concrete secret-level ref composes with an alias template. A backward-compatible starting point would be:

  1. an explicit provider-scoped per-secret override, if that surface is added;
  2. the existing secret-level ref (preserving its current route-wide behavior);
  3. the selected provider alias's template;
  4. the provider's built-in convention address.

An alternative is to reject a secret-level ref combined with a provider template until provider-scoped per-secret overrides are designed. It should not merge coordinate tables implicitly.

Provider-scoped exceptional overrides could be added later, keyed by alias rather than scheme so multiple instances of the same provider remain distinguishable:

API_KEY = {
  refs = {
    bw_prod = { item = "legacy-item", field = "apiToken" }
  }
}

Why aliases are the right scope

Provider implementations already compile convention addresses, and several expose provider-specific path/template options. Those mechanisms are not uniform and cannot describe structured mappings such as “one Bitwarden item per project/profile, one field per secret.”

Aliases represent a configured provider instance and are already the unit used by fallback chains, credentials, caching, and user/project overrides. Attaching the mapping there lets two instances of the same scheme use different layouts without changing provider-global behavior.

This also addresses the flat/namespaced migration pressure discussed in #181 without adding a layout option independently to every provider.

Acceptance criteria

  • A fallback chain can resolve one logical secret through different native address shapes per alias.
  • Import can read using the source alias template and write using the destination alias template or convention, in either direction.
  • Provider overrides have deterministic, documented template behavior.
  • Legacy manifests retain their current resolution behavior.
  • Planning reports the alias, expanded address, and unsupported coordinate on template errors.
  • Tests cover structured-to-flat and flat-to-structured imports, fallback chains, overrides, caches, same-entry detection, invalid templates, and legacy ref compatibility.
  • The references concept, configuration reference, provider pages/lists, CLI import docs, and generated summaries document the feature with its target-version label at every point of use.

Related: #266, #181, and the original reference design in #121.

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