Compare commits

...

4 commits

7 changed files with 168 additions and 165 deletions

View file

@ -93,5 +93,9 @@ in
nodes = config.services.wireguard.nodes.mesh;
shared = false;
};
simulacrum = {
enable = true;
settings = ./test.nix;
};
};
}

View file

@ -0,0 +1,26 @@
{ cluster, lib, ... }:
{
testScript = ''
start_all()
${lib.pipe cluster.config.services.wireguard.nodes.mesh [
(map (node: /*python*/ ''
${node}.wait_for_unit("wireguard-wgmesh.target")
''))
(lib.concatStringsSep "\n")
]}
${lib.pipe cluster.config.services.wireguard.nodes.mesh [
(map (node: /*python*/ ''
with subtest("${node} can reach all other nodes"):
${lib.pipe (cluster.config.services.wireguard.otherNodes.mesh node) [
(map (peer: /*python*/ ''
${node}.succeed("ping -c3 ${cluster.config.hostLinks.${peer}.mesh.extra.meshIp}")
''))
(lib.concatStringsSep "\n ")
]}
''))
(lib.concatStringsSep "\n")
]}
'';
}

View file

@ -4,9 +4,15 @@
let
serviceConfig = config.cluster.config.services.${service};
serviceList = [ service ] ++ serviceConfig.simulacrum.deps;
serviceList = getDepsRecursive [] service;
allAugments = map (svc: config.cluster.config.services.${svc}.simulacrum.augments) serviceList;
getDepsRecursive = acc: service: let
deps = lib.subtractLists acc config.cluster.config.services.${service}.simulacrum.deps;
acc' = acc ++ [ service ];
recurse = getDepsRecursive acc';
in lib.unique (lib.flatten ([ service ] ++ map recurse deps));
lift = config;
snakeoil = {
@ -17,7 +23,8 @@ let
};
nodes = lib.attrNames config.gods.fromLight;
digits = lib.attrsets.listToAttrs (lib.zipListsWith lib.nameValuePair nodes (lib.range 1 255));
nodes' = lib.attrNames (config.gods.fromLight // { nowhere = null; });
digits = lib.attrsets.listToAttrs (lib.zipListsWith lib.nameValuePair nodes' (lib.range 1 255));
depot' = extendModules {
modules = [
({ config, ... }: {
@ -51,6 +58,12 @@ testers.runNixOSTest {
imports = [
serviceConfig.simulacrum.settings
./nowhere
{
nodes.nowhere.imports = [
config.flake.nixosModules.port-magic
];
}
] ++ allAugments;
_module.args = {
@ -73,12 +86,15 @@ testers.runNixOSTest {
${hour.interfaces.primary.link} = {
useDHCP = lib.mkForce false;
virtual = true;
ipv4.addresses = lib.mkForce [
ipv4.addresses = lib.mkForce ([
{
address = hour.interfaces.primary.addr;
prefixLength = 32;
}
];
] ++ lib.optional hour.interfaces.primary.isNat {
address = hour.interfaces.primary.addrPublic;
prefixLength = 32;
});
};
eth1.ipv4.routes = lib.pipe nodes [
(lib.filter (n: n != node))
@ -95,6 +111,7 @@ testers.runNixOSTest {
firewall.extraCommands = lib.mkAfter (lib.optionalString (hour.interfaces.primary.isNat) ''
# self-nat
iptables -t nat -A PREROUTING -d ${hour.interfaces.primary.addrPublic} -j DNAT --to-destination ${hour.interfaces.primary.addr}
iptables -t nat -A OUTPUT -d ${hour.interfaces.primary.addrPublic} -j DNAT --to-destination ${hour.interfaces.primary.addr}
iptables -t nat -A POSTROUTING -s ${hour.interfaces.primary.addr} -j SNAT --to-source ${hour.interfaces.primary.addrPublic}
'');
};

View file

@ -0,0 +1,101 @@
{ cluster, config, lib, pkgs, ... }:
let
lift = config;
cfsslConfigIntermediateCA = pkgs.writeText "simulacrum-cfssl-config.json" (builtins.toJSON {
signing = {
default.expiry = "8760h";
profiles.intermediate = {
expiry = "8760h";
usages = [
"cert sign"
"crl sign"
];
ca_constraint = {
is_ca = true;
max_path_len = 1;
};
};
};
});
caCsr = pkgs.writeText "simulacrum-ca-csr.json" (builtins.toJSON {
CN = "Simulacrum Root CA";
});
ca = pkgs.runCommand "simulacrum-snakeoil-ca" {
nativeBuildInputs = [
pkgs.cfssl
];
} ''
mkdir $out
cfssl gencert --initca ${caCsr} | cfssljson --bare $out/ca
'';
genCert = extraFlags: csrData: let
csr = pkgs.writeText "simulacrum-csr.json" (builtins.toJSON csrData);
in pkgs.runCommand "simulacrum-snakeoil-cert" {
nativeBuildInputs = [
pkgs.cfssl
];
} ''
mkdir $out
cfssl gencert ${lib.escapeShellArgs ([
"--ca=file:${ca}/ca.pem"
"--ca-key=file:${ca}/ca-key.pem"
] ++ extraFlags ++ [
csr
])} | cfssljson --bare $out/cert
'';
genHostCert = hostname: genCert [ "--hostname=${hostname}" ] { CN = hostname; };
getNodeAddr = node: (builtins.head config.nodes.${node}.networking.interfaces.eth1.ipv4.addresses).address;
in
{
imports = [
./options.nix
];
defaults = {
networking.hosts."${getNodeAddr "nowhere"}" = lib.attrNames config.nowhere.names;
security.pki.certificateFiles = [
"${ca}/ca.pem"
];
};
nowhere.certs = {
inherit ca;
intermediate = genCert [ "--config=${cfsslConfigIntermediateCA}" "--profile=intermediate" ] {
CN = "Simulacrum Intermediate CA";
};
};
nodes.nowhere = { config, depot, ... }: {
networking = {
firewall.allowedTCPPorts = [ 443 ];
interfaces.eth1.ipv4.routes = lib.mapAttrsToList (name: hour: {
address = hour.interfaces.primary.addrPublic;
prefixLength = 32;
via = getNodeAddr name;
}) depot.gods.fromLight;
nameservers = map (name: depot.hours.${name}.interfaces.primary.addrPublic) cluster.config.services.dns.nodes.authoritative;
};
services.nginx = {
enable = true;
recommendedProxySettings = true;
virtualHosts = lib.mapAttrs (name: link: let
cert = genHostCert name;
in {
forceSSL = true;
sslCertificate = "${cert}/cert.pem";
sslCertificateKey = "${cert}/cert-key.pem";
locations."/" = {
proxyPass = config.links.${link}.url;
extraConfig = "proxy_ssl_verify off;";
};
}) lift.nowhere.names;
};
};
}

View file

@ -0,0 +1,16 @@
{ lib, ... }:
{
options.nowhere = {
names = lib.mkOption {
description = "Hostnames that point Nowhere.";
type = with lib.types; attrsOf str;
default = {};
};
certs = lib.mkOption {
description = "Snakeoil certificate packages.";
type = with lib.types; attrsOf package;
default = {};
};
};
}

View file

@ -15,12 +15,6 @@ in
inherit (config) cluster;
};
garage = pkgs.callPackage ./garage.nix {
inherit (self'.packages) garage consul;
inherit (self) nixosModules;
inherit (config) cluster;
};
ipfs-cluster-upgrade = pkgs.callPackage ./ipfs-cluster-upgrade.nix {
inherit (self) nixosModules;
previous = timeMachine.preUnstable;

View file

@ -1,155 +0,0 @@
{ testers, nixosModules, cluster, garage, consul }:
testers.runNixOSTest {
name = "garage";
imports = [
./modules/consul.nix
];
extraBaseModules.services.consul.package = consul;
nodes = let
common = { config, lib, ... }: let
inherit (config.networking) hostName primaryIPAddress;
in {
imports = lib.flatten [
./modules/nixos/age-dummy-secrets
./modules/nixos/age-dummy-secrets/options.nix
nixosModules.ascensions
nixosModules.systemd-extras
nixosModules.consul-distributed-services
nixosModules.port-magic
cluster.config.services.storage.nixos.garage
cluster.config.services.storage.nixos.garageInternal
cluster.config.services.consul.nixos.ready
];
options.services.locksmith.providers = lib.mkOption {
type = lib.types.raw;
};
config = {
links.consulAgent = {
protocol = "http";
hostname = "consul";
port = 8500;
};
_module.args = {
depot.packages = { inherit garage; };
cluster.config = {
hostLinks.${hostName} = {
garageRpc.tuple = "${primaryIPAddress}:3901";
garageS3.tuple = "${primaryIPAddress}:8080";
garageWeb.tuple = "${primaryIPAddress}:8081";
};
links.garageWeb.hostname = "web.garage.example.com";
vars.meshNet.cidr = "192.168.0.0/16";
};
};
environment.etc."dummy-secrets/garageRpcSecret".text = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
networking.firewall.allowedTCPPorts = [ 3901 8080 ];
services.garage = {
layout.initial = lib.mkOverride 51 {
garage1 = { zone = "dc1"; capacity = 1000; };
garage2 = { zone = "dc1"; capacity = 1000; };
garage3 = { zone = "dc1"; capacity = 1000; };
};
};
system.ascensions.garage-layout.incantations = lib.mkOverride 51 (i: [ ]);
specialisation.modifiedLayout = {
inheritParentConfig = true;
configuration = {
services.garage = {
layout.initial = lib.mkForce {
garage1 = { zone = "dc1"; capacity = 2000; };
garage2 = { zone = "dc1"; capacity = 1000; };
garage3 = { zone = "dc1"; capacity = 1000; };
};
keys.testKey.allow.createBucket = true;
buckets = {
bucket1 = {
allow.testKey = [ "read" "write" ];
quotas = {
maxObjects = 300;
maxSize = 400 * 1024 * 1024;
};
};
bucket2 = {
allow.testKey = [ "read" ];
};
};
};
system.ascensions.garage-layout.incantations = lib.mkForce (i: [
(i.runGarage ''
garage layout assign -z dc1 -c 2000 "$(garage node id -q | cut -d@ -f1)"
garage layout apply --version 2
'')
]);
};
};
};
};
in {
garage1.imports = [ common ];
garage2.imports = [ common ];
garage3.imports = [ common ];
};
testScript = { nodes, ... }: /*python*/ ''
nodes = [garage1, garage2, garage3]
start_all()
with subtest("should bootstrap new cluster"):
for node in nodes:
node.wait_for_unit("garage.service")
for node in nodes:
node.wait_until_fails("garage status | grep 'NO ROLE ASSIGNED'")
with subtest("should apply new layout with ascension"):
for node in nodes:
node.wait_until_succeeds('test "$(systemctl list-jobs | wc -l)" -eq 1')
for node in nodes:
node.succeed("/run/current-system/specialisation/modifiedLayout/bin/switch-to-configuration test")
for node in nodes:
node.wait_until_succeeds("garage layout show | grep -w 2000")
assert "1" in node.succeed("garage layout show | grep -w 2000 | wc -l")
assert "2" in node.succeed("garage layout show | grep -w 1000 | wc -l")
with subtest("should apply new layout from scratch"):
for node in nodes:
node.systemctl("stop garage.service")
node.succeed("rm -rf /var/lib/garage-metadata")
for node in nodes:
node.systemctl("start garage.service")
for node in nodes:
node.wait_for_unit("garage.service")
for node in nodes:
node.wait_until_fails("garage status | grep 'NO ROLE ASSIGNED'")
for node in nodes:
node.wait_until_succeeds("garage layout show | grep -w 2000")
assert "1" in node.succeed("garage layout show | grep -w 2000 | wc -l")
assert "2" in node.succeed("garage layout show | grep -w 1000 | wc -l")
with subtest("should create specified buckets and keys"):
for node in nodes:
node.wait_until_succeeds('test "$(systemctl is-active garage-apply)" != activating')
garage1.succeed("garage key list | grep testKey")
garage1.succeed("garage bucket list | grep bucket1")
garage1.succeed("garage bucket list | grep bucket2")
with subtest("should delete unspecified buckets and keys"):
garage1.succeed("garage bucket create unwantedbucket")
garage1.succeed("garage key new --name unwantedkey")
garage1.succeed("systemctl restart garage-apply.service")
garage1.fail("garage key list | grep unwantedkey")
garage1.fail("garage bucket list | grep unwantedbucket")
'';
}