Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nixos/github-runner: init at v2.277.1 #116775

Merged
merged 21 commits into from Apr 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
67da734
github-runner: init at 2.277.1
veehaitch Mar 18, 2021
a10f465
nixos/github-runner: initial version
veehaitch Mar 18, 2021
b4d9c31
nixos/github-runner: add warning if tokenFile in Nix store
veehaitch Mar 19, 2021
ac82082
github-runner: don't accept unexpected attrs
veehaitch Mar 19, 2021
fd1f0d2
github-runner: formatting nits
veehaitch Mar 19, 2021
a12d9d8
github-runner: add pre and post hooks to checkPhase
veehaitch Mar 19, 2021
320479b
nixos/github-runner: update ExecStartPre= comment
veehaitch Mar 20, 2021
9a51add
nixos/github-runner: adapt tokenFile option description
veehaitch Mar 20, 2021
08df6fa
nixos/github-runner: remove mkDefault for DynamicUser=
veehaitch Mar 20, 2021
91bf483
nixos/github-runner: create a parent for systemd dirs
veehaitch Mar 27, 2021
fcfa809
nixos/github-runner: use specifier to get abs runtime path
veehaitch Mar 27, 2021
9dcd2ec
nixos/github-runner: use hostname as default for option `name`
veehaitch Mar 28, 2021
193ac67
nixos/github-runner: use types.str for `name` option
veehaitch Mar 28, 2021
c4bd4d3
nixos/github-runner: pass dir paths explicitly for ExecStartPre=
veehaitch Mar 29, 2021
929aa7a
nixos/github-runner: update variable and script naming
veehaitch Mar 29, 2021
a37e84e
nixos/github-runner: let systemd choose the user/group
veehaitch Mar 29, 2021
6e222be
Revert "nixos/github-runner: use types.str for `name` option"
veehaitch Mar 29, 2021
095f377
nixos/github-runner: use types.path for `tokenFile` option
veehaitch Mar 29, 2021
64f4d4a
nixos/github-runner: escape options used as shell arguments
veehaitch Mar 29, 2021
a68dac1
nixos/github-runner: wait for network-online.target
veehaitch Apr 1, 2021
cd24f4f
github-runner: ignore additional online tests
veehaitch Apr 4, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Expand Up @@ -285,6 +285,7 @@
./services/continuous-integration/hail.nix
./services/continuous-integration/hercules-ci-agent/default.nix
./services/continuous-integration/hydra/default.nix
./services/continuous-integration/github-runner.nix
./services/continuous-integration/gitlab-runner.nix
./services/continuous-integration/gocd-agent/default.nix
./services/continuous-integration/gocd-server/default.nix
Expand Down
299 changes: 299 additions & 0 deletions nixos/modules/services/continuous-integration/github-runner.nix
@@ -0,0 +1,299 @@
{ config, pkgs, lib, ... }:
with lib;
let
cfg = config.services.github-runner;
svcName = "github-runner";
systemdDir = "${svcName}/${cfg.name}";
# %t: Runtime directory root (usually /run); see systemd.unit(5)
runtimeDir = "%t/${systemdDir}";
# %S: State directory root (usually /var/lib); see systemd.unit(5)
stateDir = "%S/${systemdDir}";
# %L: Log directory root (usually /var/log); see systemd.unit(5)
logsDir = "%L/${systemdDir}";
in
{
options.services.github-runner = {
enable = mkOption {
default = false;
example = true;
description = ''
Whether to enable GitHub Actions runner.

Note: GitHub recommends using self-hosted runners with private repositories only. Learn more here:
<link xlink:href="https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners"
>About self-hosted runners</link>.
'';
type = lib.types.bool;
};

url = mkOption {
type = types.str;
description = ''
Repository to add the runner to.

Changing this option triggers a new runner registration.
'';
example = "https://github.com/nixos/nixpkgs";
};

tokenFile = mkOption {
type = types.path;
description = ''
The full path to a file which contains the runner registration token.
The file should contain exactly one line with the token without any newline.
The token can be used to re-register a runner of the same name but is time-limited.

Changing this option or the file's content triggers a new runner registration.
'';
example = "/run/secrets/github-runner/nixos.token";
};

name = mkOption {
# Same pattern as for `networking.hostName`
type = types.strMatching "^$|^[[:alnum:]]([[:alnum:]_-]{0,61}[[:alnum:]])?$";
description = ''
Name of the runner to configure. Defaults to the hostname.

Changing this option triggers a new runner registration.
'';
example = "nixos";
default = config.networking.hostName;
};

runnerGroup = mkOption {
type = types.nullOr types.str;
description = ''
Name of the runner group to add this runner to (defaults to the default runner group).

Changing this option triggers a new runner registration.
'';
default = null;
};

extraLabels = mkOption {
type = types.listOf types.str;
description = ''
Extra labels in addition to the default (<literal>["self-hosted", "Linux", "X64"]</literal>).

Changing this option triggers a new runner registration.
'';
example = literalExample ''[ "nixos" ]'';
default = [ ];
};

replace = mkOption {
type = types.bool;
description = ''
Replace any existing runner with the same name.

Without this flag, registering a new runner with the same name fails.
'';
default = false;
};

extraPackages = mkOption {
type = types.listOf types.package;
description = ''
Extra packages to add to <literal>PATH</literal> of the service to make them available to workflows.
'';
default = [ ];
};
};

config = mkIf cfg.enable {
warnings = optionals (isStorePath cfg.tokenFile) [
''
`services.github-runner.tokenFile` points to the Nix store and, therefore, is world-readable.
Consider using a path outside of the Nix store to keep the token private.
''
];

systemd.services.${svcName} = {
description = "GitHub Actions runner";

wantedBy = [ "multi-user.target" ];
wants = [ "network-online.target" ];
after = [ "network.target" "network-online.target" ];

environment = {
HOME = runtimeDir;
RUNNER_ROOT = runtimeDir;
};

path = (with pkgs; [
bash
coreutils
git
gnutar
gzip
]) ++ [
config.nix.package
] ++ cfg.extraPackages;

serviceConfig = rec {
ExecStart = "${pkgs.github-runner}/bin/runsvc.sh";

# Does the following, sequentially:
# - Copy the current and the previous `tokenFile` to the $RUNTIME_DIRECTORY
# and make it accessible to the service user to allow for a content
# comparison.
# - If the module configuration or the token has changed, clear the state directory.
# - Configure the runner.
# - Copy the configured `tokenFile` to the $STATE_DIRECTORY and make it
# inaccessible to the service user.
# - Set up the directory structure by creating the necessary symlinks.
ExecStartPre =
let
# Wrapper script which expects the full path of the state, runtime and logs
# directory as arguments. Overrides the respective systemd variables to provide
# unambiguous directory names. This becomes relevant, for example, if the
# caller overrides any of the StateDirectory=, RuntimeDirectory= or LogDirectory=
# to contain more than one directory. This causes systemd to set the respective
# environment variables with the path of all of the given directories, separated
# by a colon.
writeScript = name: lines: pkgs.writeShellScript "${svcName}-${name}.sh" ''
set -euo pipefail

STATE_DIRECTORY="$1"
RUNTIME_DIRECTORY="$2"
LOGS_DIRECTORY="$3"

${lines}
'';
currentConfigPath = "$STATE_DIRECTORY/.nixos-current-config.json";
runnerRegistrationConfig = getAttrs [ "name" "tokenFile" "url" "runnerGroup" "extraLabels" ] cfg;
newConfigPath = builtins.toFile "${svcName}-config.json" (builtins.toJSON runnerRegistrationConfig);
currentConfigTokenFilename = ".current-token";
newConfigTokenFilename = ".new-token";
runnerCredFiles = [
".credentials"
".credentials_rsaparams"
".runner"
];
ownConfigTokens = writeScript "own-config-tokens" ''
# Copy current and new token file to runtime dir and make it accessible to the service user
cp ${escapeShellArg cfg.tokenFile} "$RUNTIME_DIRECTORY/${newConfigTokenFilename}"
chmod 600 "$RUNTIME_DIRECTORY/${newConfigTokenFilename}"
chown "$USER" "$RUNTIME_DIRECTORY/${newConfigTokenFilename}"

if [[ -e "$STATE_DIRECTORY/${currentConfigTokenFilename}" ]]; then
cp "$STATE_DIRECTORY/${currentConfigTokenFilename}" "$RUNTIME_DIRECTORY/${currentConfigTokenFilename}"
chmod 600 "$RUNTIME_DIRECTORY/${currentConfigTokenFilename}"
chown "$USER" "$RUNTIME_DIRECTORY/${currentConfigTokenFilename}"
fi
'';
disownConfigTokens = writeScript "disown-config-tokens" ''
# Make the token inaccessible to the runner service user
chmod 600 "$STATE_DIRECTORY/${currentConfigTokenFilename}"
chown root:root "$STATE_DIRECTORY/${currentConfigTokenFilename}"
'';
unconfigureRunner = writeScript "unconfigure" ''
differs=
# Set `differs = 1` if current and new runner config differ or if `currentConfigPath` does not exist
${pkgs.diffutils}/bin/diff -q '${newConfigPath}' "${currentConfigPath}" >/dev/null 2>&1 || differs=1
# Also trigger a registration if the token content changed
${pkgs.diffutils}/bin/diff -q \
"$RUNTIME_DIRECTORY"/{${currentConfigTokenFilename},${newConfigTokenFilename}} \
>/dev/null 2>&1 || differs=1

if [[ -n "$differs" ]]; then
echo "Config has changed, removing old runner state."
echo "The old runner will still appear in the GitHub Actions UI." \
"You have to remove it manually."
find "$STATE_DIRECTORY/" -mindepth 1 -delete
fi
'';
configureRunner = writeScript "configure" ''
empty=$(ls -A "$STATE_DIRECTORY")
if [[ -z "$empty" ]]; then
echo "Configuring GitHub Actions Runner"
token=$(< "$RUNTIME_DIRECTORY"/${newConfigTokenFilename})
RUNNER_ROOT="$STATE_DIRECTORY" ${pkgs.github-runner}/bin/config.sh \
--unattended \
--work "$RUNTIME_DIRECTORY" \
--url ${escapeShellArg cfg.url} \
--token "$token" \
--labels ${escapeShellArg (concatStringsSep "," cfg.extraLabels)} \
--name ${escapeShellArg cfg.name} \
${optionalString cfg.replace "--replace"} \
${optionalString (cfg.runnerGroup != null) "--runnergroup ${escapeShellArg cfg.runnerGroup}"}

# Move the automatically created _diag dir to the logs dir
mkdir -p "$STATE_DIRECTORY/_diag"
cp -r "$STATE_DIRECTORY/_diag/." "$LOGS_DIRECTORY/"
rm -rf "$STATE_DIRECTORY/_diag/"

# Cleanup token from config
rm -f "$RUNTIME_DIRECTORY"/${currentConfigTokenFilename}
mv "$RUNTIME_DIRECTORY"/${newConfigTokenFilename} "$STATE_DIRECTORY/${currentConfigTokenFilename}"

# Symlink to new config
ln -s '${newConfigPath}' "${currentConfigPath}"
fi
'';
setupRuntimeDir = writeScript "setup-runtime-dirs" ''
# Link _diag dir
ln -s "$LOGS_DIRECTORY" "$RUNTIME_DIRECTORY/_diag"

# Link the runner credentials to the runtime dir
ln -s "$STATE_DIRECTORY"/{${lib.concatStringsSep "," runnerCredFiles}} "$RUNTIME_DIRECTORY/"
'';
in
map (x: "${x} ${escapeShellArgs [ stateDir runtimeDir logsDir ]}") [
"+${ownConfigTokens}" # runs as root
unconfigureRunner
configureRunner
"+${disownConfigTokens}" # runs as root
setupRuntimeDir
];

# Contains _diag
LogsDirectory = [ systemdDir ];
# Default RUNNER_ROOT which contains ephemeral Runner data
RuntimeDirectory = [ systemdDir ];
# Home of persistent runner data, e.g., credentials
StateDirectory = [ systemdDir ];
StateDirectoryMode = "0700";
WorkingDirectory = runtimeDir;

# By default, use a dynamically allocated user
DynamicUser = true;

KillMode = "process";
KillSignal = "SIGTERM";

# Hardening (may overlap with DynamicUser=)
# The following options are only for optimizing:
# systemd-analyze security github-runner
AmbientCapabilities = "";
CapabilityBoundingSet = "";
# ProtectClock= adds DeviceAllow=char-rtc r
DeviceAllow = "";
LockPersonality = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateMounts = true;
PrivateTmp = true;
PrivateUsers = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectSystem = "strict";
RemoveIPC = true;
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
UMask = "0066";

# Needs network access
PrivateNetwork = false;
# Cannot be true due to Node
MemoryDenyWriteExecute = false;
};
};
};
}