feat: add acme

main
flop 5 days ago
parent 634ce485b8
commit 80ea31cc30
  1. 1
      flake.nix
  2. 12
      machines/core.domain.com/configuration.nix
  3. 11
      machines/deploy.domain.com/configuration.nix
  4. 28
      machines/deploy.domain.com/hardware-configuration.nix
  5. 61
      modules/acme.nix
  6. 12
      modules/nginx.nix

@ -38,6 +38,7 @@
./modules/users.nix
./modules/auto-update.nix
./modules/nginx.nix
./modules/acme.nix
];
# Central subdomain -> service mapping.

@ -3,13 +3,12 @@
{
imports = [
./hardware-configuration.nix
../../modules/keycloak.nix
];
boot.loader.grub.enable = true;
boot.loader.grub.device = "/dev/sda";
networking.hostName = "auth";
networking.hostName = "core";
networking.domain = "domain.com";
time.timeZone = "UTC";
@ -29,6 +28,11 @@
bash
];
infra.acme = {
enable = true;
email = "admin@${config.networking.domain}";
};
services.keycloak = {
enable = true;
hostname = "auth.domain.com";
@ -47,8 +51,8 @@
infra.autoUpdate = {
enable = true;
# selects the nixosConfigurations.auth.domain.com output from the flake
flake = "${git_url}#auth.domain.com";
# selects the nixosConfigurations.core.domain.com output from the flake
flake = "${git_url}#core.domain.com";
allowReboot = true;
};

@ -8,7 +8,7 @@
boot.loader.grub.enable = true;
boot.loader.grub.device = "/dev/sda";
networking.hostName = "status";
networking.hostName = "deploy";
networking.domain = "domain.com";
time.timeZone = "UTC";
@ -28,6 +28,11 @@
bash
];
infra.acme = {
enable = true;
email = "admin@${config.networking.domain}";
};
infra.gitea = {
enable = true;
hostname = "git.${config.networking.domain}";
@ -40,8 +45,8 @@
infra.autoUpdate = {
enable = true;
# selects the nixosConfigurations.status.domain.com output from the flake
flake = "${git_url}#status.domain.com";
# selects the nixosConfigurations.deploy.domain.com output from the flake
flake = "${git_url}#deploy.domain.com";
allowReboot = true;
};

@ -0,0 +1,28 @@
{ config, lib, pkgs, modulesPath, ... }:
{
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [ "xhci_pci" "ehci_pci" "ahci" "usb_storage" "sd_mod" "sr_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
fileSystems."/" = {
device = "/dev/disk/by-label/nixos";
fsType = "ext4";
};
fileSystems."/boot" = {
device = "/dev/disk/by-label/boot";
fsType = "vfat";
};
swapDevices = [ { device = "/dev/disk/by-label/swap"; } ];
networking.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}

@ -0,0 +1,61 @@
# Let's Encrypt (ACME) certificate issuance, per-host HTTP-01.
#
# Termination & deployment scheme
# -------------------------------
# Each NixOS host terminates TLS itself for the subdomains it serves.
# There is no central certificate store and no distribution step.
#
# 1. A host declares which subdomains it serves via `domains = [ ... ]`
# in flake.nix (consumed by modules/nginx.nix).
# 2. With `infra.acme.enable = true`, the nginx module sets
# `enableACME = true; forceSSL = true;` on every served vhost.
# 3. security.acme requests one cert per vhost via the HTTP-01
# challenge; nginx answers /.well-known/acme-challenge/ on :80.
# 4. On issue/renewal, nginx is reloaded automatically
# (`reloadServices`), so the new certificate goes live at once.
# 5. Renewals run on a systemd timer; every host (core, deploy, ...)
# keeps its own certificates fresh — nothing is deployed by hand.
#
# Adding a new endpoint (e.g. deploy.domain.com):
# - add the host (or add the subdomain to an existing host's `domains`),
# - enable `infra.acme` on that host,
# - point the subdomain's DNS at the host and leave :80 reachable so
# the HTTP-01 challenge can complete.
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.infra.acme;
in {
options.infra.acme = {
enable = mkEnableOption "Let's Encrypt (ACME) TLS certificates";
email = mkOption {
type = types.str;
description = "Account email used for Let's Encrypt";
};
staging = mkOption {
type = types.bool;
default = false;
description = ''
Use the Let's Encrypt staging environment
(untrusted certificates, no rate limiting) for testing.
'';
};
};
config = mkIf cfg.enable {
security.acme = {
acceptTerms = true;
defaults = {
inherit (cfg) email;
reloadServices = [ "nginx.service" ];
} // optionalAttrs cfg.staging {
server = "https://acme-staging-v02.api.letsencrypt.org/directory";
};
};
};
}

@ -3,18 +3,22 @@
let
served = lib.filterAttrs (k: _: builtins.elem k domains) vhosts;
enabled = served != { };
acme = config.infra.acme.enable or false;
in {
config = lib.mkIf enabled {
services.nginx = {
enable = true;
virtualHosts = lib.mapAttrs (name: vhost:
let
port = vhost.ports.http or 80;
port = vhost.ports.http or null;
hasProxy = port != null;
root = vhost.root or null;
in {
enableACME = vhost.enableACME or false;
forceSSL = vhost.enableACME or false;
enableACME = acme;
forceSSL = acme;
root = lib.mkIf (root != null) root;
extraConfig = vhost.extraConfig or "";
locations."/" = {
locations."/" = lib.mkIf hasProxy {
proxyPass = "http://localhost:${toString port}";
proxyWebsockets = vhost.websockets or false;
};

Loading…
Cancel
Save