Monorepo for Tangled tangled.org
4

Configure Feed

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

nix: add secureMode module support

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

author
Anirudh Oppiliappan
date (Jun 12, 2026, 12:21 PM +0300) commit 6c2f9e9a parent 4c0f6886 change-id wxnrvonr
+87 -39
+87 -39
nix/modules/knot.nix
··· 178 178 description = "Enable development mode (disables signature verification)"; 179 179 }; 180 180 181 + secureMode = mkOption { 182 + type = types.bool; 183 + default = false; 184 + description = "Isolate git subprocesses with Landlock (requires kernel >= 5.13)"; 185 + }; 186 + 181 187 maxResponseKB = mkOption { 182 188 type = types.int; 183 189 default = 5120; ··· 222 228 -output authorized-keys \ 223 229 -internal-api "http://${cfg.server.internalListenAddr}" \ 224 230 -git-dir "${cfg.repo.scanPath}" \ 225 - -log-path /tmp/knotguard.log 231 + -log-path /tmp/knotguard.log \ 232 + ${optionalString cfg.server.secureMode "-secure-mode -guard-path /run/wrappers/bin/knot"} 226 233 ''; 227 234 }; 228 235 236 + # In secure mode, the guard subcommand (run via sshd's forced command as 237 + # the git user) needs CAP_SETUID to drop to the virtual UID before exec'ing 238 + # git. security.wrappers installs a setcap'd wrapper around the binary. 239 + security.wrappers = mkIf cfg.server.secureMode { 240 + knot = { 241 + source = "${cfg.package}/bin/knot"; 242 + owner = "root"; 243 + group = cfg.gitUser; 244 + permissions = "u+rx,g+x"; 245 + capabilities = "cap_setuid,cap_setgid,cap_chown+eip"; 246 + }; 247 + }; 248 + 229 249 systemd.services.knot = { 230 250 description = "knot service"; 231 251 after = ["network-online.target" "sshd.service"]; ··· 243 263 ''; 244 264 in '' 245 265 mkdir -p "${cfg.repo.scanPath}" 246 - chown -R ${cfg.gitUser}:${cfg.gitUser} "${cfg.repo.scanPath}" 247 - 248 266 mkdir -p "${cfg.stateDir}/.config/git" 249 267 cat > "${cfg.stateDir}/.config/git/config" << EOF 250 268 [user] ··· 255 273 [uploadpack] 256 274 allowFilter = true 257 275 allowReachableSHA1InWant = true 276 + [safe] 277 + directory = * 258 278 EOF 259 279 ${setMotd} 260 - chown -R ${cfg.gitUser}:${cfg.gitUser} "${cfg.stateDir}" 280 + chown ${cfg.gitUser}:${cfg.gitUser} "${cfg.repo.scanPath}" 281 + chown -R ${cfg.gitUser}:${cfg.gitUser} "${cfg.stateDir}/.config" 282 + ${optionalString cfg.server.secureMode '' 283 + # secure mode runs git subprocesses as virtual UIDs that are not in 284 + # the git group. they need execute (traverse) permission on the 285 + # state dir so they can resolve $HOME/.config/git/config; read 286 + # permission is intentionally withheld so they can't list other 287 + # repos by name. 288 + chmod o+x "${cfg.stateDir}" 289 + ''} 290 + ${optionalString (cfg.motdFile != null || cfg.motd != null) "chown ${cfg.gitUser}:${cfg.gitUser} ${cfg.stateDir}/motd"} 291 + ${optionalString cfg.server.secureMode '' 292 + ${cfg.package}/bin/knot migrate-isolation \ 293 + --force \ 294 + --git-dir "${cfg.repo.scanPath}" \ 295 + --db "${cfg.server.dbPath}" \ 296 + --internal-api "${cfg.server.internalListenAddr}" 297 + chown ${cfg.gitUser}:${cfg.gitUser} "${cfg.server.dbPath}" 298 + ''} 261 299 ''; 262 300 263 - serviceConfig = { 264 - User = cfg.gitUser; 265 - PermissionsStartOnly = true; 266 - WorkingDirectory = cfg.stateDir; 267 - Environment = [ 268 - "PATH=${lib.makeBinPath [pkgs.bash pkgs.git pkgs.coreutils]}:/run/current-system/sw/bin" 269 - "KNOT_REPO_SCAN_PATH=${cfg.repo.scanPath}" 270 - "KNOT_REPO_README=${concatStringsSep "," cfg.repo.readme}" 271 - "KNOT_REPO_MAIN_BRANCH=${cfg.repo.mainBranch}" 272 - "KNOT_GIT_USER_NAME=${cfg.git.userName}" 273 - "KNOT_GIT_USER_EMAIL=${cfg.git.userEmail}" 274 - "APPVIEW_ENDPOINT=${cfg.appviewEndpoint}" 275 - "KNOT_SERVER_INTERNAL_LISTEN_ADDR=${cfg.server.internalListenAddr}" 276 - "KNOT_SERVER_LISTEN_ADDR=${cfg.server.listenAddr}" 277 - "KNOT_SERVER_DB_PATH=${cfg.server.dbPath}" 278 - "KNOT_SERVER_HOSTNAME=${cfg.server.hostname}" 279 - "KNOT_SERVER_PLC_URL=${cfg.server.plcUrl}" 280 - "KNOT_SERVER_JETSTREAM_ENDPOINT=${cfg.server.jetstreamEndpoint}" 281 - "KNOT_SERVER_OWNER=${cfg.server.owner}" 282 - "KNOT_MIRRORS=${concatStringsSep "," cfg.knotmirrors}" 283 - "KNOT_SERVER_LOG_DIDS=${ 284 - if cfg.server.logDids 285 - then "true" 286 - else "false" 287 - }" 288 - "KNOT_SERVER_DEV=${ 289 - if cfg.server.dev 290 - then "true" 291 - else "false" 292 - }" 293 - "KNOT_SERVER_MAX_RESPONSE_KB=${toString cfg.server.maxResponseKB}" 294 - ]; 295 - ExecStart = "${cfg.package}/bin/knot server"; 296 - Restart = "always"; 297 - }; 301 + serviceConfig = 302 + { 303 + User = cfg.gitUser; 304 + PermissionsStartOnly = true; 305 + WorkingDirectory = cfg.stateDir; 306 + Environment = [ 307 + "PATH=${lib.makeBinPath [pkgs.bash pkgs.git pkgs.coreutils]}:/run/current-system/sw/bin" 308 + "KNOT_REPO_SCAN_PATH=${cfg.repo.scanPath}" 309 + "KNOT_REPO_README=${concatStringsSep "," cfg.repo.readme}" 310 + "KNOT_REPO_MAIN_BRANCH=${cfg.repo.mainBranch}" 311 + "KNOT_GIT_USER_NAME=${cfg.git.userName}" 312 + "KNOT_GIT_USER_EMAIL=${cfg.git.userEmail}" 313 + "APPVIEW_ENDPOINT=${cfg.appviewEndpoint}" 314 + "KNOT_SERVER_INTERNAL_LISTEN_ADDR=${cfg.server.internalListenAddr}" 315 + "KNOT_SERVER_LISTEN_ADDR=${cfg.server.listenAddr}" 316 + "KNOT_SERVER_DB_PATH=${cfg.server.dbPath}" 317 + "KNOT_SERVER_HOSTNAME=${cfg.server.hostname}" 318 + "KNOT_SERVER_PLC_URL=${cfg.server.plcUrl}" 319 + "KNOT_SERVER_JETSTREAM_ENDPOINT=${cfg.server.jetstreamEndpoint}" 320 + "KNOT_SERVER_OWNER=${cfg.server.owner}" 321 + "KNOT_MIRRORS=${concatStringsSep "," cfg.knotmirrors}" 322 + "KNOT_SERVER_LOG_DIDS=${ 323 + if cfg.server.logDids 324 + then "true" 325 + else "false" 326 + }" 327 + "KNOT_SERVER_DEV=${ 328 + if cfg.server.dev 329 + then "true" 330 + else "false" 331 + }" 332 + "KNOT_SERVER_MAX_RESPONSE_KB=${toString cfg.server.maxResponseKB}" 333 + "KNOT_SERVER_SECURE_MODE=${ 334 + if cfg.server.secureMode 335 + then "true" 336 + else "false" 337 + }" 338 + ]; 339 + ExecStart = "${cfg.package}/bin/knot server"; 340 + Restart = "always"; 341 + } 342 + // optionalAttrs cfg.server.secureMode { 343 + AmbientCapabilities = ["CAP_CHOWN" "CAP_SETUID" "CAP_SETGID"]; 344 + CapabilityBoundingSet = ["CAP_CHOWN" "CAP_SETUID" "CAP_SETGID"]; 345 + }; 298 346 }; 299 347 300 348 networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [22];