Files
nix-config/hosts/12kingdoms-youko/homelab-services/gitea.nix
T
Ryan Yin 7dc4e7a39f feat(security): harden homelab SSH, services and audit trail
The homelab hosts accept root SSH with long-lived file keys, run several
exposed services with almost no systemd sandboxing, and keep no audit trail of
privileged actions. Close those three gaps, weighing each measure against its
cost instead of adopting a policy wholesale.

SSH
- Turn off KbdInteractiveAuthentication: nixpkgs defaults it to true and PAM
  then still accepts the Unix password despite PasswordAuthentication = false.
- authorizedKeysInHomedir = false: only keys declared in Nix take effect;
  youko keeps Gitea's forced-command file through `Match User gitea`.
- Pin aquamarine's passphrase-free remote-builder keys to
  `restrict,command="nix-daemon --stdio --store daemon"`, so builds run in the
  system daemon instead of an audited root login session.

systemd sandboxing
- Add the mylib.systemdHardening preset and apply it to the exposed, weakly
  sandboxed services: caddy 7.8 -> 1.5, vmalert 8.2 -> 1.4, rustfs 6.6 -> 1.4,
  whisper-asr 7.7 -> 1.6. An eval test pins the key restrictions.
- Rejected: a global all-keys policy (<=0.2 score gains on services nixpkgs
  already sandboxes) and per-service confinement (bind paths to maintain).

auditd on all four hosts, reusing nixpkgs' loader and unit
- Audit login sessions only: a first `-a never,task -F auid=unset` rule keeps
  services out of the per-syscall audit path. A compromised service is
  therefore unrecorded until it opens a session.
- Record root commands, credential and host-key access, writes to SSH, account,
  PAM, Nix-trust and audit configuration, module loads and session denials.
- Rejected: global denial logging, service-UID watches and
  --loginuid-immutable (the kernel already records loginuid changes).

Verified on all four hosts after deployment: zero failed units, k3s 6/6 Ready,
and the exposed services healthy.

Assisted-by: Claude Code (claude-opus-5-5)
2026-10-31 01:09:15 +08:00

104 lines
3.3 KiB
Nix

{ pkgs, ... }:
{
# Git over SSH reads Gitea's own ~/.ssh/authorized_keys (keys pinned to
# `gitea serv`), which modules/nixos/base/ssh.nix otherwise ignores.
services.openssh.extraConfig = ''
Match User gitea
AuthorizedKeysFile %h/.ssh/authorized_keys /etc/ssh/authorized_keys.d/%u
'';
# https://github.com/NixOS/nixpkgs/blob/nixos-26.05/nixos/modules/services/misc/gitea.nix
services.gitea = {
enable = true;
user = "gitea";
group = "gitea";
appName = "Ryan Yin's Gitea Service";
lfs.enable = true;
# Enable a timer that runs gitea dump to generate backup-files of the current gitea database and repositories.
dump = {
enable = false;
interval = "hourly";
file = "gitea-dump";
type = "tar.zst";
};
# Path to a file containing the SMTP password.
# mailerPasswordFile = "";
settings = {
server = {
SSH_PORT = 2222;
PROTOCOL = "http";
HTTP_PORT = 3301;
HTTP_ADDR = "127.0.0.1";
ROOT_URL = "https://git.writefor.fun/";
};
# one of "Trace", "Debug", "Info", "Warn", "Error", "Critical"
log.LEVEL = "Info";
# Marks session cookies as "secure" as a hint for browsers to only send them via HTTPS.
session.COOKIE_SECURE = true;
# NOTE: The first registered user will be the administrator,
# so this parameter should NOT be set before the first user registers!
service.DISABLE_REGISTRATION = true;
# https://docs.gitea.com/administration/config-cheat-sheet#security-security
security = {
LOGIN_REMEMBER_DAYS = 31;
PASSWORD_HASH_ALGO = "scrypt";
MIN_PASSWORD_LENGTH = 10;
};
# "cron.sync_external_users" = {
# RUN_AT_START = true;
# SCHEDULE = "@every 24h";
# UPDATE_EXISTING = true;
# };
mailer = {
ENABLED = true;
MAILER_TYPE = "sendmail";
FROM = "do-not-reply@writefor.fun";
SENDMAIL_PATH = "${pkgs.system-sendmail}/bin/sendmail";
};
other = {
SHOW_FOOTER_VERSION = false;
};
};
database = {
type = "sqlite3";
# create a local database automatically.
createDatabase = true;
};
};
# services.gitea-actions-runner.instances."default" = {
# enable = true;
# name = "default";
# labels = [
# # provide a debian base with nodejs for actions
# "debian-latest:docker://node:18-bullseye"
# # fake the ubuntu name, because node provides no ubuntu builds
# "ubuntu-latest:docker://node:18-bullseye"
# # provide native execution on the host
# "native:host"
# ];
# gitea = "http://git.writefor.fun";
# # Path to an environment file,
# # containing the TOKEN environment variable,
# # that holds a token to register at the configured Gitea instance.
# tokenFile = "xxx"; # use agenix for secrets.
# # Configuration for act_runner daemon.
# # For an example configuration, see:
# # https://gitea.com/gitea/act_runner/src/branch/main/internal/pkg/config/config.example.yaml
# settings = {};
# # List of packages, that are available to actions,
# # when the runner is configured with a host execution label.
# hostPackages = with pkgs; [
# bash
# coreutils
# curl
# gawk
# gitMinimal
# gnused
# nodejs
# wget
# ];
# };
}