NixOS / nix

Nix, the purely functional package manager
https://nixos.org/
GNU Lesser General Public License v2.1
11.55k stars 1.45k forks source link

Flake authentication #2849

Open edolstra opened 5 years ago

edolstra commented 5 years ago

Inspired by https://github.com/NixOS/rfcs/pull/34 discussion:

Currently we don't have any way to authenticate flakes. We only have transport security, e.g. by downloading via https or ssh, so if the server is compromised, it can serve Trojaned flakes. Thus, we would like to have the ability to require that flakes are signed in some way.

Authentication is only needed when fetching from a mutable flake reference (i.e. one that doesn't include a Git revision or content hash). It will be assumed that the immutable flake references in a lock file were authenticated at the time the lock file was generated. [Actually it just occurs to me that for security, github flakes must include a content hash in the lock file, since we have no way to verify that the Git revision corresponds to the contents.]

Ideally, the flake registry would contain public keys that are allowed to sign the trees fetched from a mutable flake reference. For example,

{
    "version": 1,
    "flakes": {
        "dwarffs": {
            "uri": "github:edolstra/dwarffs/flake",
            "publicKeys": ["E52y5ZkdNLl3LUYl3TiwR7XTZAPU7jgkEYDvsmbavio="]
        },
        "nixpkgs": {
            "uri": "github:NixOS/nixpkgs-channels",
            "publicKeys": ["FioIjAooQuwH200U4u5H2asLDCNZXaHpmxQnj2fcggQ="]
        }
    }
}

Alternatively, public keys could be part of a flake URI, e.g. a flake dependency could specify a public key like this:

{
  requires = [
    github:edolstra/dwarffs?publicKey=E52y5ZkdNLl3LUYl3TiwR7XTZAPU7jgkEYDvsmbavio=
  ];
}

However, the main issue is how to associate a signature with the downloaded flake.

Another possibility is to fetch the signature from some content-addressable source specified in the flake registry, e.g. "nixpkgs": { "sigs": "https://nixos.org/flake-sigs", ... }. The client would compute the hash of the data to sign (see below) and then download the signature from https://nixos.org/flake-sigs/<hash>.

Signature contents: The signature should be over the string <content-hash>:<git-revision>:<git-ref>:<refcount-or-commit-date>. The git-ref (branch or tag name) is included to prevent an attack where the attacker replaces one channel (e.g. nixos-19.03) with another (e.g. nixos-unstable). refcount-or-commit-date could be used to prevent rollback attacks, but this requires the client to keep some state about the last refcount or date it saw from a mutable flake.

Registry authentication: The registry should also be signed. The public signing key will be hard-coded in Nix.

Certificates: We should allow the private signing key ("cold key") for a flake to be kept offline. This can be done by signing the flake using a hot key, and including in the signature file a certificate where the hot key signs the cold key. Revoked keys could be listed in the registry. (This would be pretty nice to have for binary cache signing as well.)

CSVdB commented 5 years ago

How can you sign mutable references? They can by definition change, so including anything like a content hash is impossible. Who would be able to sign these things exactly? Is that GitHub itself?

For immutable references, I would definitely try to include some sort of content hash to secure against GitHub being hacked.

edolstra commented 5 years ago

You don't sign mutable references, you sign particular versions of a flake. This could be done as a signature over an immutable reference. But the real question is where to get the signatures from.

stale[bot] commented 3 years ago

I marked this as stale due to inactivity. → More info

hmenke commented 3 years ago

This is still important to me. I don't know whether that is feasible in general but at least for Git Nix could just use existing commit signatures. That's sort of portable because there already exists instrumentation in Git and it would also work for non-flake references to Git repos.

L-as commented 2 years ago

Relevant: https://github.com/NixOS/rfcs/pull/100

stale[bot] commented 2 years ago

I marked this as stale due to inactivity. → More info

hmenke commented 2 years ago

Begone, bot!

cyntheticfox commented 1 year ago

I'm particularly concerned about this, but mostly about what was a footnote: registry signing.

While it's a good idea to validate/sign the flakes themselves, the global registry itself should be signed and validated, since otherwise that opens up the possibility of server spoofing (DNS poisoning most likely).

The two issues can probably be solved separately even: checking whether or not the list of aliases provided by the registry is provided securely doesn't have to be tied to whether or not the resulting reference is secure.

Even then, that would mean you could then build a secure registry by providing aliases to specific revisions (possibly with hashing? idk about that part).

benaryorg commented 1 year ago

This is still important to me. I don't know whether that is feasible in general but at least for Git Nix could just use existing commit signatures. That's sort of portable because there already exists instrumentation in Git and it would also work for non-flake references to Git repos.

I fully agree. Specifically using built in git GPG signatures means that, given a trust store is provided out-of-band, any git repository with any hoster can be used, whether it be GitHub, GitLab, self-hosted Gitea, whatever Smart HTTP implementation or just bare repositories via SSH, it incurs no dependency on infrastructure that can be compromised other than the developer of the flake. The signatures do not only apply to commits, they also apply to tags, which would allow one to mirror an upstream flake repository without tags and push signed tags to the mirror for commits which have been verified in some form (code review for external sources is important in hardened environments). Furthermore all signatures apply to the commit, which includes the tree-reference among other data, which by extension is also verified, thus we can rely on the provided flake.lock being verified, and as long as the refs and proposed content-hashes provided therein can be fetched we also have a theoretical verification of the entire supply chain.

As an added benefit this does not require any additional tooling for the developers of flakes; everything requires is built-in and ships with git, provided the runtime dependencies are met (gpg is in your PATH).

While I am a strong proponent of ed25519 signatures everywhere, git already provides plenty of tooling for this use-case. If additional tooling was required or even new workflows (such as signing release tarballs) I am sure this would lead to either less adoption, or adoption in ways that actually reduce security (think of CI pipelines signing code; opening up side-channels).

but I would prefer to avoid GPG because 1) it's 1990s crypto

I'd like to remind you that the Linux kernel is 1990s stuff too, as is Plan9, as is SSH, all of which are several years older than GPG actually (which is a 1999 thing), making it four years younger than the initial IPv6 drafts.

cyntheticfox commented 1 year ago

Given the added support for git signing with ssh keys since I last commented, and that recent debacle of GitHub exposing their SSH host key, it might be an increasingly decent idea...

EDIT: Would be of use here? Haven't used it so I have no idea.