summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2026-05-30 23:54:05 +0200
committerLudovic Courtès <ludo@gnu.org>2026-08-17 16:47:36 +0200
commit64d4de2a920445e5992f020e56490f5fcbdbba7c (patch)
treea869360b32fbc5fb0f49d297708ddde801aa3a95 /tests
parent8cb871e0c69030599fa8e8410f1ecaee21ddbbd0 (diff)
daemon: Bypass authentication when importing content-addressed store items.
This puts ‘importPaths’ on par with ‘addToStore’ and ‘addTextToStore’: since the two latter RPCs let anyone add content-addressed items in the store, there’s no reason for ‘importPaths’ to require signatures by authorized keys on these content-addressed items. This will allow for things like ‘guix copy’ of .drv items without authorization, or ‘guix deploy’ with (build-locally? #f) without authorization. * nix/libstore/store-api.hh (isContentAddressedPath): New prototype. * nix/libstore/store-api.cc (isContentAddressedPath): New function. * nix/libutil/util.hh (isPlainFile): New prototype. * nix/libutil/util.cc (isPlainFile): New function. * nix/libstore/local-store.cc (LocalStore::importPath): Define ‘narHash’ and ‘contentAddressed’. Allow unsigned imports when ‘contentAddressed’ is true; bypass signature verification when ‘contentAddressed’ is true. * tests/store.scm ("import not signed"): Rewrite to not use a content-addressed store item. ("import signed by unauthorized key"): Likewise. ("import not signed but content-addressed tree"): New test. ("import not signed but content-addressed regular file"): New test. ("import signed by unauthorized key but content-addressed"): New test. ("import with corrupt signature"): New test. ("import signed by authorized key but hash doesn't match"): New test. ("import with corrupt signature but content-addressed"): New test. ("import signed by authorized key, hash doesn't match, but content-addressed"): New test. * doc/guix.texi (Invoking guix archive): Document the exception for content-addressed store items. Add anchor for ‘--authorize’. (Invoking guix deploy): Document the benefit of (build-locally? #f). Add cross-reference for ‘authorize?’. Co-authored-by: Reepca Russelstein <reepca@russelstein.xyz> Signed-off-by: Ludovic Courtès <ludo@gnu.org> Merges: #8979
Diffstat (limited to 'tests')
-rw-r--r--tests/store.scm295
1 files changed, 280 insertions, 15 deletions
diff --git a/tests/store.scm b/tests/store.scm
index e7ba7469047..ba634ce2205 100644
--- a/tests/store.scm
+++ b/tests/store.scm
@@ -30,7 +30,8 @@
30 #:use-module (guix derivations) 30 #:use-module (guix derivations)
31 #:use-module ((guix modules) 31 #:use-module ((guix modules)
32 #:select (source-module-closure)) 32 #:select (source-module-closure))
33 #:use-module (guix serialization) 33 #:use-module ((guix serialization)
34 #:hide (store-path read-string))
34 #:use-module (guix build utils) 35 #:use-module (guix build utils)
35 #:use-module ((gnu build linux-container) 36 #:use-module ((gnu build linux-container)
36 #:select (unprivileged-user-namespace-supported?)) 37 #:select (unprivileged-user-namespace-supported?))
@@ -1456,18 +1457,25 @@ System: x86_64-linux~%"
1456 1457
1457 1458
1458(test-assert "import not signed" 1459(test-assert "import not signed"
1459 (let* ((text (random-text)) 1460 (let* ((content (random-text))
1460 (file (add-file-tree-to-store %store 1461 (name "fake-derivation")
1461 `("tree" directory 1462 (store-item (output-path "out"
1462 ("text" regular (data ,text)) 1463 (gcrypt:sha256 #vu8())
1463 ("link" symlink "text")))) 1464 name))
1464 (dump (call-with-bytevector-output-port 1465 (dump (call-with-bytevector-output-port
1465 (lambda (port) 1466 (lambda (port)
1466 (write-int 1 port) ;start 1467 (write-int 1 port) ;start
1467 1468
1468 (write-file file port) ;contents 1469 (write-file-tree name port ;contents
1470 #:file-type+size
1471 (lambda (_)
1472 (values 'regular
1473 (string-length content)))
1474 #:file-port
1475 (lambda (_)
1476 (open-input-string content)))
1469 (write-int #x4558494e port) ;%export-magic 1477 (write-int #x4558494e port) ;%export-magic
1470 (write-string file port) ;store item 1478 (write-string store-item port) ;store item
1471 (write-string-list '() port) ;references 1479 (write-string-list '() port) ;references
1472 (write-string "" port) ;deriver 1480 (write-string "" port) ;deriver
1473 (write-int 0 port) ;not signed 1481 (write-int 0 port) ;not signed
@@ -1485,11 +1493,11 @@ System: x86_64-linux~%"
1485 #f)))) 1493 #f))))
1486 1494
1487(test-assert "import signed by unauthorized key" 1495(test-assert "import signed by unauthorized key"
1488 (let* ((text (random-text)) 1496 (let* ((content (random-text))
1489 (file (add-file-tree-to-store %store 1497 (name "fake-derivation")
1490 `("tree" directory 1498 (store-item (output-path "out"
1491 ("text" regular (data ,text)) 1499 (gcrypt:sha256 #vu8())
1492 ("link" symlink "text")))) 1500 name))
1493 (key (gcrypt:generate-key 1501 (key (gcrypt:generate-key
1494 (gcrypt:string->canonical-sexp 1502 (gcrypt:string->canonical-sexp
1495 "(genkey (ecdsa (curve Ed25519) (flags rfc6979)))"))) 1503 "(genkey (ecdsa (curve Ed25519) (flags rfc6979)))")))
@@ -1497,9 +1505,16 @@ System: x86_64-linux~%"
1497 (lambda (port) 1505 (lambda (port)
1498 (write-int 1 port) ;start 1506 (write-int 1 port) ;start
1499 1507
1500 (write-file file port) ;contents 1508 (write-file-tree name port ;contents
1509 #:file-type+size
1510 (lambda (_)
1511 (values 'regular
1512 (string-length content)))
1513 #:file-port
1514 (lambda (_)
1515 (open-input-string content)))
1501 (write-int #x4558494e port) ;%export-magic 1516 (write-int #x4558494e port) ;%export-magic
1502 (write-string file port) ;store item 1517 (write-string store-item port) ;store item
1503 (write-string-list '() port) ;references 1518 (write-string-list '() port) ;references
1504 (write-string "" port) ;deriver 1519 (write-string "" port) ;deriver
1505 (write-int 1 port) ;signed 1520 (write-int 1 port) ;signed
@@ -1524,6 +1539,256 @@ System: x86_64-linux~%"
1524 (pk 'unauthorized-imported imported) 1539 (pk 'unauthorized-imported imported)
1525 #f)))) 1540 #f))))
1526 1541
1542(test-assert "import with corrupt signature"
1543 (let* ((content (random-text))
1544 (name "fake-derivation")
1545 (store-item (output-path "out"
1546 (gcrypt:sha256 #vu8())
1547 name))
1548 (dump (call-with-bytevector-output-port
1549 (lambda (port)
1550 (write-int 1 port) ;start
1551
1552 (write-file-tree name port ;contents
1553 #:file-type+size
1554 (lambda (_)
1555 (values 'regular
1556 (string-length content)))
1557 #:file-port
1558 (lambda (_)
1559 (open-input-string content)))
1560 (write-int #x4558494e port) ;%export-magic
1561 (write-string store-item port) ;store item
1562 (write-string-list '() port) ;references
1563 (write-string "" port) ;deriver
1564 (write-int 1 port) ;signed
1565 (write-string (object->string '(signature broken))
1566 port)
1567
1568 (write-int 0 port))))) ;done
1569
1570 ;; Ensure 'import-paths' raises an exception.
1571 (guard (c ((store-protocol-error? c)
1572 (and (not (zero? (store-protocol-error-status c)))
1573 (string-contains (store-protocol-error-message c)
1574 "corrupt signature"))))
1575 (let* ((source (open-bytevector-input-port dump))
1576 (imported (import-paths %store source)))
1577 (pk 'corrupt-signature-imported imported)
1578 #f))))
1579
1580(test-assert "import signed by authorized key but hash doesn't match"
1581 (let* ((content (random-text))
1582 (name "fake-derivation")
1583 (store-item (output-path "out"
1584 (gcrypt:sha256 #vu8())
1585 name))
1586 ;; This key is known to be in the ACL by default.
1587 (public-key
1588 (call-with-input-file (string-append %config-directory "/signing-key.pub")
1589 (compose gcrypt:string->canonical-sexp get-string-all)))
1590 (private-key
1591 (call-with-input-file (string-append %config-directory "/signing-key.sec")
1592 (compose gcrypt:string->canonical-sexp get-string-all)))
1593
1594 (dump (call-with-bytevector-output-port
1595 (lambda (port)
1596 (write-int 1 port) ;start
1597
1598 (write-file-tree name port ;contents
1599 #:file-type+size
1600 (lambda (_)
1601 (values 'regular
1602 (string-length content)))
1603 #:file-port
1604 (lambda (_)
1605 (open-input-string content)))
1606 (write-int #x4558494e port) ;%export-magic
1607 (write-string store-item port) ;store item
1608 (write-string-list '() port) ;references
1609 (write-string "" port) ;deriver
1610 (write-int 1 port) ;signed
1611 (write-string (gcrypt:canonical-sexp->string
1612 (signature-sexp
1613 (gcrypt:bytevector->hash-data
1614 (gcrypt:sha256 #vu8(0 1 2))
1615 #:key-type 'rsa)
1616 private-key
1617 public-key))
1618 port)
1619
1620 (write-int 0 port))))) ;done
1621
1622 ;; Ensure 'import-paths' raises an exception.
1623 (guard (c ((store-protocol-error? c)
1624 (and (not (zero? (store-protocol-error-status c)))
1625 (string-contains (store-protocol-error-message c)
1626 "hash doesn't match"))))
1627 (let* ((source (open-bytevector-input-port dump))
1628 (imported (import-paths %store source)))
1629 (pk 'hash-mismatch-imported imported)
1630 #f))))
1631
1632(test-assert "import not signed but content-addressed tree"
1633 (let* ((text (random-text))
1634 (file (add-file-tree-to-store %store
1635 `("tree" directory
1636 ("text" regular (data ,text))
1637 ("link" symlink "text"))))
1638 (dump (call-with-bytevector-output-port
1639 (lambda (port)
1640 (write-int 1 port) ;start
1641
1642 (write-file file port) ;contents
1643 (write-int #x4558494e port) ;%export-magic
1644 (write-string file port) ;store item
1645 (write-string-list '() port) ;references
1646 (write-string "" port) ;deriver
1647 (write-int 0 port) ;not signed
1648
1649 (write-int 0 port))))) ;done
1650
1651 ;; Ensure 'import-paths' completes despite the lack of signature.
1652 (let ((source (open-bytevector-input-port dump)))
1653 (match (import-paths %store source)
1654 ((imported)
1655 (string=? (pk 'imported imported) file))))))
1656
1657(test-assert "import not signed but content-addressed regular file"
1658 (let* ((name "content-addressed")
1659 (content (random-text))
1660 (store-item (store-path "text"
1661 (gcrypt:sha256 (string->utf8 content))
1662 name))
1663 (dump (call-with-bytevector-output-port
1664 (lambda (port)
1665 (write-int 1 port) ;start
1666 (write-file-tree name port ;contents
1667 #:file-type+size
1668 (lambda (_)
1669 (values 'regular
1670 (string-length content)))
1671 #:file-port
1672 (lambda (_)
1673 (open-input-string content)))
1674 (write-int #x4558494e port) ;%export-magic
1675 (write-string store-item port)
1676 (write-string-list '() port) ;references
1677 (write-string "" port) ;deriver
1678 (write-int 0 port) ;not signed
1679 (write-int 0 port))))) ;done
1680
1681 ;; Ensure 'import-paths' completes despite the lack of signature.
1682 (let ((source (open-bytevector-input-port dump)))
1683 (match (import-paths %store source)
1684 ((imported)
1685 (and (string=? (pk 'imported imported) store-item)
1686 (string=? (call-with-input-file imported get-string-all)
1687 content)))))))
1688
1689(test-assert "import signed by unauthorized key but content-addressed"
1690 (let* ((text (random-text))
1691 (file (add-file-tree-to-store %store
1692 `("tree" directory
1693 ("text" regular (data ,text))
1694 ("link" symlink "text"))))
1695 (key (gcrypt:generate-key
1696 (gcrypt:string->canonical-sexp
1697 "(genkey (ecdsa (curve Ed25519) (flags rfc6979)))")))
1698 (dump (call-with-bytevector-output-port
1699 (lambda (port)
1700 (write-int 1 port) ;start
1701
1702 (write-file file port) ;contents
1703 (write-int #x4558494e port) ;%export-magic
1704 (write-string file port) ;store item
1705 (write-string-list '() port) ;references
1706 (write-string "" port) ;deriver
1707 (write-int 1 port) ;signed
1708 (write-string (gcrypt:canonical-sexp->string
1709 (signature-sexp
1710 (gcrypt:bytevector->hash-data
1711 (gcrypt:sha256 #vu8(0 1 2))
1712 #:key-type 'ecc)
1713 (gcrypt:find-sexp-token key 'private-key)
1714 (gcrypt:find-sexp-token key 'public-key)))
1715 port)
1716
1717 (write-int 0 port))))) ;done
1718
1719 ;; Ensure 'import-paths' succeeds despite the unauthorized signature.
1720 (let ((source (open-bytevector-input-port dump)))
1721 (match (import-paths %store source)
1722 ((imported)
1723 (string=? imported file))))))
1724
1725(test-assert "import with corrupt signature but content-addressed"
1726 (let* ((text (random-text))
1727 (file (add-file-tree-to-store %store
1728 `("tree" directory
1729 ("text" regular (data ,text))
1730 ("link" symlink "text"))))
1731 (dump (call-with-bytevector-output-port
1732 (lambda (port)
1733 (write-int 1 port) ;start
1734
1735 (write-file file port) ;contents
1736 (write-int #x4558494e port) ;%export-magic
1737 (write-string file port) ;store item
1738 (write-string-list '() port) ;references
1739 (write-string "" port) ;deriver
1740 (write-int 1 port) ;signed
1741 (write-string (object->string '(signature broken))
1742 port)
1743
1744 (write-int 0 port))))) ;done
1745
1746 ;; Ensure 'import-paths' succeeds despite the corrupt signature.
1747 (let ((source (open-bytevector-input-port dump)))
1748 (match (import-paths %store source)
1749 ((imported)
1750 (string=? imported file))))))
1751
1752(test-assert "import signed by authorized key, hash doesn't match, but content-addressed"
1753 (let* ((text (random-text))
1754 (file (add-file-tree-to-store %store
1755 `("tree" directory
1756 ("text" regular (data ,text))
1757 ("link" symlink "text"))))
1758 ;; This key is known to be in the ACL by default.
1759 (public-key
1760 (call-with-input-file (string-append %config-directory "/signing-key.pub")
1761 (compose gcrypt:string->canonical-sexp get-string-all)))
1762 (private-key
1763 (call-with-input-file (string-append %config-directory "/signing-key.sec")
1764 (compose gcrypt:string->canonical-sexp get-string-all)))
1765 (dump (call-with-bytevector-output-port
1766 (lambda (port)
1767 (write-int 1 port) ;start
1768
1769 (write-file file port) ;contents
1770 (write-int #x4558494e port) ;%export-magic
1771 (write-string file port) ;store item
1772 (write-string-list '() port) ;references
1773 (write-string "" port) ;deriver
1774 (write-int 1 port) ;signed
1775 (write-string (gcrypt:canonical-sexp->string
1776 (signature-sexp
1777 (gcrypt:bytevector->hash-data
1778 (gcrypt:sha256 #vu8(0 1 2))
1779 #:key-type 'rsa)
1780 private-key
1781 public-key))
1782 port)
1783
1784 (write-int 0 port))))) ;done
1785
1786 ;; Ensure 'import-paths' succeeds despite the hash mismatch.
1787 (let ((source (open-bytevector-input-port dump)))
1788 (match (import-paths %store source)
1789 ((imported)
1790 (string=? imported file))))))
1791
1527(test-assert "import corrupt path" 1792(test-assert "import corrupt path"
1528 (let* ((text (random-text)) 1793 (let* ((text (random-text))
1529 (file (add-text-to-store %store "text" text)) 1794 (file (add-text-to-store %store "text" text))