diff --git a/modules/le_nginx.nix b/modules/le_nginx.nix index 2484791..9814335 100644 --- a/modules/le_nginx.nix +++ b/modules/le_nginx.nix @@ -1,3 +1,4 @@ +# DEPRECATED: use security.acme + ./nginx.nix on nixos >=16.03 { config, lib, pkgs, ... }: let cfg = config.le_nginx; diff --git a/modules/nginx.nix b/modules/nginx.nix new file mode 100644 index 0000000..eb9af5a --- /dev/null +++ b/modules/nginx.nix @@ -0,0 +1,172 @@ +{ config, lib, pkgs, ... }: +let +cfg = config.nginxssl; +sslcfg = dir: '' + ssl on; + ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH'; + ssl_certificate_key ${dir}/key.pem; + ssl_certificate ${dir}/fullchain.pem; + ssl_trusted_certificate ${dir}/fullchain.pem; + ssl_dhparam /etc/nginx/dhparam.pem; + ssl_protocols TLSv1.1 TLSv1.2; + # ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK'; + ssl_prefer_server_ciphers on; + add_header Strict-Transport-Security max-age=15768000; + ssl_stapling on; + ssl_stapling_verify on; +''; + +makeChallenges = servername: key_webroot: '' + server { + listen 80; + server_name ${servername}; + server_tokens off; + location /.well-known/acme-challenge { + default_type text/plain; + alias ${key_webroot}/.well-known/acme-challenge; + } + } +''; +makeServerBlock = servername: {key_root, key_webroot, contents, ...}: '' + server { + listen 80; + server_name ${servername}; + server_tokens off; + location /.well-known/acme-challenge { + default_type text/plain; + alias ${key_webroot}/.well-known/acme-challenge; + } + location / { + rewrite ^(.*) https://$host$1 permanent; + } + } + server { + listen 443; + server_name ${servername}; + server_tokens off; + ${sslcfg key_root} + ${contents} + } +''; +#vhosts = with lib; unique (concatMap (splitString " ") (attrNames cfg.servers)); +servopts = {...}: { + options = { + key_webroot = mkOption { + type = types.string; + description = "The path where the acme challenge is stored"; + }; + key_root = mkOption { + type = types.string; + description = "The path where the SSL keys are stored"; + }; + contents = mkOption { + type = types.string; + description = "Extra server block contents, like location blocks"; + example = "location / {}"; + }; + }; +}; +inherit (lib) mkEnableOption mkOption types mkIf; +in +{ + options.nginxssl = { + enable = mkEnableOption "enable new nginx module"; + no_vhost_keydir = mkOption { + type = types.string; + default = "/etc/sslcerts/no_vhost"; + description = "The path where the SSL keys for the default are stored (can and will be self-signed)"; + }; + servers = mkOption { + type = types.attrsOf types.optionSet; + description = "The servers to host"; + default = {}; + example = {"git.domain.com" = { + contents = "location / {}"; + key_root = "/var/lib/acme/git.domain.com"; + key_webroot = "/etc/sslcerts/acmeroot"; + }; + }; + options = [ servopts ]; + }; + challenges = mkOption { + type = types.attrsOf types.string; + default = []; + example = {"mail.domain.com" = "/var/lib/acme/mail.domain.com";}; + description = "Other domains to host challenges for"; + }; + }; + config = mkIf cfg.enable { + services.nginx = { + enable = true; + httpConfig = '' + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log logs/access.log main; + sendfile on; + #tcp_nopush on; + + #keepalive_timeout 0; + keepalive_timeout 65; + + server_tokens off; + + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + + gzip on; + # the default thing, for if no vhost is given + # generate default.pem and default.key manually + # and self-sign, if you feel like it + server { + listen 80 default_server; + server_name ""; + location / { + rewrite ^(.*) https://$host$1 permanent; + } + } + server { + listen 443 default_server spdy deferred; + server_name ""; + + ${sslcfg cfg.no_vhost_keydir} + + location / { + root ${pkgs.nginx}/html; + index index.html index.htm; + } + + location = /50x.html { + root ${pkgs.nginx}/html; + } + } + + ${lib.concatStringsSep "\n" (lib.mapAttrsToList makeChallenges cfg.challenges)} + + ${lib.concatStringsSep "\n" (lib.mapAttrsToList makeServerBlock cfg.servers)} + + ''; + }; + networking.firewall.allowedTCPPorts = [80 443]; + system.activationScripts.nginxdhparams = + '' + if ! [[ -e /etc/nginx/dhparam.pem ]]; then + mkdir -p /etc/nginx/ + ${pkgs.openssl}/bin/openssl dhparam -out /etc/nginx/dhparam.pem 2048 + fi + dir=${cfg.no_vhost_keydir} + mkdir -m 0700 -p $dir + if ! [[ -e $dir/key.pem ]]; then + ${pkgs.openssl}/bin/openssl genrsa -passout pass:foo -des3 -out $dir/key-in.pem 1024 + ${pkgs.openssl}/bin/openssl req -passin pass:foo -new -key $dir/key-in.pem -out $dir/key.csr \ + -subj "/C=NL/CN=www.example.com" + ${pkgs.openssl}/bin/openssl rsa -passin pass:foo -in $dir/key-in.pem -out $dir/key.pem + ${pkgs.openssl}/bin/openssl x509 -req -days 365 -in $dir/key.csr -signkey $dir/key.pem -out $dir/fullchain.pem + fi + ''; + }; + + +} diff --git a/pennyworth/configuration.nix b/pennyworth/configuration.nix index 468e0d2..97aa012 100644 --- a/pennyworth/configuration.nix +++ b/pennyworth/configuration.nix @@ -7,11 +7,14 @@ let secrets = import ; yoricc = import ../packages/yori-cc.nix; + acmeWebRoot = "/etc/sslcerts/acmeroot"; + acmeKeyDir = "${config.security.acme.directory}/yori.cc"; in { imports = [ ./hardware-configuration.nix ../roles/common.nix + ../modules/nginx.nix ]; networking.hostName = secrets.hostnames.pennyworth; @@ -25,48 +28,42 @@ in users.extraUsers.root.hashedPassword = secrets.pennyworth_hashedPassword; - services.nginx = { + nginxssl = { enable = true; - httpConfig = '' - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log logs/access.log main; - sendfile on; - #tcp_nopush on; - - #keepalive_timeout 0; - keepalive_timeout 65; - - - gzip on; - - server { - listen 80; - server_name ""; - - location / { - root ${pkgs.nginx}/usr/share/nginx/html; - index index.html index.htm; - } - - location = /50x.html { - root ${pkgs.nginx}/usr/share/nginx/html; - } - } - - server { - listen 80; - server_name yori.cc; - server_tokens off; + challenges."${config.networking.hostName}" = acmeWebRoot; + servers."yori.cc" = { + key_root = acmeKeyDir; + key_webroot = acmeWebRoot; + contents = '' location / { root ${yoricc}/web; } - } - - ''; + ''; + }; }; - networking.firewall.allowedTCPPorts = [80]; + + # Let's Encrypt configuration. + security.acme.certs."yori.cc" = + { email = secrets.email; + extraDomains = { + "${config.networking.hostName}" = null; + }; + webroot = acmeWebRoot; + postRun = "systemctl reload nginx.service dovecot2.service opensmtpd.service"; + }; + # Generate a dummy self-signed certificate until we get one from + # Let's Encrypt. + system.activationScripts.letsEncryptKeys = + '' + dir=${acmeKeyDir} + mkdir -m 0700 -p $dir + if ! [[ -e $dir/key.pem ]]; then + ${pkgs.openssl}/bin/openssl genrsa -passout pass:foo -des3 -out $dir/key-in.pem 1024 + ${pkgs.openssl}/bin/openssl req -passin pass:foo -new -key $dir/key-in.pem -out $dir/key.csr \ + -subj "/C=NL/CN=www.example.com" + ${pkgs.openssl}/bin/openssl rsa -passin pass:foo -in $dir/key-in.pem -out $dir/key.pem + ${pkgs.openssl}/bin/openssl x509 -req -days 365 -in $dir/key.csr -signkey $dir/key.pem -out $dir/fullchain.pem + fi + ''; }