Skip to content

Add secretspec support - #16300

Open
domenkozar wants to merge 1 commit into
NixOS:masterfrom
cachix:secretspec
Open

Add secretspec support#16300
domenkozar wants to merge 1 commit into
NixOS:masterfrom
cachix:secretspec

Conversation

@domenkozar

@domenkozar domenkozar commented Aug 13, 2026

Copy link
Copy Markdown
Member

Nix credentials currently have to be stored as literal configuration values. This makes long-lived forge tokens, netrc contents, and build secrets visible to configuration introspection and harder to manage through external secret providers.

I've written about this in secrets don't belong in config.

This PR introduces first-class support for https://secretspec.dev (akin to terraform) that comes currently with support for 30 secrets providers via pkgs.secretspec-ffi.

secretspec-access-tokens = github.com=GITHUB_TOKEN
secretspec-netrc-file = NIX_NETRC
secretspec-impure-env = PRIVATE_TOKEN=BUILD_TOKEN
secretspec-file = /etc/nix/secretspec.toml
secretspec-profile = production
secretspec-scope = nix

Nix ships with its own secretspec.toml that can be overiden for customization via secretspec-file.

It also comes with an extensive test suite.

How it works

SecretSpec separates secret declarations from secret values:

  1. secretspec.toml declares the available secret names and whether each
    secret is an inline value or a materialized file.
  2. secretspec-provider, secretspec-profile, and secretspec-scope
    select the resolution context.
  3. Nix settings map existing credential consumers to SecretSpec names.
  4. The value is resolved lazily, only when that consumer needs it.

Closes: #6536
Related: #8635
Related: #6942

@github-actions github-actions Bot added documentation with-tests Issues related to testing. PRs with tests have some priority store Issues and pull requests concerning the Nix store fetching Networking with the outside (non-Nix) world, input locking labels Aug 13, 2026
Nix credentials currently have to be stored as literal configuration values. This makes long-lived forge tokens, netrc contents, and build secrets visible to configuration introspection and harder to manage through external secret providers.

Allow these credentials to be referenced by SecretSpec name and resolved only when used. Support GitHub, GitLab, and SourceHut access tokens with the existing host and path-prefix matching, complete netrc files, and impure environment variables requested by fixed-output derivations.

Preserve compatibility by preferring literal values for equally specific access-token mappings and equally named impure environment variables. SecretSpec netrc files take precedence over netrc-file.

Prevent flakes from configuring credential-bearing settings, even when flake configuration is accepted. Propagate the requesting client trust level through local and remote builds so untrusted daemon clients cannot consume daemon-configured SecretSpec impure environment mappings.

Make SecretSpec support optional at build time so libstore remains available where secretspec-ffi cannot be built.

Closes: NixOS#6536
Related: NixOS#8635
Related: NixOS#6942
Assisted-by: Claude Code (claude-opus-5)
Assisted-by: Codex (GPT-5)
@domenkozar

Copy link
Copy Markdown
Member Author

We could even go further to provide helpers for migration from existing files, but that would need a bit more work.

[project]
name = "nix"
revision = "1.0"

# `system` is intentionally supplied by the machine's global SecretSpec config.
# This alias exposes the existing /etc/nix/netrc as a migration source.
[providers]
legacy_netrc = "file:///etc/nix"

[profiles.default]
NIX_NETRC = {
  description = "Complete netrc file used by Nix",
  required = false,
  as_path = true,
  providers = ["system", "legacy_netrc"],
  refs = {
    legacy_netrc = { item = "netrc" },
  },
}

and then migrate:

$ secretspec \
    --file /etc/nix/secretspec.toml \
    --profile default \
    import legacy_netrc \
    --delete-source

I'll add ini support to file provider then we can also extract access-tokens as part of migration.

@xokdvium xokdvium left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of very questionable things here.

Comment on lines +125 to +131
/* Keep every resolved context alive so an in-flight user never observes an
`as_path` file being deleted after a configuration change. */
mutable Sync<std::map<SecretSpecRequest, std::shared_ptr<SecretSpecCache>>> _caches;

/* Serializes resolution so that a request is resolved only once, without
blocking cache lookups for the duration of the resolution. */
mutable std::mutex _resolveMutex;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh, I'm feeling like a parrot at this point. No, please. No more global mutable state until we figure out how to not rely on globals for things.

Having global credentials (especially stuffed into the settings systems) seems like a disaster waiting to happen.

Comment thread src/libflake/config.cc
Comment on lines +80 to +83
if (globalConfig.getFlakeConfigSetting(baseName) == FlakeConfigSetting::Forbidden) {
warn("ignoring flake configuration setting '%s' because it is not allowed to be set by flakes", name);
continue;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, can we remove nixConfig ? :)

I don't think this makes much sense considering how nixConfig now can RCE you pretty trivially.

{
std::string value;
std::string description;
FlakeConfigSetting flakeConfigSetting;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a huge layer violation to pollute libutil with flakes.

static const ResolvedSecret & getSecret(const SecretSpecCache & cache, const std::string & name)
{
if (auto error = cache.errors.find(name); error != cache.errors.end())
std::rethrow_exception(error->second);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will lead to data races and mutation of the refcounted exception. This is not how to rethrow shared errors. The trace system mutates them.

Comment on lines -272 to +277
Worker(Store & store, Store & evalStore);
Worker(Store & store, Store & evalStore, TrustedFlag requestTrusted = Trusted);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing how we'd love to make the worker less ephemeral and properly reusable this seems like the wrong abstraction layer.

Also the default argument seems very footgunny if the idea is that it's security relevant?

{
copyDrvsFromEvalStore(drvPaths);
auto conn(store->getConnection());
setOptions(*conn);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Comment on lines +21 to +25
std::string SecretSpecSettings::defaultFile()
{
return NIX_SECRETSPEC_FILE;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, usually nix (in static builds) does not rely on non-embedded data.
Or at least we try to. Seems like that would break that?

@xokdvium

Copy link
Copy Markdown
Contributor

Tbh I think we should have a better way to abstract around credential providers, so that it's much more hidden away and sits behind a consistent interface. That should hopefully allow us to pay off the existing tech debt around credentials too.

@arianvp

arianvp commented Aug 14, 2026

Copy link
Copy Markdown
Member

Linking nix against 27 cloud SDKs transitively sounds like a terrible idea. We just made a lot of effort getting rid of the aws SDK dependency.

Keeping nix’s dependency footprint small is useful for static linking, and trust bootstrapping.

Why not have an interface like varlink over Unix domain sockets for credential brokerage? So that nix can talk to some secretspec credential daemon?

that way we don’t explode the build time dependency footprint of nix. Credentials are a runtime concern; move it to a runtime daemon.

This is also what https://spiffe.io does (albeit gRPC; not varlink)

I started a similar discussion here systemd/systemd#40469

but iirc there are also similar discussions happening in gnome

another option would be exploring introducing this as a nix plugin? though personally I’m very much in IPC > shared library camp for plugin-shaped things.

@KiaraGrouwstra

KiaraGrouwstra commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

iiuc, the fix to store leakage here could be a simple as having the interface be a file (or what @arianvp said) rather than the secret itself?
now, in nixpkgs.lib i think NixOS/nixpkgs#503858 could bridge that already, without changes on the nix side.
even if we wanted a native solution from the nix end itself tho, why tightly couple things rather than say having such a more generic interface make for the boundary?

@grahamc

grahamc commented Aug 14, 2026

Copy link
Copy Markdown
Member

Related: #9857 and rebased in #16087

@xokdvium

Copy link
Copy Markdown
Contributor

In line with @arianvp's comment. Could we maybe indeed come up with a simple varlink interface / internal cred API (maybe consolidate with from #16087), so that it doesn't require too many changes to the internals?

@jaen

jaen commented Aug 16, 2026

Copy link
Copy Markdown

simple varlink interface / internal cred API

Ditto to this, I have a weird personal secrets thing (nothing pre-existing worked well for my LUKS setup after scripted stage1 got deprecated) and I'd rather just plug it in to some extension point rather than be forced to migrate to something else that may or may not support my use-case.

It's also probably good to keep options open until the ecosystem at large converges on one option, instead of blessing an arbitrary one.

@domenkozar

domenkozar commented Aug 17, 2026

Copy link
Copy Markdown
Member Author

Thanks everyone for feedback, I'll try to incorporate it in next iteration.

Draft for Resolver and Provider IPC for secretspec is ready. Feedback welcome!

Provider allows talking to secretspec cli without bringing in huge dependency chain, like for Nix.

Provider IPC allows you to build your own provider!

@domenkozar

Copy link
Copy Markdown
Member Author

simple varlink interface / internal cred API

Ditto to this, I have a weird personal secrets thing (nothing pre-existing worked well for my LUKS setup after scripted stage1 got deprecated) and I'd rather just plug it in to some extension point rather than be forced to migrate to something else that may or may not support my use-case.

Could you share what's missing bit? I'm happy to integrate that into our proposal for IPC.

It's also probably good to keep options open until the ecosystem at large converges on one option, instead of blessing an arbitrary one.

I'm going to rework the PR so it integrates secretstpec via IPC but leaves options open for other protocols to co-exist.

That way we can improve our protocol given the feedback while allowing anyone else to add their own.

@domenkozar

Copy link
Copy Markdown
Member Author

I've split out secrets abstraction inside Nix into #16339 where I explain why it's better to have a generic interface inside instead of #9857 / #16087

Once I'm happy with IPC (reviews welcome) inside secretspec, I'll redo this PR as requested by many. That should also get rid of the other review raised, all globals being removed (which #957 btw has two).

@nixos-discourse

Copy link
Copy Markdown

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/secrets-don-t-belong-in-config/79078/40

@nixos-discourse

Copy link
Copy Markdown

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/secretspec-0-20/79863/1

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

Labels

documentation fetching Networking with the outside (non-Nix) world, input locking store Issues and pull requests concerning the Nix store with-tests Issues related to testing. PRs with tests have some priority

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Specify access token via file

7 participants