Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/cli-windows-arm64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
# holding the target path.
cp secretspec/README.md staging/README.md
cp CHANGELOG.md LICENSE staging/
cp target/aarch64-pc-windows-msvc/dist/secretspec.exe staging/
cp target/aarch64-pc-windows-msvc/dist/{secretspec,docker-credential-secretspec}.exe staging/

- name: Create the archive
shell: pwsh
Expand All @@ -88,7 +88,7 @@ jobs:
$names = $zip.Entries.FullName
$zip.Dispose()
# Every file dist puts in a Windows archive, at the archive root.
foreach ($entry in 'secretspec.exe', 'README.md', 'LICENSE', 'CHANGELOG.md') {
foreach ($entry in 'secretspec.exe', 'docker-credential-secretspec.exe', 'README.md', 'LICENSE', 'CHANGELOG.md') {
if ($names -notcontains $entry) {
throw "the archive is missing $entry"
}
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ as in 0.19.0.
checksum. The static installer keeps selecting the x86_64 build on Windows
ARM64, which runs under emulation, so download the archive directly for a
native binary.
- A read-only `docker-credential-secretspec` helper lets Docker retrieve
registry usernames and tokens through any SecretSpec provider.
`secretspec docker configure` and `unconfigure` safely manage per-registry
Docker credential-helper settings without replacing existing helpers, while
`secretspec docker login` and `logout` manage isolated embedded credentials;
custom manifests remain available through `--file` (0.20+).

### Fixed

Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ thiserror = "2.0"
etcetera = "0.11"
colored = "3.0"
dotenvy = "0.15"
dunce = "1.0"
inquire = { version = "0.9.4", features = ["experimental-multiline-input"] }
miette = { version = "7.6", features = ["fancy"] }
serde_json = "1.0"
Expand Down Expand Up @@ -59,6 +60,7 @@ rand = "0.9"
rsa = { version = "0.9", features = ["pem"] }
uuid = { version = "1", features = ["serde", "v4"] }
data-encoding = "2"
sha2 = "0.10"
detect-coding-agent = "0.1"
age = { version = "0.12", features = ["armor", "plugin", "ssh"] }

Expand Down
10 changes: 10 additions & 0 deletions docs/astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,16 @@ Values can be resolved from: keyring (default), KeePass KDBX (0.17+), dotenv fil
},
],
},
{
label: "Integrations",
items: [
{
label: "Docker credentials",
slug: "integrations/docker",
badge: { text: "0.20+", variant: "note" },
},
],
},
{
label: "SDK",
items: [
Expand Down
173 changes: 173 additions & 0 deletions docs/src/content/docs/integrations/docker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
---
title: Docker credentials
description: Let Docker retrieve registry credentials through SecretSpec providers
---

The Docker credential integration is available in SecretSpec 0.20+. It lets
`docker pull`, `docker push`, `docker build`, and Docker Compose retrieve
registry credentials from any SecretSpec provider without copying the
password or token into Docker's `config.json`.

## Quick start

Configure the registry with its non-secret username:

::::danger[This changes your Docker configuration]
Docker has no repository-local configuration. `configure` updates
`$DOCKER_CONFIG/config.json` when `DOCKER_CONFIG` is set, or the user-level
`~/.docker/config.json` (`%USERPROFILE%\.docker\config.json` on Windows)
otherwise. The change applies to every Docker command using that configuration.
SecretSpec preserves unrelated settings and refuses to replace another helper.
Undo it with `secretspec docker unconfigure --registry ghcr.io`.
::::

```bash
$ secretspec docker configure --registry ghcr.io --username YOUR_USERNAME
```

After confirmation, the command prints the matching login command:

```console
Configured Docker credential for ghcr.io.
Docker configuration: /home/you/.docker/config.json
Store the credential with: secretspec docker login 'ghcr.io'
Undo with: secretspec docker unconfigure --registry 'ghcr.io'
```

Store the password or access token in SecretSpec's embedded, registry-isolated
credential store:

```bash
$ secretspec docker login ghcr.io
```

Docker now invokes `docker-credential-secretspec get` automatically:

```bash
$ docker pull ghcr.io/OWNER/IMAGE:TAG
$ docker push ghcr.io/OWNER/IMAGE:TAG
```

`configure` does not retrieve or store the credential. It adds the registry's
`credHelpers` entry and records only the registry, username, provider selection,
and other value-free metadata. `login` prompts for the secret and stores it
through the selected provider. Each registry has a separate SecretSpec project
identity, so credentials cannot collide between registries.

To use a provider other than your default, pass the same override to both
commands. The follow-up command printed by `configure` includes it automatically:

```bash
$ secretspec docker configure \
--registry ghcr.io \
--username YOUR_USERNAME \
--provider onepassword
$ secretspec docker login ghcr.io --provider onepassword
```

## Docker Hub

Docker uses the historical key `https://index.docker.io/v1/` for Docker Hub.
SecretSpec 0.20+ normalizes the familiar Docker Hub hostnames and URL forms to
that key:

```bash
$ secretspec docker configure \
--registry docker.io \
--username YOUR_DOCKER_ID
$ secretspec docker login docker.io
```

Registry addresses may contain a port, such as
`registry.example.com:5000`, but not a repository path. Credentials are scoped
to the registry rather than an image namespace.

## Use a project manifest

For a credential already declared by a project, pass `--file` to select the
advanced custom-manifest mode. In this mode, `--token-secret` and either
`--username` or `--username-secret` are required:

```toml
[project]
name = "docker-credentials"
revision = "1.0"

[profiles.default]
GHCR_TOKEN = { description = "GitHub Container Registry token" }
```

```bash
$ secretspec --file secretspec.toml docker configure \
--registry ghcr.io \
--token-secret GHCR_TOKEN \
--username YOUR_USERNAME
```

To resolve the username from SecretSpec too, declare it and replace
`--username` with `--username-secret GHCR_USERNAME`. Custom-manifest mode also
accepts `--profile` and `--provider`.

The managed state records the manifest's absolute path and resolved profile,
but never resolved secret values. If the manifest moves, rerun `configure` for
the affected registry. Manage custom-manifest values with `secretspec set` and
`secretspec delete`; `secretspec docker login` and `logout` intentionally manage
only the embedded store.

## Alternate Docker configuration directory

SecretSpec honors `DOCKER_CONFIG` when selecting `config.json`, just like the
Docker CLI:

```bash
$ DOCKER_CONFIG="$HOME/.config/docker-work" \
secretspec docker configure \
--registry registry.example.com \
--username YOUR_USERNAME
```

Use the same `DOCKER_CONFIG` value when unconfiguring entries from that file.

## Remove credentials and configuration

Remove an embedded secret without changing Docker's helper configuration:

```bash
$ secretspec docker logout ghcr.io
```

Pass the same `--provider` used for login when it was explicitly overridden.

Remove one helper registration from the active Docker configuration:

```bash
$ secretspec docker unconfigure --registry ghcr.io
```

Remove every Docker credential helper registration that SecretSpec owns in
that file:

```bash
$ secretspec docker unconfigure --all
```

Configuration changes prompt with a default of **No**. Pass `--yes` for
non-interactive setup or removal. SecretSpec preserves the default credential
store, other registry helpers, existing `auths`, and unrelated Docker options.
If a managed entry changes outside SecretSpec, `unconfigure` refuses to modify
it.

`logout` and `unconfigure` are independent: logout deletes the embedded secret,
while unconfigure removes Docker's reference to the helper. This matches the
separation between `login` and `configure`.

## Read-only helper behavior

In SecretSpec 0.20+, `docker-credential-secretspec` answers Docker's `get`
operation. It rejects `store`, `erase`, and `list`, so Docker's own
`docker login` and `docker logout` cannot overwrite or delete values in a shared
provider. Use `secretspec docker login` and `secretspec docker logout` for the
embedded store, or normal SecretSpec commands for a custom manifest.

When no matching configuration or stored value exists, the helper returns
Docker's standard credential-not-found response.
67 changes: 67 additions & 0 deletions docs/src/content/docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,73 @@ Run 'secretspec check --provider bws' to verify authentication.

A read-only source provider is rejected. An alias that declares no credentials reports that there is nothing to store.

### docker configure (0.20+)

Configure Docker to retrieve credentials for one registry through SecretSpec.

```bash
$ secretspec docker configure --registry <REGISTRY> --username <USERNAME> [OPTIONS]
```

**Options:**

- `--registry <REGISTRY>` - Registry hostname, optionally including a port;
Docker Hub aliases are normalized to Docker's canonical registry key
- `--username <USERNAME>` - Non-secret registry username; required for the
embedded store, or as an alternative to `--username-secret` with `--file`
- `--token-secret <KEY>` - Custom manifest key containing the password or
access token; requires `--file`
- `--username-secret <KEY>` - Custom manifest key containing the username;
requires `--file` and conflicts with `--username`
- `-P, --profile <PROFILE>` - Custom manifest profile; requires `--file`
- `-p, --provider <PROVIDER>` - Provider override the helper should use
- `-y, --yes` - Confirm the Docker configuration change non-interactively

Without `--file`, the command configures the embedded registry-isolated store
and prints the corresponding `secretspec docker login` command. With `--file`,
`--token-secret` and either username option are required. The command adds a
registry-specific `credHelpers` entry to Docker's `config.json`, prompts with a
default of **No**, and refuses to replace an existing helper.

### docker login (0.20+)

Store a password or token in the embedded Docker credential store:

```bash
$ secretspec docker login <REGISTRY> [--provider <PROVIDER>]
```

The registry is normalized exactly as it is for `configure`. Each registry is
stored under a separate SecretSpec project identity. This command rejects
`--file`; use `secretspec set` for custom-manifest credentials.

### docker logout (0.20+)

Remove a password or token from the embedded Docker credential store:

```bash
$ secretspec docker logout <REGISTRY> [--provider <PROVIDER>]
```

Use the same provider override supplied to `login`. This does not remove the
Docker helper registration; use `unconfigure` for that.

### docker unconfigure (0.20+)

Remove one or all Docker credentials configured by SecretSpec in the active
Docker configuration.

```bash
$ secretspec docker unconfigure --registry <REGISTRY>
$ secretspec docker unconfigure --all
```

Use `--yes` to confirm the change non-interactively. `--all` removes only
entries SecretSpec owns; it preserves the default credential store, other
registry helpers, stored authentication entries, and unrelated Docker options.
See [Docker credentials](/integrations/docker/) for complete setup, custom
manifest, and ownership details.

### check
Check if all required secrets are available, with interactive prompting for missing secrets.

Expand Down
9 changes: 8 additions & 1 deletion secretspec/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ name = "secretspec"
path = "src/bin/secretspec.rs"
required-features = ["cli"]

[[bin]]
name = "docker-credential-secretspec"
path = "src/bin/docker-credential-secretspec.rs"
required-features = ["cli"]

[dependencies]
clap.workspace = true
keyring = { workspace = true, optional = true }
Expand All @@ -27,6 +32,7 @@ thiserror.workspace = true
etcetera.workspace = true
colored.workspace = true
dotenvy.workspace = true
dunce = { workspace = true, optional = true }
inquire.workspace = true
miette.workspace = true
serde_json.workspace = true
Expand All @@ -52,12 +58,13 @@ rand.workspace = true
rsa.workspace = true
uuid.workspace = true
data-encoding.workspace = true
sha2.workspace = true
detect-coding-agent.workspace = true
age = { workspace = true, optional = true }

[features]
default = ["cli", "keyring", "kdbx", "keeper", "gcsm", "awssm", "awsps", "vault", "openbao", "bws", "akv", "infisical", "bw", "age", "scaleway", "sops"]
cli = ["dep:toml_edit"]
cli = ["dep:toml_edit", "dep:dunce"]
keyring = ["dep:keyring", "dep:whoami"]
kdbx = ["dep:keepass"]
keeper = ["dep:keeper-secrets-manager-core"]
Expand Down
1 change: 1 addition & 0 deletions secretspec/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ SecretSpec fixes this by separating secret **declaration** from secret **storage
- **Composed Secrets (0.16+)**: Derive read-only values such as DSNs from declared secrets with strict, order-independent `${UPPERCASE_NAME}` references
- **[Configuration Inheritance](https://secretspec.dev/concepts/inheritance/)**: Extend and override shared configurations using the `extends` feature
- **[Audit Logging](https://secretspec.dev/concepts/audit/)**: Every secret access recorded locally (who, when, why, outcome) — on by default, secret values never logged
- **[Docker credential helper](https://secretspec.dev/integrations/docker/)** (0.20+): Authenticate registry pulls and pushes through any SecretSpec provider
- **[Discovery](https://secretspec.dev/reference/cli#init)**: `secretspec init` to discover secrets from existing `.env` files

## Quick Start
Expand Down
3 changes: 3 additions & 0 deletions secretspec/src/bin/docker-credential-secretspec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() -> std::process::ExitCode {
secretspec::integration::docker::main()
}
Loading
Loading