Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
0da36a23e0 |
15 changed files with 511 additions and 0 deletions
|
@ -16,6 +16,7 @@
|
|||
in {
|
||||
nixosModules.default = { imports = [ ./options.nix ]; };
|
||||
nixosModules.backend-on-machine = { imports = [ ./backends/on-machine.nix ]; };
|
||||
nixosModules.generators = { imports = [ ./generators ]; };
|
||||
# TODO fix tests
|
||||
checks = forAllSystems (system: let
|
||||
tests = {
|
||||
|
|
11
generators/default.nix
Normal file
11
generators/default.nix
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
imports = [
|
||||
./disk-id
|
||||
./garage
|
||||
./machine-id
|
||||
./root-password
|
||||
./sshd
|
||||
./state-version
|
||||
./user-password
|
||||
];
|
||||
}
|
30
generators/disk-id/default.nix
Normal file
30
generators/disk-id/default.nix
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.vars.disk-id;
|
||||
in
|
||||
{
|
||||
options.vars.disk-id.enable = lib.mkEnableOption "Generates a uuid for use in disk device naming";
|
||||
config = lib.mkIf cfg.enable {
|
||||
vars.generators.disk-id = {
|
||||
files.diskId.secret = false;
|
||||
runtimeInputs = [
|
||||
pkgs.coreutils
|
||||
pkgs.bash
|
||||
];
|
||||
script = ''
|
||||
uuid=$(bash ${./uuid4.sh})
|
||||
|
||||
# Remove the hyphens from the UUID
|
||||
uuid_no_hyphens=$(echo -n "$uuid" | tr -d '-')
|
||||
|
||||
echo -n "$uuid_no_hyphens" > "$out/diskId"
|
||||
'';
|
||||
};
|
||||
disko.devices.disk."main".name = "main-" + config.vars.generators.disk-id.files.diskId.value;
|
||||
};
|
||||
}
|
20
generators/disk-id/uuid4.sh
Normal file
20
generators/disk-id/uuid4.sh
Normal file
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Read 16 bytes from /dev/urandom
|
||||
uuid=$(dd if=/dev/urandom bs=1 count=16 2>/dev/null | od -An -tx1 | tr -d ' \n')
|
||||
|
||||
# Break the UUID into pieces and apply the required modifications
|
||||
byte6=${uuid:12:2}
|
||||
byte8=${uuid:16:2}
|
||||
|
||||
# Construct the correct version and variant
|
||||
hex_byte6=$(printf "%x" $((0x$byte6 & 0x0F | 0x40)))
|
||||
hex_byte8=$(printf "%x" $((0x$byte8 & 0x3F | 0x80)))
|
||||
|
||||
# Rebuild the UUID with the correct fields
|
||||
uuid_v4="${uuid:0:12}${hex_byte6}${uuid:14:2}${hex_byte8}${uuid:18:14}"
|
||||
|
||||
# Format the UUID correctly 8-4-4-4-12
|
||||
uuid_formatted="${uuid_v4:0:8}-${uuid_v4:8:4}-${uuid_v4:12:4}-${uuid_v4:16:4}-${uuid_v4:20:12}"
|
||||
|
||||
echo -n "$uuid_formatted"
|
59
generators/garage/default.nix
Normal file
59
generators/garage/default.nix
Normal file
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.vars.garage;
|
||||
in
|
||||
{
|
||||
options.vars.garage.enable = lib.mkEnableOption ''
|
||||
S3-compatible object store for small self-hosted geo-distributed deployments.
|
||||
|
||||
This module generates garage-specific keys automatically.
|
||||
|
||||
Options: [NixosModuleOptions](https://search.nixos.org/options?channel=unstable&size=50&sort=relevance&type=packages&query=garage)
|
||||
Documentation: https://garagehq.deuxfleurs.fr/
|
||||
'';
|
||||
config = lib.mkIf cfg.enable {
|
||||
systemd.services.garage.serviceConfig = {
|
||||
LoadCredential = [
|
||||
"rpc_secret_path:${config.vars.generators.garage-shared.files.rpc_secret.path}"
|
||||
"admin_token_path:${config.vars.generators.garage.files.admin_token.path}"
|
||||
"metrics_token_path:${config.vars.generators.garage.files.metrics_token.path}"
|
||||
];
|
||||
Environment = [
|
||||
"GARAGE_ALLOW_WORLD_READABLE_SECRETS=true"
|
||||
"GARAGE_RPC_SECRET_FILE=%d/rpc_secret_path"
|
||||
"GARAGE_ADMIN_TOKEN_FILE=%d/admin_token_path"
|
||||
"GARAGE_METRICS_TOKEN_FILE=%d/metrics_token_path"
|
||||
];
|
||||
};
|
||||
|
||||
vars.generators.garage = {
|
||||
files.admin_token = { };
|
||||
files.metrics_token = { };
|
||||
runtimeInputs = [
|
||||
pkgs.coreutils
|
||||
pkgs.openssl
|
||||
];
|
||||
script = ''
|
||||
openssl rand -base64 -out "$out"/admin_token 32
|
||||
openssl rand -base64 -out "$out"/metrics_token 32
|
||||
'';
|
||||
};
|
||||
|
||||
vars.generators.garage-shared = {
|
||||
share = true;
|
||||
files.rpc_secret = { };
|
||||
runtimeInputs = [
|
||||
pkgs.coreutils
|
||||
pkgs.openssl
|
||||
];
|
||||
script = ''
|
||||
openssl rand -hex -out "$out"/rpc_secret 32
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
46
generators/machine-id/default.nix
Normal file
46
generators/machine-id/default.nix
Normal file
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
var = config.vars.generators.machine-id.files.machineId or { };
|
||||
cfg = config.vars.machine-id;
|
||||
in
|
||||
{
|
||||
options.vars.machine-id.enable = lib.mkEnableOption "Sets the /etc/machine-id and exposes it as a nix option";
|
||||
config = lib.mkIf cfg.enable (lib.mkMerge [
|
||||
(lib.mkIf ((var.value or null) != null) {
|
||||
assertions = [
|
||||
{
|
||||
assertion = lib.stringLength var.value == 32;
|
||||
message = "machineId must be exactly 32 characters long.";
|
||||
}
|
||||
];
|
||||
boot.kernelParams = [
|
||||
''systemd.machine_id=${var.value}''
|
||||
];
|
||||
environment.etc."machine-id" = {
|
||||
text = var.value;
|
||||
};
|
||||
})
|
||||
{
|
||||
vars.generators.machine-id = {
|
||||
files.machineId.secret = false;
|
||||
runtimeInputs = [
|
||||
pkgs.coreutils
|
||||
pkgs.bash
|
||||
];
|
||||
script = ''
|
||||
uuid=$(bash ${./uuid4.sh})
|
||||
|
||||
# Remove the hyphens from the UUID
|
||||
uuid_no_hyphens=$(echo -n "$uuid" | tr -d '-')
|
||||
|
||||
echo -n "$uuid_no_hyphens" > "$out/machineId"
|
||||
'';
|
||||
};
|
||||
}
|
||||
]);
|
||||
}
|
20
generators/machine-id/uuid4.sh
Normal file
20
generators/machine-id/uuid4.sh
Normal file
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Read 16 bytes from /dev/urandom
|
||||
uuid=$(dd if=/dev/urandom bs=1 count=16 2>/dev/null | od -An -tx1 | tr -d ' \n')
|
||||
|
||||
# Break the UUID into pieces and apply the required modifications
|
||||
byte6=${uuid:12:2}
|
||||
byte8=${uuid:16:2}
|
||||
|
||||
# Construct the correct version and variant
|
||||
hex_byte6=$(printf "%x" $((0x$byte6 & 0x0F | 0x40)))
|
||||
hex_byte8=$(printf "%x" $((0x$byte8 & 0x3F | 0x80)))
|
||||
|
||||
# Rebuild the UUID with the correct fields
|
||||
uuid_v4="${uuid:0:12}${hex_byte6}${uuid:14:2}${hex_byte8}${uuid:18:14}"
|
||||
|
||||
# Format the UUID correctly 8-4-4-4-12
|
||||
uuid_formatted="${uuid_v4:0:8}-${uuid_v4:8:4}-${uuid_v4:12:4}-${uuid_v4:16:4}-${uuid_v4:20:12}"
|
||||
|
||||
echo -n "$uuid_formatted"
|
9
generators/options.nix
Normal file
9
generators/options.nix
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
{
|
||||
options.vars = {
|
||||
unattended = lib.mkEnableOption "Whether to default to generating values unattended, rather than prompting for desired values.";
|
||||
};
|
||||
}
|
60
generators/root-password/default.nix
Normal file
60
generators/root-password/default.nix
Normal file
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.vars.root-password;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
../options.nix
|
||||
];
|
||||
options.vars.root-password = {
|
||||
enable = lib.mkEnableOption "Automatically generates and configures a password for the root user.";
|
||||
prompt = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = !config.vars.unattended;
|
||||
example = true;
|
||||
description = "Whether the user should be prompted.";
|
||||
};
|
||||
};
|
||||
config = lib.mkIf cfg.enable {
|
||||
users.mutableUsers = false;
|
||||
users.users.root.hashedPasswordFile =
|
||||
config.vars.generators.root-password.files.password-hash.path;
|
||||
|
||||
vars.generators.root-password = {
|
||||
files.password-hash = {
|
||||
neededFor = "users";
|
||||
};
|
||||
files.password-hash.restartUnits = lib.optional (config.services.userborn.enable) "userborn.service";
|
||||
files.password = {
|
||||
deploy = false;
|
||||
};
|
||||
runtimeInputs = [
|
||||
pkgs.coreutils
|
||||
pkgs.mkpasswd
|
||||
pkgs.xkcdpass
|
||||
];
|
||||
prompts = lib.mkIf cfg.prompt {
|
||||
password = {
|
||||
type = "hidden";
|
||||
persist = true;
|
||||
description = "You can autogenerate a password, if you leave this prompt blank.";
|
||||
};
|
||||
};
|
||||
|
||||
script = ''
|
||||
prompt_value=${if cfg.prompt then ''$(cat "$prompts"/password)'' else ""}
|
||||
if [[ -n "''${prompt_value-}" ]]; then
|
||||
echo "$prompt_value" | tr -d "\n" > "$out"/password
|
||||
else
|
||||
xkcdpass --numwords 3 --delimiter - --count 1 | tr -d "\n" > "$out"/password
|
||||
fi
|
||||
mkpasswd -s -m sha-512 < "$out"/password | tr -d "\n" > "$out"/password-hash
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
6
generators/sshd/client.nix
Normal file
6
generators/sshd/client.nix
Normal file
|
@ -0,0 +1,6 @@
|
|||
{ ... }:
|
||||
{
|
||||
imports = [
|
||||
./shared.nix
|
||||
];
|
||||
}
|
6
generators/sshd/default.nix
Normal file
6
generators/sshd/default.nix
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
imports = [
|
||||
./client.nix
|
||||
./server.nix
|
||||
];
|
||||
}
|
103
generators/sshd/server.nix
Normal file
103
generators/sshd/server.nix
Normal file
|
@ -0,0 +1,103 @@
|
|||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
stringSet = list: builtins.attrNames (builtins.groupBy lib.id list);
|
||||
|
||||
domains = stringSet config.vars.sshd.certificate.searchDomains;
|
||||
|
||||
cfg = config.vars.sshd;
|
||||
|
||||
name = config.networking.hostName;
|
||||
in
|
||||
{
|
||||
imports = [ ../shared.nix ];
|
||||
options = {
|
||||
vars.sshd = {
|
||||
enable = lib.mkEnableOption "Set up the opensshd service, generating a host key for the machine.";
|
||||
hostKeys.rsa.enable = lib.mkEnableOption "Generate RSA host key";
|
||||
};
|
||||
};
|
||||
config = lib.mkIf cfg.enable {
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
settings.PasswordAuthentication = false;
|
||||
|
||||
settings.HostCertificate = lib.mkIf (
|
||||
cfg.certificate.searchDomains != [ ]
|
||||
) config.vars.generators.openssh-cert.files."ssh.id_ed25519-cert.pub".path;
|
||||
|
||||
hostKeys =
|
||||
[
|
||||
{
|
||||
path = config.vars.generators.openssh.files."ssh.id_ed25519".path;
|
||||
type = "ed25519";
|
||||
}
|
||||
]
|
||||
++ lib.optional cfg.hostKeys.rsa.enable {
|
||||
path = config.vars.generators.openssh-rsa.files."ssh.id_rsa".path;
|
||||
type = "rsa";
|
||||
};
|
||||
};
|
||||
|
||||
vars.generators.openssh = {
|
||||
files."ssh.id_ed25519" = { };
|
||||
files."ssh.id_ed25519.pub".secret = false;
|
||||
runtimeInputs = [
|
||||
pkgs.coreutils
|
||||
pkgs.openssh
|
||||
];
|
||||
script = ''
|
||||
ssh-keygen -t ed25519 -N "" -f "$out"/ssh.id_ed25519
|
||||
'';
|
||||
};
|
||||
|
||||
programs.ssh.knownHosts.sshd-self-ed25519 = {
|
||||
hostNames = [
|
||||
"localhost"
|
||||
config.networking.hostName
|
||||
] ++ (lib.optional (config.networking.domain != null) config.networking.fqdn);
|
||||
publicKey = config.vars.generators.openssh.files."ssh.id_ed25519.pub".value;
|
||||
};
|
||||
|
||||
vars.generators.openssh-rsa = lib.mkIf config.vars.sshd.hostKeys.rsa.enable {
|
||||
files."ssh.id_rsa" = { };
|
||||
files."ssh.id_rsa.pub".secret = false;
|
||||
runtimeInputs = [
|
||||
pkgs.coreutils
|
||||
pkgs.openssh
|
||||
];
|
||||
script = ''
|
||||
ssh-keygen -t rsa -b 4096 -N "" -f "$out"/ssh.id_rsa
|
||||
'';
|
||||
};
|
||||
|
||||
vars.generators.openssh-cert = lib.mkIf (cfg.certificate.searchDomains != [ ]) {
|
||||
files."ssh.id_ed25519-cert.pub".secret = false;
|
||||
dependencies = [
|
||||
"openssh"
|
||||
"openssh-ca"
|
||||
];
|
||||
validation = {
|
||||
inherit name;
|
||||
domains = lib.genAttrs config.vars.sshd.certificate.searchDomains lib.id;
|
||||
};
|
||||
runtimeInputs = [
|
||||
pkgs.openssh
|
||||
pkgs.jq
|
||||
];
|
||||
script = ''
|
||||
ssh-keygen \
|
||||
-s $in/openssh-ca/id_ed25519 \
|
||||
-I ${name} \
|
||||
-h \
|
||||
-n ${lib.concatMapStringsSep "," (d: "${name}.${d}") domains} \
|
||||
$in/openssh/ssh.id_ed25519.pub
|
||||
mv $in/openssh/ssh.id_ed25519-cert.pub "$out"/ssh.id_ed25519-cert.pub
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
44
generators/sshd/shared.nix
Normal file
44
generators/sshd/shared.nix
Normal file
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.vars.sshd.certificate;
|
||||
in
|
||||
{
|
||||
options.vars.sshd.certificate = {
|
||||
enable = lib.mkEnableOption "Add machines to the known hosts, enabling secure remote access to them over ssh.";
|
||||
searchDomains = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
example = [ "mydomain.com" ];
|
||||
description = "List of domains to include in the certificate. This option will prepend the machine name in front of each domain before adding it to the certificate.";
|
||||
};
|
||||
};
|
||||
config = lib.mkIf cfg.enable {
|
||||
vars.generators.openssh-ca =
|
||||
lib.mkIf (config.vars.sshd.certificate.searchDomains != [ ])
|
||||
{
|
||||
share = true;
|
||||
files.id_ed25519.deploy = false;
|
||||
files."id_ed25519.pub" = {
|
||||
deploy = false;
|
||||
secret = false;
|
||||
};
|
||||
runtimeInputs = [
|
||||
pkgs.openssh
|
||||
];
|
||||
script = ''
|
||||
ssh-keygen -t ed25519 -N "" -f "$out"/id_ed25519
|
||||
'';
|
||||
};
|
||||
|
||||
programs.ssh.knownHosts.ssh-ca = lib.mkIf (config.vars.sshd.certificate.searchDomains != [ ]) {
|
||||
certAuthority = true;
|
||||
extraHostNames = builtins.map (domain: "*.${domain}") config.vars.sshd.certificate.searchDomains;
|
||||
publicKey = config.vars.generators.openssh-ca.files."id_ed25519.pub".value;
|
||||
};
|
||||
};
|
||||
}
|
22
generators/state-version/default.nix
Normal file
22
generators/state-version/default.nix
Normal file
|
@ -0,0 +1,22 @@
|
|||
{ config, lib, ... }:
|
||||
let
|
||||
var = config.vars.generators.state-version.files.version or { };
|
||||
cfg = config.vars.state-version;
|
||||
in
|
||||
{
|
||||
options.vars.state-version.enable = lib.mkEnableOption "Automatically generate the state version of the nixos installation.";
|
||||
config = lib.mkIf cfg.enable {
|
||||
system.stateVersion = lib.mkDefault (lib.removeSuffix "\n" var.value);
|
||||
|
||||
vars.generators.state-version = {
|
||||
files.version = {
|
||||
secret = false;
|
||||
value = lib.mkDefault config.system.nixos.release;
|
||||
};
|
||||
runtimeInputs = [ ];
|
||||
script = ''
|
||||
echo -n ${config.system.stateVersion} > "$out"/version
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
74
generators/user-password/default.nix
Normal file
74
generators/user-password/default.nix
Normal file
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
pkgs,
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.vars.user-password;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
../options.nix
|
||||
];
|
||||
options.vars.user-password = {
|
||||
enable = lib.mkEnableOption ''
|
||||
Automatically generates and configures a password for a user.
|
||||
|
||||
This will set `mutableUsers` to `false`, meaning you can not manage user passwords through `passwd` anymore.
|
||||
'';
|
||||
users = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.submodule {
|
||||
options = {
|
||||
prompt = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = !config.vars.unattended;
|
||||
example = true;
|
||||
description = "Whether the user should be prompted.";
|
||||
};
|
||||
};
|
||||
});
|
||||
description = "The users for which to generate passwords.";
|
||||
example = "{ alice = { }; }";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
users.mutableUsers = false;
|
||||
users.users = lib.mapAttrs (userName: _: {
|
||||
hashedPasswordFile = config.vars.generators."user-password-${userName}".files.user-password-hash.path;
|
||||
isNormalUser = lib.mkDefault true;
|
||||
}) cfg.users;
|
||||
|
||||
vars.generators = lib.mapAttrs (userName: user: {
|
||||
"user-password-${userName}" = {
|
||||
files.user-password-hash.neededFor = "users";
|
||||
files.user-password-hash.restartUnits = lib.optional (config.services.userborn.enable) "userborn.service";
|
||||
|
||||
prompts = lib.mkIf user.prompt {
|
||||
"user-password-${userName}" = {
|
||||
type = "hidden";
|
||||
persist = true;
|
||||
description = "You can autogenerate a password, if you leave this prompt blank.";
|
||||
files.user-password.deploy = false;
|
||||
};
|
||||
};
|
||||
|
||||
runtimeInputs = [
|
||||
pkgs.coreutils
|
||||
pkgs.xkcdpass
|
||||
pkgs.mkpasswd
|
||||
];
|
||||
script = ''
|
||||
prompt_value=${if user.prompt then ''$(cat "$prompts"/user-password)'' else ""}
|
||||
if [[ -n "''${prompt_value-}" ]]; then
|
||||
echo "$prompt_value" | tr -d "\n" > "$out"/user-password
|
||||
else
|
||||
xkcdpass --numwords 3 --delimiter - --count 1 | tr -d "\n" > "$out"/user-password
|
||||
fi
|
||||
mkpasswd -s -m sha-512 < "$out"/user-password | tr -d "\n" > "$out"/user-password-hash
|
||||
'';
|
||||
};
|
||||
}) cfg.users;
|
||||
};
|
||||
}
|
Loading…
Add table
Reference in a new issue