1= Fleet23++++4<p align="center"><a href="https://github.com/CertainLach/fleet"><img alt="fleet temporary logo generated with midjourney" src="./docs/tmplogo.png" width="200px"></img></a></p>5++++67An NixOS cluster deployment tool.89== Advantages over existing configuration systems (NixOps/Morph)1011- Modules can configure multiple hosts at once (I.e for wireguard/kubernetes installation)12- Secrets can be securely stored in Git (No one except target hosts can decrypt them), automatically regenerated, reencrypted, etc.13- Automatic rollback on deployment failure, which will work, as long as system is passing initrd stage (So still be carefull with root filesystem mount)1415== Secret generator example1617TODO:: This section should into some kind of fleet documentation... But as there is none, it is just left here as-is.1819=== Quickly run securely setup gitlab2021[source,nix]22----23{config, ...}: {24 secrets = let ownership = { owner = "gitlab"; group = "gitlab"; }; in {25 gitlab-initial-root = {26 generator = {mkPassword}: mkPassword {};27 } // ownership;28 gitlab-secret = {29 generator = {mkPassword}: mkPassword {};30 } // ownership;31 gitlab-otp = {32 generator = {mkPassword}: mkPassword {};33 } // ownership;34 gitlab-db = {35 generator = {mkPassword}: mkPassword {};36 } // ownership;37 gitlab-jws = {38 generator = {mkRsa}: mkRsa {};39 } // ownership;40 };41 services.gitlab = let secrets = config.secrets; in {42 enable = true;43 initialRootPasswordFile = secrets.gitlab-initial-root.secretPath;44 secrets = {45 secretFile = secrets.gitlab-secret.secretPath;46 otpFile = secrets.gitlab-otp.secretPath;47 dbFile = secrets.gitlab-db.secretPath;48 jwsFile = secrets.gitlab-jws.secretPath;49 };50 };51}52----5354=== Securely initialize kubernetes secrets5556In my homelab and clusters, I almost always have some sort of HSM, and to issue new kubernetes certs I directly connect to it.57This setup should probably split into multiple steps, where I allow target machine to generate CSR, then copy it to the HSM machine, and then sign it there... But this is just the plan.58I want to build ansible-like script execution in fleet for this kind of tasks.5960[source,nix]61----62{...}: {63 64 nixpkgs.overlays = [65 (final: prev: let66 lib = final.lib;67 in {68 readKubernetesCa = {impureOn}:69 final.mkImpureSecretGenerator ''70 cd ~/ca7172 cert=kubernetes-intermediateCA.crt7374 expires_at=$(openssl x509 -in $cert -noout -enddate | cut -d= -f2 | xargs -I{} date -u -d {} +"%Y-%m-%dT%H:%M:%S.%NZ")75 echo -n $expires_at > $out/expires_at7677 cat $cert > $out/public78 ''79 impureOn;80 mkKubernetesCert = {81 subj,82 sans ? [],83 impureOn,84 }:85 final.mkImpureSecretGenerator ''86 cd ~/ca8788 params=$(sudo mktemp)89 csr=$(sudo mktemp)90 cert=$(sudo mktemp)91 sudo openssl ecparam -genkey -name secp384r1 -out $params92 sudo openssl req -new -key $params \93 -subj "${lib.strings.concatStringsSep "" (lib.attrsets.mapAttrsToList (k: v: "/${k}=${v}") subj)}" \94 ${lib.optionalString (sans != []) "-addext \"subjectAltName = ${lib.strings.concatStringsSep "," sans}\""} \95 -out $csr96 sudo hsms x509 -req -days 365 -in $csr -CA kubernetes-intermediateCA.crt -CAkey "pkcs11:object=[CENSORED] Kubernetes Intermediate CA;type=private" -CAcreateserial -copy_extensions copy -out $cert9798 expires_at=$(sudo openssl x509 -in $cert -noout -enddate | cut -d= -f2 | xargs -I{} date -u -d {} +"%Y-%m-%dT%H:%M:%S.%NZ")99 echo -n $expires_at > $out/expires_at100101 sudo cat $params | encrypt > $out/secret102 sudo cat $cert > $out/public103 ''104 impureOn;105 })106 ];107 108 109 110 environment.systemPackages = [111 pkgs.openssl.bin112 (pkgs.writeShellApplication {113 name = "hsms";114 text = ''115 set -eu116 export OPENSSL_CONF=${openssl-conf}117 118 HSM_PIN=$(cat ${config.secrets.hsm-pin.secretPath})119 exec ${pkgs.openssl}/bin/openssl "$@" -keyform=engine -CAkeyform=engine -engine=pkcs11 -passin=pass:"$HSM_PIN"120 '';121 })122 (pkgs.writeShellApplication {123 name = "hsmt";124 text = ''125 set -eu126 HSM_PIN=$(cat ${config.secrets.hsm-pin.secretPath})127 exec ${pkgs.opensc}/bin/pkcs11-tool -l --pin="$HSM_PIN" "$@"128 '';129 })130 ];131 132 133 sharedSecrets = {134 "ca.pem" = {135 136 regenerateOnOwnerAdded = false;137 138 expectedOwners = ["controlplane-1" "controlplane-2" "worker-1" "worker-2"];139 generator = {readKubernetesCa}:140 readKubernetesCa {141 impureOn = "[CENSORED]";142 };143 };144 "kube-admin.pem" = {145 regenerateOnOwnerAdded = false;146 expectedOwners = ["cluster-admin"];147 generator = {mkKubernetesCert}:148 mkKubernetesCert {149 subj = {150 CN = "admin";151 O = "system:masters";152 };153 impureOn = "[CENSORED]";154 };155 };156 "kube-apiserver.pem" = {157 158 159 160 161 162 163 164 regenerateOnOwnerAdded = true;165 expectedOwners = ["controlplane-1" "controlplane-2"];166 generator = {mkKubernetesCert}:167 mkKubernetesCert {168 inherit sans;169 subj.CN = "kubernetes";170 impureOn = "[CENSORED]";171 };172 };173}174----