Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

docs: secure mode

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>

author
Anirudh Oppiliappan
committer
Tangled
date (Jun 12, 2026, 12:22 PM +0300) commit 477659de parent e5c6b73c change-id uvzktpsw
+119
+119
docs/DOCS.md
··· 603 603 Note that you should add a newline at the end if setting a non-empty message 604 604 since the knot won't do this for you. 605 605 606 + ## Secure Mode 607 + 608 + Secure Mode isolates each `git` subprocess to the repository it is 609 + operating on, using two mechanisms: 610 + 611 + - **Linux Landlock** restricts the filesystem paths the subprocess 612 + can access -- it can only read/write its own repository and the 613 + system directories it needs to run. 614 + - **UID isolation** runs each subprocess as a virtual UID assigned 615 + to the repository owner, so that repositories belonging to 616 + different owners are isolated from each other at the OS level 617 + even if Landlock were somehow bypassed. 618 + 619 + Secure Mode requires: 620 + 621 + - Linux kernel >= 5.19 (Landlock V2). This is the minimum needed 622 + for `git push` to work, because receive-pack's quarantine 623 + migration uses cross-directory rename which requires the 624 + Landlock `REFER` access right (added in V2). Kernels 5.13-5.18 625 + support Landlock V1 and clones will work, but pushes will fail 626 + with cross-device link errors. On kernels without any Landlock 627 + support (< 5.13), the sandbox call is a no-op: UID isolation 628 + still applies but no filesystem restriction is enforced. 629 + - `CAP_SETUID`, `CAP_SETGID`, and `CAP_CHOWN` available to the 630 + knot process. The NixOS module grants these automatically; for 631 + manual setups see the `setcap` step below. 632 + 633 + ### NixOS 634 + 635 + Add `server.secureMode = true;` to your knot module configuration: 636 + 637 + ```nix 638 + services.tangled.knot = { 639 + server.secureMode = true; 640 + # ... other options 641 + }; 642 + ``` 643 + 644 + The NixOS module handles everything else automatically: 645 + 646 + - Grants the required capabilities to the knot service via 647 + `AmbientCapabilities` in the systemd unit. 648 + - Installs a capability-bearing wrapper at 649 + `/run/wrappers/bin/knot` via `security.wrappers`, so that 650 + SSH-invoked git operations (pushes) also run under the correct 651 + UID without requiring the service to run as root. 652 + - Runs `knot migrate-isolation` at service start to chown 653 + existing repositories to their virtual UIDs. 654 + 655 + ### Manual setup 656 + 657 + **Step 1.** Grant the required capabilities to the knot binary. 658 + This allows the knot process to switch to virtual UIDs at runtime 659 + without running as root. You will need to repeat this step 660 + whenever the binary is updated. 661 + 662 + ``` 663 + sudo setcap cap_setuid,cap_setgid,cap_chown+eip /usr/local/bin/knot 664 + ``` 665 + 666 + **Step 2.** Run the migration tool to assign virtual UIDs to all 667 + existing repositories and set their filesystem permissions. This 668 + must be run as root: 669 + 670 + ``` 671 + sudo knot migrate-isolation \ 672 + --git-dir /home/git \ 673 + --db /home/git/knotserver.db \ 674 + --internal-api 127.0.0.1:5444 675 + ``` 676 + 677 + You can re-run this at any time with `--force` to reapply 678 + permissions (e.g. after a manual repair or after updating the 679 + binary). 680 + 681 + **Step 2a.** Ensure the home directory is traversable by 682 + non-group users. Git subprocesses run as virtual UIDs that are 683 + not in the git group, and they need to resolve 684 + `$HOME/.config/git/config` to load the global config: 685 + 686 + ``` 687 + sudo chmod o+x /home/git 688 + ``` 689 + 690 + This adds only the execute bit, not read -- the virtual UIDs can 691 + traverse to known paths but cannot list directory contents. 692 + 693 + **Step 3.** Enable Secure Mode in your environment file: 694 + 695 + ``` 696 + KNOT_SERVER_SECURE_MODE=true 697 + ``` 698 + 699 + Or pass it as a flag: 700 + 701 + ``` 702 + knot server --secure-mode 703 + ``` 704 + 705 + **Step 4.** Regenerate the `AuthorizedKeysCommand` with the 706 + `-secure-mode` flag. This causes `knot keys` to emit guard 707 + command lines that include `-secure-mode`, so SSH pushes also 708 + get UID isolation: 709 + 710 + ``` 711 + sudo tee /etc/ssh/sshd_config.d/authorized_keys_command.conf <<EOF 712 + Match User git 713 + AuthorizedKeysCommand /usr/local/bin/knot keys \ 714 + -o authorized-keys -secure-mode 715 + AuthorizedKeysCommandUser nobody 716 + EOF 717 + ``` 718 + 719 + Reload `sshd` after making this change. 720 + 721 + > **Note:** the server will refuse to start in Secure Mode if any 722 + > repositories have not yet been isolation-migrated. Re-run 723 + > `migrate-isolation` if you see this error. 724 + 606 725 ## Troubleshooting 607 726 608 727 If you run your own knot, you may run into some of these