git-authenticate: Prevent removal of '.guix-authorizations'.

* guix/git-authenticate.scm (commit-authorized-keys)
[parents-have-authorizations-file?, assert-parents-lack-authorizations]:
New procedures.
Use the latter before returning DEFAULT-AUTHORIZATIONS.
* guix/git.scm (false-if-git-not-found): Export.
* guix/tests/git.scm (populate-git-repository): Add 'remove' clause.
* tests/git-authenticate.scm ("signed commits, .guix-authorizations removed"):
New test.
This commit is contained in:
Ludovic Courtès 2020-06-07 23:06:41 +02:00
parent 1fd7de45f2
commit e782756080
No known key found for this signature in database
GPG Key ID: 090B11993D9AEBB5
4 changed files with 72 additions and 1 deletions

View File

@ -19,6 +19,7 @@
(define-module (guix git-authenticate)
#:use-module (git)
#:use-module (guix base16)
#:use-module ((guix git) #:select (false-if-git-not-found))
#:use-module (guix i18n)
#:use-module (guix openpgp)
#:use-module ((guix utils)
@ -145,6 +146,27 @@ return a list of authorized fingerprints."
"Return the list of OpenPGP fingerprints authorized to sign COMMIT, based on
authorizations listed in its parent commits. If one of the parent commits
does not specify anything, fall back to DEFAULT-AUTHORIZATIONS."
(define (parents-have-authorizations-file? commit)
;; Return true if at least one of the parents of COMMIT has the
;; '.guix-authorizations' file.
(find (lambda (commit)
(false-if-git-not-found
(tree-entry-bypath (commit-tree commit)
".guix-authorizations")))
(commit-parents commit)))
(define (assert-parents-lack-authorizations commit)
;; If COMMIT removes the '.guix-authorizations' file found in one of its
;; parents, raise an error.
(when (parents-have-authorizations-file? commit)
(raise (condition
(&unauthorized-commit-error (commit (commit-id commit))
(signing-key #f))
(&message
(message (format #f (G_ "commit ~a attempts \
to remove '.guix-authorizations' file")
(oid->string (commit-id commit)))))))))
(define (commit-authorizations commit)
(catch 'git-error
(lambda ()
@ -155,7 +177,11 @@ does not specify anything, fall back to DEFAULT-AUTHORIZATIONS."
(open-bytevector-input-port (blob-content blob)))))
(lambda (key error)
(if (= (git-error-code error) GIT_ENOTFOUND)
default-authorizations
(begin
;; Prevent removal of '.guix-authorizations' since it would make
;; it trivial to force a fallback to DEFAULT-AUTHORIZATIONS.
(assert-parents-lack-authorizations commit)
default-authorizations)
(throw key error)))))
(apply lset-intersection bytevector=?

View File

@ -39,6 +39,7 @@
honor-system-x509-certificates!
with-repository
false-if-git-not-found
update-cached-checkout
url+commit->name
latest-repository-commit

View File

@ -76,6 +76,9 @@ Return DIRECTORY on success."
port)))
(git "add" file)
(loop rest)))
((('remove file) rest ...)
(git "rm" "-f" file)
(loop rest))
((('commit text) rest ...)
(git "commit" "-m" text)
(loop rest))

View File

@ -282,5 +282,46 @@
merge master3)
#:keyring-reference "master"))))))
(unless (gpg+git-available?) (test-skip 1))
(test-assert "signed commits, .guix-authorizations removed"
(with-fresh-gnupg-setup (list %ed25519-public-key-file
%ed25519-secret-key-file)
(with-temporary-git-repository directory
`((add "signer.key" ,(call-with-input-file %ed25519-public-key-file
get-string-all))
(add ".guix-authorizations"
,(object->string
`(authorizations (version 0)
((,(key-fingerprint
%ed25519-public-key-file)
(name "Charlie"))))))
(commit "zeroth commit")
(add "a.txt" "A")
(commit "first commit"
(signer ,(key-fingerprint %ed25519-public-key-file)))
(remove ".guix-authorizations")
(commit "second commit"
(signer ,(key-fingerprint %ed25519-public-key-file)))
(add "b.txt" "B")
(commit "third commit"
(signer ,(key-fingerprint %ed25519-public-key-file))))
(with-repository directory repository
(let ((commit1 (find-commit repository "first"))
(commit2 (find-commit repository "second"))
(commit3 (find-commit repository "third")))
;; COMMIT1 and COMMIT2 are fine.
(and (authenticate-commits repository (list commit1 commit2)
#:keyring-reference "master")
;; COMMIT3 is rejected because COMMIT2 removes
;; '.guix-authorizations'.
(guard (c ((unauthorized-commit-error? c)
(oid=? (git-authentication-error-commit c)
(commit-id commit2))))
(authenticate-commits repository
(list commit1 commit2 commit3)
#:keyring-reference "master")
'failed)))))))
(test-end "git-authenticate")