nixpkgs/nixos/modules/services/web-apps/gerrit.nix
Felix Singer 3d30811d4e nixos/gerrit: Apply initial hardening using the systemd unit
These options are a good start for sandboxing the service. It's planned
to set `ProtectSystem` to `strict` instead of `full`, but that requires
specific directories to be configured as writable. It's also planned to
filter system calls. However, that requires more testing but it
shouldn't prevent us from applying these options for now and add others
later.

Signed-off-by: Felix Singer <felixsinger@posteo.net>
2024-10-10 07:12:28 +02:00

254 lines
6.9 KiB
Nix

{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.gerrit;
# NixOS option type for git-like configs
gitIniType = with types;
let
primitiveType = either str (either bool int);
multipleType = either primitiveType (listOf primitiveType);
sectionType = lazyAttrsOf multipleType;
supersectionType = lazyAttrsOf (either multipleType sectionType);
in lazyAttrsOf supersectionType;
gerritConfig = pkgs.writeText "gerrit.conf" (
lib.generators.toGitINI cfg.settings
);
replicationConfig = pkgs.writeText "replication.conf" (
lib.generators.toGitINI cfg.replicationSettings
);
# Wrap the gerrit java with all the java options so it can be called
# like a normal CLI app
gerrit-cli = pkgs.writeShellScriptBin "gerrit" ''
set -euo pipefail
jvmOpts=(
${lib.escapeShellArgs cfg.jvmOpts}
-Xmx${cfg.jvmHeapLimit}
)
exec ${cfg.jvmPackage}/bin/java \
"''${jvmOpts[@]}" \
-jar ${cfg.package}/webapps/${cfg.package.name}.war \
"$@"
'';
gerrit-plugins = pkgs.runCommand
"gerrit-plugins"
{
buildInputs = [ gerrit-cli ];
}
''
shopt -s nullglob
mkdir $out
for name in ${toString cfg.builtinPlugins}; do
echo "Installing builtin plugin $name.jar"
gerrit cat plugins/$name.jar > $out/$name.jar
done
for file in ${toString cfg.plugins}; do
name=$(echo "$file" | cut -d - -f 2-)
echo "Installing plugin $name"
ln -sf "$file" $out/$name
done
'';
in
{
options = {
services.gerrit = {
enable = mkEnableOption "Gerrit service";
package = mkPackageOption pkgs "gerrit" { };
jvmPackage = mkPackageOption pkgs "jre_headless" { };
jvmOpts = mkOption {
type = types.listOf types.str;
default = [
"-Dflogger.backend_factory=com.google.common.flogger.backend.log4j.Log4jBackendFactory#getInstance"
"-Dflogger.logging_context=com.google.gerrit.server.logging.LoggingContext#getInstance"
];
description = "A list of JVM options to start gerrit with.";
};
jvmHeapLimit = mkOption {
type = types.str;
default = "1024m";
description = ''
How much memory to allocate to the JVM heap
'';
};
listenAddress = mkOption {
type = types.str;
default = "[::]:8080";
description = ''
`hostname:port` to listen for HTTP traffic.
This is bound using the systemd socket activation.
'';
};
settings = mkOption {
type = gitIniType;
default = {};
description = ''
Gerrit configuration. This will be generated to the
`etc/gerrit.config` file.
'';
};
replicationSettings = mkOption {
type = gitIniType;
default = {};
description = ''
Replication configuration. This will be generated to the
`etc/replication.config` file.
'';
};
plugins = mkOption {
type = types.listOf types.package;
default = [];
description = ''
List of plugins to add to Gerrit. Each derivation is a jar file
itself where the name of the derivation is the name of plugin.
'';
};
builtinPlugins = mkOption {
type = types.listOf (types.enum cfg.package.passthru.plugins);
default = [];
description = ''
List of builtins plugins to install. Those are shipped in the
`gerrit.war` file.
'';
};
serverId = mkOption {
type = types.str;
description = ''
Set a UUID that uniquely identifies the server.
This can be generated with
`nix-shell -p util-linux --run uuidgen`.
'';
};
};
};
config = mkIf cfg.enable {
assertions = [
{
assertion = cfg.replicationSettings != {} -> elem "replication" cfg.builtinPlugins;
message = "Gerrit replicationSettings require enabling the replication plugin";
}
];
services.gerrit.settings = {
cache.directory = "/var/cache/gerrit";
container.heapLimit = cfg.jvmHeapLimit;
gerrit.basePath = lib.mkDefault "git";
gerrit.serverId = cfg.serverId;
httpd.inheritChannel = "true";
httpd.listenUrl = lib.mkDefault "http://${cfg.listenAddress}";
index.type = lib.mkDefault "lucene";
};
# Add the gerrit CLI to the system to run `gerrit init` and friends.
environment.systemPackages = [ gerrit-cli ];
systemd.sockets.gerrit = {
unitConfig.Description = "Gerrit HTTP socket";
wantedBy = [ "sockets.target" ];
listenStreams = [ cfg.listenAddress ];
};
systemd.services.gerrit = {
description = "Gerrit";
wantedBy = [ "multi-user.target" ];
requires = [ "gerrit.socket" ];
after = [ "gerrit.socket" "network.target" ];
path = [
gerrit-cli
pkgs.bash
pkgs.coreutils
pkgs.git
pkgs.openssh
];
environment = {
GERRIT_HOME = "%S/gerrit";
GERRIT_TMP = "%T";
HOME = "%S/gerrit";
XDG_CONFIG_HOME = "%S/gerrit/.config";
};
preStart = ''
set -euo pipefail
# bootstrap if nothing exists
if [[ ! -d git ]]; then
gerrit init --batch --no-auto-start
fi
# install gerrit.war for the plugin manager
rm -rf bin
mkdir bin
ln -sfv ${cfg.package}/webapps/${cfg.package.name}.war bin/gerrit.war
# copy the config, keep it mutable because Gerrit
ln -sfv ${gerritConfig} etc/gerrit.config
ln -sfv ${replicationConfig} etc/replication.config
# install the plugins
rm -rf plugins
ln -sv ${gerrit-plugins} plugins
''
;
serviceConfig = {
CacheDirectory = "gerrit";
DynamicUser = true;
ExecStart = "${gerrit-cli}/bin/gerrit daemon --console-log";
LimitNOFILE = 4096;
StandardInput = "socket";
StandardOutput = "journal";
StateDirectory = "gerrit";
WorkingDirectory = "%S/gerrit";
AmbientCapabilities = "";
CapabilityBoundingSet = "";
LockPersonality = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateTmp = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "noaccess";
ProtectSystem = "full";
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
UMask = 027;
};
};
};
meta.maintainers = with lib.maintainers; [ edef zimbatm ];
# uses attributes of the linked package
meta.buildDocsInSandbox = false;
}