diff --git a/pkgs/build-support/fetchgit/default.nix b/pkgs/build-support/fetchgit/default.nix index b2f5f15a309da..a2db81192ae26 100644 --- a/pkgs/build-support/fetchgit/default.nix +++ b/pkgs/build-support/fetchgit/default.nix @@ -4,6 +4,7 @@ git, git-lfs, cacert, + callPackage, }: let @@ -64,6 +65,9 @@ lib.makeOverridable ( fetchTags ? false, # make this subdirectory the root of the result rootDir ? "", + publicKeys ? [ ], + verifyCommit ? false, + verifyTag ? false, }: /* @@ -108,89 +112,114 @@ lib.makeOverridable ( else # FIXME fetching HEAD if no rev or tag is provided is problematic at best "HEAD"; + fetchresult = + if builtins.isString sparseCheckout then + # Changed to throw on 2023-06-04 + throw + "Please provide directories/patterns for sparse checkout as a list of strings. Passing a (multi-line) string is not supported any more." + else + stdenvNoCC.mkDerivation { + inherit name; + + builder = ./builder.sh; + fetcher = ./nix-prefetch-git; + + nativeBuildInputs = [ + git + cacert + ] + ++ lib.optionals fetchLFS [ git-lfs ] + ++ nativeBuildInputs; + + inherit outputHash outputHashAlgo; + outputHashMode = "recursive"; + + # git-sparse-checkout(1) says: + # > When the --stdin option is provided, the directories or patterns are read + # > from standard in as a newline-delimited list instead of from the arguments. + sparseCheckout = builtins.concatStringsSep "\n" sparseCheckout; + + leaveDotGit = leaveDotGit || verifyCommit || verifyTag; + + inherit + url + fetchLFS + fetchSubmodules + deepClone + branchName + nonConeMode + preFetch + postFetch + fetchTags + rootDir + ; + rev = revWithTag; + + NIX_PREFETCH_GIT_CHECKOUT_HOOK = + if verifyTag then + '' + clean_git -C "$dir" fetch origin "$rev:$rev" + '' + else + null; + + postHook = + if netrcPhase == null then + null + else + '' + ${netrcPhase} + # required that git uses the netrc file + mv {,.}netrc + export NETRC=$PWD/.netrc + export HOME=$PWD + ''; + + impureEnvVars = + lib.fetchers.proxyImpureEnvVars + ++ netrcImpureEnvVars + ++ [ + "GIT_PROXY_COMMAND" + "NIX_GIT_SSL_CAINFO" + "SOCKS_SERVER" + + # This is a parameter intended to be set by setup hooks or preFetch + # scripts that want per-URL control over HTTP proxies used by Git + # (if per-URL control isn't needed, `http_proxy` etc. will + # suffice). It must be a whitespace-separated (with backslash as an + # escape character) list of pairs like this: + # + # http://domain1/path1 proxy1 https://domain2/path2 proxy2 + # + # where the URLs are as documented in the `git-config` manual page + # under `http..*`, and the proxies are as documented on the + # same page under `http.proxy`. + "FETCHGIT_HTTP_PROXIES" + ]; + + inherit preferLocalBuild meta allowedRequisites; + + passthru = { + gitRepoUrl = url; + inherit tag; + }; + }; + + verifySignature = callPackage ./verify.nix { }; in - - if builtins.isString sparseCheckout then - # Changed to throw on 2023-06-04 - throw - "Please provide directories/patterns for sparse checkout as a list of strings. Passing a (multi-line) string is not supported any more." - else - stdenvNoCC.mkDerivation { - inherit name; - - builder = ./builder.sh; - fetcher = ./nix-prefetch-git; - - nativeBuildInputs = [ - git - cacert - ] - ++ lib.optionals fetchLFS [ git-lfs ] - ++ nativeBuildInputs; - - inherit outputHash outputHashAlgo; - outputHashMode = "recursive"; - - # git-sparse-checkout(1) says: - # > When the --stdin option is provided, the directories or patterns are read - # > from standard in as a newline-delimited list instead of from the arguments. - sparseCheckout = builtins.concatStringsSep "\n" sparseCheckout; - + if verifyCommit || verifyTag then + verifySignature { inherit - url + revWithTag + verifyCommit + verifyTag + publicKeys leaveDotGit - fetchLFS - fetchSubmodules - deepClone - branchName - nonConeMode - preFetch - postFetch - fetchTags - rootDir + fetchresult + name ; - rev = revWithTag; - - postHook = - if netrcPhase == null then - null - else - '' - ${netrcPhase} - # required that git uses the netrc file - mv {,.}netrc - export NETRC=$PWD/.netrc - export HOME=$PWD - ''; - - impureEnvVars = - lib.fetchers.proxyImpureEnvVars - ++ netrcImpureEnvVars - ++ [ - "GIT_PROXY_COMMAND" - "NIX_GIT_SSL_CAINFO" - "SOCKS_SERVER" - - # This is a parameter intended to be set by setup hooks or preFetch - # scripts that want per-URL control over HTTP proxies used by Git - # (if per-URL control isn't needed, `http_proxy` etc. will - # suffice). It must be a whitespace-separated (with backslash as an - # escape character) list of pairs like this: - # - # http://domain1/path1 proxy1 https://domain2/path2 proxy2 - # - # where the URLs are as documented in the `git-config` manual page - # under `http..*`, and the proxies are as documented on the - # same page under `http.proxy`. - "FETCHGIT_HTTP_PROXIES" - ]; - - inherit preferLocalBuild meta allowedRequisites; - - passthru = { - gitRepoUrl = url; - inherit tag; - }; } + else + fetchresult ) ) diff --git a/pkgs/build-support/fetchgit/tests.nix b/pkgs/build-support/fetchgit/tests.nix index e3fdd5fb8672a..68b6aa651ae09 100644 --- a/pkgs/build-support/fetchgit/tests.nix +++ b/pkgs/build-support/fetchgit/tests.nix @@ -1,4 +1,9 @@ -{ testers, fetchgit, ... }: +{ + testers, + fetchgit, + fetchurl, + ... +}: { simple = testers.invalidateFetcherByDrvHash fetchgit { name = "simple-nix-source"; @@ -105,4 +110,52 @@ rootDir = "misc/systemd"; sha256 = "sha256-UhxHk4SrXYq7ZDMtXLig5SigpbITrVgkpFTmryuvpcM="; }; + + ssh-commit-verification = testers.invalidateFetcherByDrvHash fetchgit { + name = "ssh-commit-verification-source"; + url = "https://codeberg.org/flandweber/git-verify"; + rev = "a43858e8f106b313aed68b6455a45340db7dd758"; + sha256 = "sha256-IH2ed4oRruhWkPorcEETmbzWpaqY6/tNSUUMk+ntZ3M="; + verifyCommit = true; + publicKeys = [ + { + type = "ssh-ed25519"; + key = "AAAAC3NzaC1lZDI1NTE5AAAAIBiNDWMPZNRItkm1U1CQkJUUrrmM+l7CdE6wyUHzr4Nr"; + } + ]; + }; + + gpg-commit-verification = testers.invalidateFetcherByDrvHash fetchgit { + name = "gpg-commit-verification-source"; + url = "https://gitlab.torproject.org/tpo/core/tor"; + rev = "8888e4ca6b44bb7476139be4644e739035441b35"; + sha256 = "sha256-HSYUwzm3k4GAIt/ds80i8HM8hZLgC7zTUJBqhF6wBvA="; + verifyCommit = true; + publicKeys = [ + { + type = "gpg"; + key = fetchurl { + url = "https://web.archive.org/web/20241109193517/https://keys.openpgp.org/vks/v1/by-fingerprint/5EF3A41171BB77E6110ED2D01F3D03348DB1A3E2"; + sha256 = "sha256-xvBWfaS1py7vyDIIYGtATqBOnWafd3B6OB2Blhfm4MU="; + }; + } + ]; + }; + + gpg-tag-verification = testers.invalidateFetcherByDrvHash fetchgit { + name = "gpg-tag-verification-source"; + url = "https://gitlab.torproject.org/tpo/core/tor"; + tag = "tor-0.4.8.12"; + sha256 = "sha256-AXVD5I7KyDVAPIOHcPRHHfW0uwPxjCuY9t1Bf/pBLps="; + verifyTag = true; + publicKeys = [ + { + type = "gpg"; + key = fetchurl { + url = "https://web.archive.org/web/20241109193821/https://keys.openpgp.org/vks/v1/by-fingerprint/B74417EDDF22AC9F9E90F49142E86A2A11F48D36"; + sha256 = "sha256-M4mvelY1nLeGuhgZIpF4oAe80kbJl2+wcDI6zp9YwXo="; + }; + } + ]; + }; } diff --git a/pkgs/build-support/fetchgit/verify.nix b/pkgs/build-support/fetchgit/verify.nix new file mode 100644 index 0000000000000..8d0171f471fca --- /dev/null +++ b/pkgs/build-support/fetchgit/verify.nix @@ -0,0 +1,89 @@ +{ + lib, + runCommand, + writeShellApplication, + writeText, + git, + openssh, + gnupg, +}: +( + { + name, + revWithTag, + verifyCommit, + verifyTag, + publicKeys, + leaveDotGit, + fetchresult, + }: + let + # split gpg keys from ssh keys + keysPartitioned = lib.partition (k: k.type == "gpg") publicKeys; + gpgKeys = lib.catAttrs "key" keysPartitioned.right; + sshKeys = keysPartitioned.wrong; + # create a keyring containing gpgKeys + gpgKeyring = runCommand "gpgKeyring" { buildInputs = [ gnupg ]; } '' + gpg --homedir /build --no-default-keyring --keyring $out --fingerprint # create empty keyring at $out + for KEY in ${lib.concatStringsSep " " gpgKeys} + do + gpg --homedir /build --no-default-keyring --keyring $out --import $KEY # import $KEY + done + ''; + gitOnRepo = writeShellApplication { + name = "gitOnRepo"; + runtimeInputs = [ git ]; + text = '' + git -C "${fetchresult}" -c safe.directory='*' "$@" + ''; + }; + # wrap gpg to use gpgKeyring + gpgWithKeys = writeShellApplication { + name = "gpgWithKeys"; + runtimeInputs = [ + gnupg + gitOnRepo + ]; + text = '' + committerTime="$(gitOnRepo -c core.pager=cat log --format="%cd" --date=raw -n 1 ${revWithTag})" + gpg --faked-system-time "$committerTime" --homedir /build --no-default-keyring --always-trust --keyring ${gpgKeyring} "$@" + ''; + }; + # create "allowed signers" file for ssh key verification: https://man.openbsd.org/ssh-keygen.1#ALLOWED_SIGNERS + allowedSignersFile = writeText "allowed signers" ( + lib.concatMapStrings (k: "* ${k.type} ${k.key}\n") sshKeys + ); + in + runCommand name + { + buildInputs = [ + gitOnRepo + openssh + gpgWithKeys + ]; + inherit verifyCommit verifyTag leaveDotGit; + } + '' + gpgWithKeys -k + if test "$verifyCommit" == 1; then + gitOnRepo \ + -c gpg.ssh.allowedSignersFile="${allowedSignersFile}" \ + -c gpg.program="gpgWithKeys" \ + verify-commit ${revWithTag} + fi + + if test "$verifyTag" == 1; then + gitOnRepo \ + -c gpg.ssh.allowedSignersFile="${allowedSignersFile}" \ + -c gpg.program="gpgWithKeys" \ + verify-tag ${revWithTag} + fi + + if test "$leaveDotGit" != 1; then + cp -r --no-preserve=all "${fetchresult}" $out + rm -rf "$out"/.git + else + ln "${fetchresult}" $out + fi + '' +)