From 2ef7864c9f4d7ebfd1a698f1b77ca18d204ebfb4 Mon Sep 17 00:00:00 2001 From: pinpox Date: Fri, 31 Oct 2025 16:54:22 +0100 Subject: [PATCH] Add ssl module example --- clanServices/ssl/README.md | 1 + clanServices/ssl/default.nix | 111 ++++++++++++++++++++++++++ clanServices/ssl/flake-module.nix | 47 +++++++++++ clanServices/ssl/tests/vm/default.nix | 23 ++++++ 4 files changed, 182 insertions(+) create mode 100644 clanServices/ssl/README.md create mode 100644 clanServices/ssl/default.nix create mode 100644 clanServices/ssl/flake-module.nix create mode 100644 clanServices/ssl/tests/vm/default.nix diff --git a/clanServices/ssl/README.md b/clanServices/ssl/README.md new file mode 100644 index 000000000..04c30234b --- /dev/null +++ b/clanServices/ssl/README.md @@ -0,0 +1 @@ +This a test README just to appease the eval warnings if we don't have one \ No newline at end of file diff --git a/clanServices/ssl/default.nix b/clanServices/ssl/default.nix new file mode 100644 index 000000000..5d6ee6d95 --- /dev/null +++ b/clanServices/ssl/default.nix @@ -0,0 +1,111 @@ +/* + Set up a CA chain for the clan. There will be one root CA for each instance + of the ssl service, then each host has its own host CA that is signed by the + instance-wide root CA. + + Trusting the root CA, will result in also trusting the individual host CAs, + as they are signed by it. + + Hosts can then use their respective host CAs to expose SSL secured services. +*/ +{ + exports, + config, + lib, + ... +}: +{ + _class = "clan.service"; + manifest.name = "clan-core/ssl"; + manifest.description = "Set up a CA infrastucture for your clan"; + manifest.readme = builtins.readFile ./README.md; + + # Generate a root CA for each instances of the ssl module. + exports = lib.mapAttrs' (instanceName: _: { + "ssl/${instanceName}///".vars.generators.ssl-root-ca = + { config, ... }: + { + + files.key = { }; + files.cert.secret = false; + + runtimeInputs = [ + config.pkgs.pkgs.openssl + ]; + + script = '' + # Generate CA private key (4096-bit RSA) + openssl genrsa -out "$out/key" 4096 + + # Generate self-signed CA certificate (valid for 10 years) + openssl req -new -x509 \ + -key "$out/key" \ + -out "$out/cert" \ + -days 3650 \ + -subj "/C=US/ST=State/L=City/O=Organization/OU=IT/CN=Root CA" \ + -sha256 + ''; + }; + }) config.instances; + + roles.default = { + description = "Generate a host CA, signed by the root CA and trust the root CA"; + perInstance = + { + instanceName, + machine, + ... + }: + { + # Generate a host CA, which depends on (is signed by) the root CA + exports = { + "ssl/${instanceName}/default/${machine.name}/".vars.generators.ssl-host-ca = + { config, ... }: + { + + dependencies = { + ssl-root-ca = exports."ssl/${instanceName}///".vars.generators.ssl-root-ca; + }; + + files.key = { }; + files.cert.secret = false; + + runtimeInputs = [ + config.pkgs.pkgs.openssl + ]; + + script = '' + # Generate intermediate CA private key (4096-bit RSA) + openssl genrsa -out "$out/key" 4096 + + # Generate Certificate Signing Request (CSR) for intermediate CA + openssl req -new \ + -key "$out/key" \ + -out "$out/csr" \ + -subj "/C=US/ST=State/L=City/O=Organization/OU=IT/CN=Host CA" + + # Sign the CSR with the root CA to create the intermediate certificate + openssl x509 -req \ + -in "$out/csr" \ + -CA "$dependencies/ssl-root-ca/cert" \ + -CAkey "$dependencies/ssl-root-ca/key" \ + -CAcreateserial \ + -out "$out/cert" \ + -days 3650 \ + -sha256 \ + -extfile <(printf "basicConstraints=CA:TRUE\nkeyUsage=keyCertSign,cRLSign") + ''; + }; + }; + + nixosModule = + { ... }: + { + # We trust the (public) root CA certificate on all machines with this role + security.pki.certificateFiles = [ + exports."ssl/${instanceName}///".vars.generators.ssl-root-ca.files.cert.path + ]; + }; + }; + }; +} diff --git a/clanServices/ssl/flake-module.nix b/clanServices/ssl/flake-module.nix new file mode 100644 index 000000000..ebc17f9b2 --- /dev/null +++ b/clanServices/ssl/flake-module.nix @@ -0,0 +1,47 @@ +{ + self, + inputs, + lib, + ... +}: +let + module = ./default.nix; +in +{ + clan.modules = { + ssl = module; + }; + perSystem = + { ... }: + let + # Module that contains the tests + # This module adds: + # - legacyPackages..eval-tests-ssl + # - checks..eval-tests-ssl + # unit-test-module = ( + # self.clanLib.test.flakeModules.makeEvalChecks { + # inherit module; + # inherit inputs; + # fileset = lib.fileset.unions [ + # # The ssl service being tested + # ../../clanServices/ssl + # # Required modules + # ../../nixosModules/clanCore + # ]; + # testName = "ssl"; + # tests = ./tests/eval-tests.nix; + # # Optional arguments passed to the test + # testArgs = { }; + # } + # ); + in + { + # imports = [ unit-test-module ]; + + clan.nixosTests.ssl = { + imports = [ ./tests/vm/default.nix ]; + + clan.modules.ssl = module; + }; + }; +} diff --git a/clanServices/ssl/tests/vm/default.nix b/clanServices/ssl/tests/vm/default.nix new file mode 100644 index 000000000..f8987d763 --- /dev/null +++ b/clanServices/ssl/tests/vm/default.nix @@ -0,0 +1,23 @@ +{ + name = "ssl"; + + clan = { + directory = ./.; + inventory = { + machines.peer1 = { }; + machines.peer2 = { }; + + instances."test" = { + module.name = "ssl"; + module.input = "self"; + roles.default.machines.peer1 = { }; + }; + }; + }; + + testScript = + { ... }: + '' + start_all() + ''; +}