use contract for ssl block
This commit is contained in:
parent
909ebe405a
commit
adc09acc49
21 changed files with 790 additions and 144 deletions
|
@ -26,6 +26,10 @@ Self Host Blocks provides at least one implementation for each block and allows
|
|||
implementation if you want to, as long as it passes the tests. You can then use blocks to improve
|
||||
services you already have deployed.
|
||||
|
||||
```{=include=} chapters html:into-file=//blocks-ssl.html
|
||||
modules/blocks/ssl/docs/default.md
|
||||
```
|
||||
|
||||
```{=include=} chapters html:into-file=//blocks-backup.html
|
||||
modules/blocks/backup/docs/default.md
|
||||
```
|
||||
|
|
|
@ -68,21 +68,15 @@ let
|
|||
|
||||
optionsDocs = buildOptionsDocs {
|
||||
modules = allModules ++ [ scrubbedModule ];
|
||||
variablelistId = "selfhostblocks-block-backup-options";
|
||||
includeModuleSystemOptions = false;
|
||||
};
|
||||
|
||||
backupOptionsDocs = buildOptionsDocs {
|
||||
modules = [ ../modules/blocks/backup.nix scrubbedModule ];
|
||||
variablelistId = "selfhostblocks-options";
|
||||
includeModuleSystemOptions = false;
|
||||
};
|
||||
|
||||
nextcloudOptionsDocs = buildOptionsDocs {
|
||||
modules = [ ../modules/services/nextcloud-server.nix scrubbedModule ];
|
||||
individualModuleOptionsDocs = path: (buildOptionsDocs {
|
||||
modules = [ path scrubbedModule ];
|
||||
variablelistId = "selfhostblocks-options";
|
||||
includeModuleSystemOptions = false;
|
||||
};
|
||||
}).optionsJSON;
|
||||
|
||||
nmd = import nmdsrc {
|
||||
inherit lib;
|
||||
|
@ -135,22 +129,27 @@ in stdenv.mkDerivation {
|
|||
'@OPTIONS_JSON@' \
|
||||
${optionsDocs.optionsJSON}/share/doc/nixos/options.json
|
||||
|
||||
substituteInPlace ./modules/blocks/ssl/docs/default.md \
|
||||
--replace \
|
||||
'@OPTIONS_JSON@' \
|
||||
${individualModuleOptionsDocs ../modules/blocks/ssl.nix}/share/doc/nixos/options.json
|
||||
|
||||
substituteInPlace ./modules/blocks/backup/docs/default.md \
|
||||
--replace \
|
||||
'@OPTIONS_JSON@' \
|
||||
${backupOptionsDocs.optionsJSON}/share/doc/nixos/options.json
|
||||
${individualModuleOptionsDocs ../modules/blocks/backup.nix}/share/doc/nixos/options.json
|
||||
|
||||
substituteInPlace ./modules/services/nextcloud-server/docs/default.md \
|
||||
--replace \
|
||||
'@OPTIONS_JSON@' \
|
||||
${nextcloudOptionsDocs.optionsJSON}/share/doc/nixos/options.json
|
||||
${individualModuleOptionsDocs ../modules/services/nextcloud-server.nix}/share/doc/nixos/options.json
|
||||
|
||||
find . -name "*.md" -print0 | \
|
||||
while IFS= read -r -d ''' f; do
|
||||
substituteInPlace "''${f}" \
|
||||
--replace \
|
||||
'@REPO@' \
|
||||
"${ghRoot}"
|
||||
"${ghRoot}" 2>/dev/null
|
||||
done
|
||||
|
||||
nixos-render-docs manual html \
|
||||
|
|
|
@ -58,6 +58,8 @@
|
|||
release = "0.0.1";
|
||||
};
|
||||
|
||||
lib.contracts = pkgs.callPackage ./modules/contracts {};
|
||||
|
||||
checks =
|
||||
let
|
||||
importFiles = files:
|
||||
|
@ -96,6 +98,7 @@
|
|||
// (vm_test "postgresql" ./test/vm/postgresql.nix)
|
||||
// (vm_test "monitoring" ./test/vm/monitoring.nix)
|
||||
// (vm_test "nextcloud" ./test/vm/nextcloud.nix)
|
||||
// (vm_test "ssl" ./test/vm/ssl.nix)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
let
|
||||
cfg = config.shb.authelia;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
|
||||
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
||||
|
||||
autheliaCfg = config.services.authelia.instances.${fqdn};
|
||||
|
@ -35,6 +37,12 @@ in
|
|||
example = "mydomain.com";
|
||||
};
|
||||
|
||||
ssl = lib.mkOption {
|
||||
description = "Path to SSL files";
|
||||
type = lib.types.nullOr contracts.ssl.certs;
|
||||
default = null;
|
||||
};
|
||||
|
||||
ldapEndpoint = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Endpoint for LDAP authentication backend.";
|
||||
|
@ -293,9 +301,9 @@ in
|
|||
lib.mkBefore (lib.concatStringsSep "\n" (map mkCfg cfg.oidcClients));
|
||||
|
||||
services.nginx.virtualHosts.${fqdn} = {
|
||||
forceSSL = lib.mkIf config.shb.ssl.enable true;
|
||||
sslCertificate = lib.mkIf config.shb.ssl.enable "/var/lib/acme/${cfg.domain}/cert.pem";
|
||||
sslCertificateKey = lib.mkIf config.shb.ssl.enable "/var/lib/acme/${cfg.domain}/key.pem";
|
||||
forceSSL = !(isNull cfg.ssl);
|
||||
sslCertificate = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.cert;
|
||||
sslCertificateKey = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.key;
|
||||
# Taken from https://github.com/authelia/authelia/issues/178
|
||||
# TODO: merge with config from https://matwick.ca/authelia-nginx-sso/
|
||||
locations."/".extraConfig = ''
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
let
|
||||
cfg = config.shb.ldap;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
|
||||
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
||||
in
|
||||
{
|
||||
|
@ -33,6 +35,12 @@ in
|
|||
default = 3890;
|
||||
};
|
||||
|
||||
ssl = lib.mkOption {
|
||||
description = "Path to SSL files";
|
||||
type = lib.types.nullOr contracts.ssl.certs;
|
||||
default = null;
|
||||
};
|
||||
|
||||
webUIListenPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
description = "Port on which the web UI is exposed.";
|
||||
|
@ -69,9 +77,9 @@ in
|
|||
enable = true;
|
||||
|
||||
virtualHosts.${fqdn} = {
|
||||
forceSSL = lib.mkIf config.shb.ssl.enable true;
|
||||
sslCertificate = lib.mkIf config.shb.ssl.enable "/var/lib/acme/${cfg.domain}/cert.pem";
|
||||
sslCertificateKey = lib.mkIf config.shb.ssl.enable "/var/lib/acme/${cfg.domain}/key.pem";
|
||||
forceSSL = !(isNull cfg.ssl);
|
||||
sslCertificate = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.cert;
|
||||
sslCertificateKey = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.key;
|
||||
locations."/" = {
|
||||
extraConfig = ''
|
||||
proxy_set_header Host $host;
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
let
|
||||
cfg = config.shb.monitoring;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
|
||||
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
||||
in
|
||||
{
|
||||
|
@ -21,6 +23,12 @@ in
|
|||
example = "mydomain.com";
|
||||
};
|
||||
|
||||
ssl = lib.mkOption {
|
||||
description = "Path to SSL files";
|
||||
type = lib.types.nullOr contracts.ssl.certs;
|
||||
default = null;
|
||||
};
|
||||
|
||||
grafanaPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
description = "Port where Grafana listens to HTTP requests.";
|
||||
|
@ -362,9 +370,10 @@ in
|
|||
enable = true;
|
||||
|
||||
virtualHosts.${fqdn} = {
|
||||
forceSSL = lib.mkIf config.shb.ssl.enable true;
|
||||
sslCertificate = lib.mkIf config.shb.ssl.enable "/var/lib/acme/${cfg.domain}/cert.pem";
|
||||
sslCertificateKey = lib.mkIf config.shb.ssl.enable "/var/lib/acme/${cfg.domain}/key.pem";
|
||||
forceSSL = !(isNull cfg.ssl);
|
||||
sslCertificate = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.cert;
|
||||
sslCertificateKey = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.key;
|
||||
|
||||
locations."/" = {
|
||||
proxyPass = "http://${toString config.services.grafana.settings.server.http_addr}:${toString config.services.grafana.settings.server.http_port}";
|
||||
proxyWebsockets = true;
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
let
|
||||
cfg = config.shb.nginx;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
|
||||
fqdn = c: "${c.subdomain}.${c.domain}";
|
||||
|
||||
autheliaConfig = lib.types.submodule {
|
||||
|
@ -19,6 +21,12 @@ let
|
|||
example = "mydomain.com";
|
||||
};
|
||||
|
||||
ssl = lib.mkOption {
|
||||
description = "Path to SSL files";
|
||||
type = lib.types.nullOr contracts.ssl.certs;
|
||||
default = null;
|
||||
};
|
||||
|
||||
authEndpoint = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Auth endpoint for SSO.";
|
||||
|
@ -102,9 +110,9 @@ in
|
|||
let
|
||||
vhostCfg = c: {
|
||||
${fqdn c} = {
|
||||
forceSSL = lib.mkIf config.shb.ssl.enable true;
|
||||
sslCertificate = lib.mkIf config.shb.ssl.enable "/var/lib/acme/${c.domain}/cert.pem";
|
||||
sslCertificateKey = lib.mkIf config.shb.ssl.enable "/var/lib/acme/${c.domain}/key.pem";
|
||||
forceSSL = !(isNull c.ssl);
|
||||
sslCertificate = lib.mkIf (!(isNull c.ssl)) c.ssl.paths.cert;
|
||||
sslCertificateKey = lib.mkIf (!(isNull c.ssl)) c.ssl.paths.key;
|
||||
|
||||
# Taken from https://github.com/authelia/authelia/issues/178
|
||||
locations."/".extraConfig = ''
|
||||
|
|
|
@ -1,98 +1,338 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.shb.ssl;
|
||||
cfg = config.shb.certs;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
in
|
||||
{
|
||||
options.shb.ssl = {
|
||||
enable = lib.mkEnableOption "selfhostblocks.ssl";
|
||||
|
||||
domain = lib.mkOption {
|
||||
description = "Domain to ask a wildcard certificate for.";
|
||||
type = lib.types.str;
|
||||
example = "domain.com";
|
||||
};
|
||||
|
||||
dnsProvider = lib.mkOption {
|
||||
description = "DNS provider to use. See https://go-acme.github.io/lego/dns/ for the list of supported providers.";
|
||||
type = lib.types.str;
|
||||
example = "linode";
|
||||
};
|
||||
|
||||
credentialsFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
options.shb.certs = {
|
||||
systemdService = lib.mkOption {
|
||||
description = ''
|
||||
Credentials file location for the chosen DNS provider.
|
||||
|
||||
The content of this file must expose environment variables as written in the
|
||||
[documentation](https://go-acme.github.io/lego/dns/) of each DNS provider.
|
||||
|
||||
For example, if the documentation says the credential must be located in the environment
|
||||
variable DNSPROVIDER_TOKEN, then the file content must be:
|
||||
|
||||
DNSPROVIDER_TOKEN=xyz
|
||||
|
||||
You can put non-secret environment variables here too or use shb.ssl.additionalcfg instead.
|
||||
Systemd oneshot service used to generate the Certificate Authority bundle.
|
||||
'';
|
||||
example = "/run/secrets/ssl";
|
||||
};
|
||||
|
||||
additionalCfg = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.str;
|
||||
description = ''Additional environment variables used to configure the DNS provider.
|
||||
|
||||
For secrets, use shb.ssl.credentialsFile instead.
|
||||
|
||||
See the chose provider's [documentation](https://go-acme.github.io/lego/dns/) for available
|
||||
options.
|
||||
'';
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
DNSPROVIDER_TIMEOUT = "10";
|
||||
DNSPROVIDER_PROPAGATION_TIMEOUT = "240";
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
dnsResolver = lib.mkOption {
|
||||
description = "IP of a DNS server used to resolve hostnames.";
|
||||
type = lib.types.str;
|
||||
default = "8.8.8.8";
|
||||
default = "shb-ca-bundle.service";
|
||||
};
|
||||
cas.selfsigned = lib.mkOption {
|
||||
description = "Generate a self-signed Certificate Authority.";
|
||||
default = {};
|
||||
type = lib.types.attrsOf (lib.types.submodule ({ config, ...}: {
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
Certificate Authority Name. You can put what you want here, it will be displayed by the
|
||||
browser.
|
||||
'';
|
||||
default = "Self Host Blocks Certificate";
|
||||
};
|
||||
|
||||
paths = lib.mkOption {
|
||||
description = ''
|
||||
Paths where CA certs will be located.
|
||||
|
||||
This option is the contract output of the `shb.certs.cas` SSL block.
|
||||
'';
|
||||
type = contracts.ssl.certs-paths;
|
||||
default = rec {
|
||||
key = "/var/lib/certs/cas/${config._module.args.name}.key";
|
||||
cert = "/var/lib/certs/cas/${config._module.args.name}.cert";
|
||||
};
|
||||
};
|
||||
|
||||
systemdService = lib.mkOption {
|
||||
description = "Systemd oneshot service used to generate the certs.";
|
||||
type = lib.types.str;
|
||||
default = "shb-certs-ca-${config._module.args.name}.service";
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
certs.selfsigned = lib.mkOption {
|
||||
description = "Generate self-signed certificates signed by a Certificate Authority.";
|
||||
default = {};
|
||||
type = lib.types.attrsOf (lib.types.submodule ({ config, ... }: {
|
||||
options = {
|
||||
ca = lib.mkOption {
|
||||
type = lib.types.nullOr contracts.ssl.cas;
|
||||
description = ''
|
||||
CA used to generate this certificate. Only used for self-signed.
|
||||
|
||||
This contract input takes the contract output of the `shb.certs.cas` SSL block.
|
||||
'';
|
||||
default = null;
|
||||
};
|
||||
|
||||
domain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
Domain to generate a certificate for. This can be a wildcard domain like
|
||||
`*.example.com`.
|
||||
'';
|
||||
example = "example.com";
|
||||
};
|
||||
|
||||
paths = lib.mkOption {
|
||||
description = ''
|
||||
Paths where certs will be located.
|
||||
|
||||
This option is the contract output of the `shb.certs.certs` SSL block.
|
||||
'';
|
||||
type = contracts.ssl.certs-paths;
|
||||
default = rec {
|
||||
key = "/var/lib/certs/selfsigned/${config._module.args.name}.key";
|
||||
cert = "/var/lib/certs/selfsigned/${config._module.args.name}.cert";
|
||||
};
|
||||
};
|
||||
|
||||
systemdService = lib.mkOption {
|
||||
description = "Systemd oneshot service used to generate the certs.";
|
||||
type = lib.types.str;
|
||||
default = "shb-certs-cert-selfsigned-${config._module.args.name}.service";
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
|
||||
adminEmail = lib.mkOption {
|
||||
description = "Admin email in case certificate retrieval goes wrong.";
|
||||
type = lib.types.str;
|
||||
};
|
||||
certs.letsencrypt = lib.mkOption {
|
||||
description = "Generate certificates signed by [Let's Encrypt](https://letsencrypt.org/).";
|
||||
default = {};
|
||||
type = lib.types.attrsOf (lib.types.submodule ({ config, ... }: {
|
||||
options = {
|
||||
domain = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
Domain to generate a certificate for. This can be a wildcard domain like
|
||||
`*.example.com`.
|
||||
'';
|
||||
example = "example.com";
|
||||
};
|
||||
|
||||
debug = lib.mkOption {
|
||||
description = "Enable debug logging";
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
paths = lib.mkOption {
|
||||
description = ''
|
||||
Paths where certs will be located.
|
||||
|
||||
This option is the contract output of the `shb.certs.certs` SSL block.
|
||||
'';
|
||||
type = contracts.ssl.certs-paths;
|
||||
default = {
|
||||
key = "/var/lib/acme/${config._module.args.name}/key.pem";
|
||||
cert = "/var/lib/acme/${config._module.args.name}/cert.pem";
|
||||
};
|
||||
};
|
||||
|
||||
systemdService = lib.mkOption {
|
||||
description = "Systemd oneshot service used to generate the certs.";
|
||||
type = lib.types.str;
|
||||
default = "shb-certs-cert-letsencrypt-${config._module.args.name}.service";
|
||||
};
|
||||
|
||||
dnsProvider = lib.mkOption {
|
||||
description = "DNS provider to use. See https://go-acme.github.io/lego/dns/ for the list of supported providers.";
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "linode";
|
||||
};
|
||||
|
||||
dnsResolver = lib.mkOption {
|
||||
description = "IP of a DNS server used to resolve hostnames.";
|
||||
type = lib.types.str;
|
||||
default = "8.8.8.8";
|
||||
};
|
||||
|
||||
credentialsFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
description = ''
|
||||
Credentials file location for the chosen DNS provider.
|
||||
|
||||
The content of this file must expose environment variables as written in the
|
||||
[documentation](https://go-acme.github.io/lego/dns/) of each DNS provider.
|
||||
|
||||
For example, if the documentation says the credential must be located in the environment
|
||||
variable DNSPROVIDER_TOKEN, then the file content must be:
|
||||
|
||||
DNSPROVIDER_TOKEN=xyz
|
||||
|
||||
You can put non-secret environment variables here too or use shb.ssl.additionalcfg instead.
|
||||
'';
|
||||
example = "/run/secrets/ssl";
|
||||
default = null;
|
||||
};
|
||||
|
||||
additionalEnvironment = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.str;
|
||||
description = ''
|
||||
Additional environment variables used to configure the DNS provider.
|
||||
|
||||
For secrets, use shb.ssl.credentialsFile instead.
|
||||
|
||||
See the chosen provider's [documentation](https://go-acme.github.io/lego/dns/) for
|
||||
available options.
|
||||
'';
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
DNSPROVIDER_TIMEOUT = "10";
|
||||
DNSPROVIDER_PROPAGATION_TIMEOUT = "240";
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
makeAvailableToUser = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = ''
|
||||
Make all certificates available to given user.
|
||||
'';
|
||||
default = null;
|
||||
};
|
||||
|
||||
adminEmail = lib.mkOption {
|
||||
description = "Admin email in case certificate retrieval goes wrong.";
|
||||
type = lib.types.str;
|
||||
};
|
||||
|
||||
debug = lib.mkOption {
|
||||
description = "Enable debug logging";
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
users.users.${config.services.nginx.user} = {
|
||||
isSystemUser = true;
|
||||
group = "nginx";
|
||||
extraGroups = [ config.security.acme.defaults.group ];
|
||||
};
|
||||
users.groups.nginx = {};
|
||||
config =
|
||||
let
|
||||
filterProvider = provider: lib.attrsets.filterAttrs (k: i: i.provider == provider);
|
||||
|
||||
security.acme = {
|
||||
acceptTerms = true;
|
||||
certs."${cfg.domain}" = {
|
||||
extraDomainNames = ["*.${cfg.domain}"];
|
||||
};
|
||||
defaults = {
|
||||
email = cfg.adminEmail;
|
||||
inherit (cfg) dnsProvider dnsResolver;
|
||||
credentialsFile = cfg.credentialsFile;
|
||||
enableDebugLogs = cfg.debug;
|
||||
};
|
||||
};
|
||||
serviceName = lib.strings.removeSuffix ".service";
|
||||
in
|
||||
lib.mkMerge [
|
||||
# Config for self-signed CA.
|
||||
{
|
||||
systemd.services = lib.mapAttrs' (_name: caCfg:
|
||||
lib.nameValuePair (serviceName caCfg.systemdService) {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
wants = [ config.shb.certs.systemdService ];
|
||||
before = [ config.shb.certs.systemdService ];
|
||||
serviceConfig.Type = "oneshot";
|
||||
serviceConfig.RuntimeDirectory = serviceName caCfg.systemdService;
|
||||
# serviceConfig.User = "nextcloud";
|
||||
# Taken from https://github.com/NixOS/nixpkgs/blob/7f311dd9226bbd568a43632c977f4992cfb2b5c8/nixos/tests/custom-ca.nix
|
||||
script = ''
|
||||
cd $RUNTIME_DIRECTORY
|
||||
|
||||
systemd.services."acme-${cfg.domain}".environment = cfg.additionalCfg;
|
||||
};
|
||||
cat >ca.template <<EOF
|
||||
organization = "${caCfg.name}"
|
||||
cn = "${caCfg.name}"
|
||||
expiration_days = 365
|
||||
ca
|
||||
cert_signing_key
|
||||
crl_signing_key
|
||||
EOF
|
||||
|
||||
mkdir -p "$(dirname -- "${caCfg.paths.key}")"
|
||||
${pkgs.gnutls}/bin/certtool \
|
||||
--generate-privkey \
|
||||
--key-type rsa \
|
||||
--sec-param High \
|
||||
--outfile ${caCfg.paths.key}
|
||||
chmod 666 ${caCfg.paths.key}
|
||||
|
||||
mkdir -p "$(dirname -- "${caCfg.paths.cert}")"
|
||||
${pkgs.gnutls}/bin/certtool \
|
||||
--generate-self-signed \
|
||||
--load-privkey ${caCfg.paths.key} \
|
||||
--template ca.template \
|
||||
--outfile ${caCfg.paths.cert}
|
||||
chmod 666 ${caCfg.paths.cert}
|
||||
'';
|
||||
}
|
||||
) cfg.cas.selfsigned;
|
||||
}
|
||||
{
|
||||
systemd.services.${serviceName config.shb.certs.systemdService} = (lib.mkIf (cfg.cas.selfsigned != {}) {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig.Type = "oneshot";
|
||||
script = ''
|
||||
mkdir -p /etc/ssl/certs
|
||||
|
||||
rm -f /etc/ssl/certs/ca-certificates.crt
|
||||
for file in ${lib.concatStringsSep " " (lib.mapAttrsToList (_name: caCfg: caCfg.paths.cert) cfg.cas.selfsigned)}; do
|
||||
cat "$file" >> /etc/ssl/certs/ca-certificates.crt
|
||||
done
|
||||
'';
|
||||
});
|
||||
}
|
||||
# Config for self-signed cert.
|
||||
{
|
||||
systemd.services = lib.mapAttrs' (_name: certCfg:
|
||||
lib.nameValuePair (serviceName certCfg.systemdService) {
|
||||
after = [ certCfg.ca.systemdService ];
|
||||
requires = [ certCfg.ca.systemdService ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig.RuntimeDirectory = serviceName certCfg.systemdService;
|
||||
# Taken from https://github.com/NixOS/nixpkgs/blob/7f311dd9226bbd568a43632c977f4992cfb2b5c8/nixos/tests/custom-ca.nix
|
||||
script = ''
|
||||
cd $RUNTIME_DIRECTORY
|
||||
|
||||
# server cert template
|
||||
cat >server.template <<EOF
|
||||
organization = "An example company"
|
||||
cn = "${certCfg.domain}"
|
||||
expiration_days = 30
|
||||
dns_name = "${certCfg.domain}"
|
||||
encryption_key
|
||||
signing_key
|
||||
EOF
|
||||
|
||||
mkdir -p "$(dirname -- "${certCfg.paths.key}")"
|
||||
${pkgs.gnutls}/bin/certtool \
|
||||
--generate-privkey \
|
||||
--key-type rsa \
|
||||
--sec-param High \
|
||||
--outfile ${certCfg.paths.key}
|
||||
chmod 666 ${certCfg.paths.key}
|
||||
|
||||
mkdir -p "$(dirname -- "${certCfg.paths.cert}")"
|
||||
${pkgs.gnutls}/bin/certtool \
|
||||
--generate-certificate \
|
||||
--load-privkey ${certCfg.paths.key} \
|
||||
--load-ca-privkey ${certCfg.ca.paths.key} \
|
||||
--load-ca-certificate ${certCfg.ca.paths.cert} \
|
||||
--template server.template \
|
||||
--outfile ${certCfg.paths.cert}
|
||||
chmod 666 ${certCfg.paths.cert}
|
||||
'';
|
||||
|
||||
serviceConfig.Type = "oneshot";
|
||||
# serviceConfig.User = "nextcloud";
|
||||
}
|
||||
) cfg.certs.selfsigned;
|
||||
}
|
||||
# Config for Let's Encrypt cert.
|
||||
{
|
||||
users.users = lib.mkMerge (lib.mapAttrsToList (name: certCfg: {
|
||||
${certCfg.makeAvailableToUser}.extraGroups = lib.mkIf (!(isNull certCfg.makeAvailableToUser)) [
|
||||
config.security.acme.defaults.group
|
||||
];
|
||||
}) cfg.certs.letsencrypt);
|
||||
|
||||
security.acme.acceptTerms = lib.mkIf (cfg.certs.letsencrypt != {}) true;
|
||||
|
||||
security.acme.certs = lib.mkMerge (lib.mapAttrsToList (name: certCfg: {
|
||||
"${name}" = {
|
||||
extraDomainNames = [ certCfg.domain ];
|
||||
email = certCfg.adminEmail;
|
||||
inherit (certCfg) dnsProvider dnsResolver;
|
||||
credentialsFile = certCfg.credentialsFile;
|
||||
enableDebugLogs = certCfg.debug;
|
||||
};
|
||||
}) cfg.certs.letsencrypt);
|
||||
|
||||
systemd.services = lib.mkMerge (lib.mapAttrsToList (name: certCfg: {
|
||||
"acme-${certCfg.domain}".environment = certCfg.additionalEnvironment;
|
||||
}) cfg.certs.letsencrypt);
|
||||
}
|
||||
];
|
||||
}
|
||||
|
|
159
modules/blocks/ssl/docs/default.md
Normal file
159
modules/blocks/ssl/docs/default.md
Normal file
|
@ -0,0 +1,159 @@
|
|||
# SSL Block {#ssl-block}
|
||||
|
||||
This NixOS module is a block that provides a contract to generate TLS certificates.
|
||||
|
||||
It is implemented by:
|
||||
- [`shb.certs.cas.selfsigned`][10] and [`shb.certs.certs.selfsigned`][11]: Generates self-signed certificates,
|
||||
including self-signed CA thanks to the [certtool][1] package.
|
||||
- [`shb.certs.certs.letsencrypt`][12]: Requests certificates from [Let's Encrypt][2].
|
||||
|
||||
[1]: https://search.nixos.org/packages?channel=23.11&show=gnutls&from=0&size=50&sort=relevance&type=packages&query=certtool
|
||||
[2]: https://letsencrypt.org/
|
||||
|
||||
[10]: blocks-ssl.html#blocks-ssl-options-shb.certs.cas.selfsigned
|
||||
[11]: blocks-ssl.html#blocks-ssl-options-shb.certs.certs.selfsigned
|
||||
[12]: blocks-ssl.html#blocks-ssl-options-shb.certs.certs.letsencrypt
|
||||
|
||||
## Contract {#ssl-block-contract}
|
||||
|
||||
The contract for this block is defined in [`/modules/contracts/ssl.nix`](@REPO@/modules/contracts/ssl.nix).
|
||||
|
||||
Every module implementing this contract provides the following options:
|
||||
|
||||
- `paths.cert`: Path to the cert file.
|
||||
- `paths.key`: Path to the key file.
|
||||
- `systemdService`: Systemd oneshot service used to generate the certificate.
|
||||
The Systemd service file name must include the `.service` suffix. Downstream users of the
|
||||
certificate can use this option to wait for the certificate to be generated.
|
||||
|
||||
## Implementations {#ssl-block-impl}
|
||||
|
||||
This sections explains how to generate certificates using the SSL block implementations.
|
||||
|
||||
### Self-Signed Certificates {#ssl-block-impl-self-signed}
|
||||
|
||||
Defined in [`/modules/blocks/ssl.nix`](@REPO@/modules/blocks/ssl.nix).
|
||||
|
||||
To use self-signed certificates, we must first generate at least one Certificate Authority (CA):
|
||||
|
||||
```nix
|
||||
shb.certs.cas.selfsigned.myca = {
|
||||
name = "My CA";
|
||||
};
|
||||
```
|
||||
|
||||
Every CA defined this way will be concatenated into the file `/etc/ssl/certs/ca-certificates.cert`
|
||||
which means those CAs and all certificates generated by those CAs will be automatically trusted.
|
||||
|
||||
We can then generate one or more certificates signed by that CA:
|
||||
|
||||
```nix
|
||||
shb.certs.certs.selfsigned = {
|
||||
"example.com" = {
|
||||
ca = config.shb.certs.cas.selfsigned.myca;
|
||||
|
||||
domain = "example.com";
|
||||
};
|
||||
"www.example.com" = {
|
||||
ca = config.shb.certs.cas.selfsigned.myca;
|
||||
|
||||
domain = "www.example.com";
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### Let's Encrypt {#ssl-block-impl-lets-encrypt}
|
||||
|
||||
Defined in [`/modules/blocks/ssl.nix`](@REPO@/modules/blocks/ssl.nix).
|
||||
|
||||
We can ask Let's Encrypt to generate a certificate with:
|
||||
|
||||
```nix
|
||||
shb.certs.certs.letsencrypt."example.com" = {
|
||||
domain = "example.com";
|
||||
dnsProvider = "linode";
|
||||
adminEmail = "admin@example.com";
|
||||
credentialsFile = /path/to/secret/file;
|
||||
additionalEnvironment = {
|
||||
LINODE_HTTP_TIMEOUT = "10";
|
||||
LINODE_POLLING_INTERVAL = "10";
|
||||
LINODE_PROPAGATION_TIMEOUT = "240";
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
The credential file's content would be a key-value pair:
|
||||
|
||||
```yaml
|
||||
LINODE_TOKEN=XYZ...
|
||||
```
|
||||
|
||||
For other providers, see the [official instruction](https://go-acme.github.io/lego/dns/).
|
||||
|
||||
## Usage {#ssl-block-usage}
|
||||
|
||||
To use either a self-signed certificates or a Let's Encrypt generated one, we can reference the path
|
||||
where the certificate and the private key are located:
|
||||
|
||||
```nix
|
||||
config.shb.certs.<implementation>.<name>.paths.cert
|
||||
config.shb.certs.<implementation>.<name>.paths.key
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```nix
|
||||
config.shb.certs.selfsigned."example.com".paths.cert
|
||||
config.shb.certs.selfsigned."example.com".paths.key
|
||||
```
|
||||
We can then configure Nginx to use those certificates:
|
||||
|
||||
```nix
|
||||
services.nginx.virtualHosts."example.com" =
|
||||
let
|
||||
cert = config.shb.certs.selfsigned."example.com";
|
||||
in
|
||||
{
|
||||
onlySSL = true;
|
||||
sslCertificate = cert.paths.cert;
|
||||
sslCertificateKey = cert.paths.key;
|
||||
|
||||
locations."/".extraConfig = ''
|
||||
add_header Content-Type text/plain;
|
||||
return 200 'It works!';
|
||||
'';
|
||||
};
|
||||
```
|
||||
|
||||
To make sure the Nginx webserver can find the generated file, we will make it wait for the
|
||||
certificate to the generated:
|
||||
|
||||
```nix
|
||||
systemd.services.nginx = {
|
||||
after = [ config.shb.certs.selfsigned."example.com".systemdService ];
|
||||
requires = [ config.shb.certs.selfsigned."example.com".systemdService ];
|
||||
};
|
||||
```
|
||||
|
||||
If needed, we can also wait on the CA bundle to be generated by waiting for the Systemd service:
|
||||
|
||||
```nix
|
||||
config.shb.certs.systemdService
|
||||
```
|
||||
|
||||
## Debug {#ssl-block-debug}
|
||||
|
||||
Each CA and Cert is generated by a systemd service whose name can be seen in `systemdService`
|
||||
options below. You can then see the latest errors messages using `journalctl`.
|
||||
|
||||
## Tests {#ssl-block-tests}
|
||||
|
||||
This block is tested in [`/tests/vm/ssl.nix`](@REPO@/tests/vm/ssl.nix).
|
||||
|
||||
## Options Reference {#ssl-block-options}
|
||||
|
||||
```{=include=} options
|
||||
id-prefix: blocks-ssl-options-
|
||||
list-id: selfhostblocks-options
|
||||
source: @OPTIONS_JSON@
|
||||
```
|
4
modules/contracts/default.nix
Normal file
4
modules/contracts/default.nix
Normal file
|
@ -0,0 +1,4 @@
|
|||
{ lib }:
|
||||
{
|
||||
ssl = import ./ssl.nix { inherit lib; };
|
||||
}
|
58
modules/contracts/ssl.nix
Normal file
58
modules/contracts/ssl.nix
Normal file
|
@ -0,0 +1,58 @@
|
|||
{ lib }:
|
||||
rec {
|
||||
certs-paths = lib.types.submodule {
|
||||
freeformType = lib.types.anything;
|
||||
|
||||
options = {
|
||||
cert = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = "Path to the cert file.";
|
||||
};
|
||||
key = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
description = "Path to the key file.";
|
||||
};
|
||||
};
|
||||
};
|
||||
cas = lib.types.submodule {
|
||||
freeformType = lib.types.anything;
|
||||
|
||||
options = {
|
||||
paths = lib.mkOption {
|
||||
description = ''
|
||||
Paths where the files for the CA will be located.
|
||||
|
||||
This option is the contract output of the `shb.certs.cas` SSL block.
|
||||
'';
|
||||
type = certs-paths;
|
||||
};
|
||||
|
||||
systemdService = lib.mkOption {
|
||||
description = "Systemd oneshot service used to generate the CA.";
|
||||
type = lib.types.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
certs = lib.types.submodule {
|
||||
freeformType = lib.types.anything;
|
||||
|
||||
options = {
|
||||
paths = lib.mkOption {
|
||||
description = ''
|
||||
Paths where the files for the certificate will be located.
|
||||
|
||||
This option is the contract output of the `shb.certs.certs` SSL block.
|
||||
'';
|
||||
type = certs-paths;
|
||||
};
|
||||
|
||||
systemdService = lib.mkOption {
|
||||
description = ''
|
||||
Systemd oneshot service used to generate the certificate. The name must include the
|
||||
`.service` suffix.
|
||||
'';
|
||||
type = lib.types.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -3,6 +3,8 @@
|
|||
let
|
||||
cfg = config.shb.arr;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
|
||||
apps = {
|
||||
radarr = {
|
||||
defaultPort = 7001;
|
||||
|
@ -146,6 +148,12 @@ let
|
|||
example = "example.com";
|
||||
};
|
||||
|
||||
ssl = lib.mkOption {
|
||||
description = "Path to SSL files";
|
||||
type = lib.types.nullOr contracts.ssl.certs;
|
||||
default = null;
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
description = "Port on which ${name} listens to incoming requests.";
|
||||
|
@ -304,7 +312,7 @@ config.xml" templatedSettings) "${config.services.radarr.dataDir}/config.xml" (
|
|||
c = cfg.${name};
|
||||
in
|
||||
lib.mkIf (c.authEndpoint != null) {
|
||||
inherit (c) subdomain domain authEndpoint;
|
||||
inherit (c) subdomain domain authEndpoint ssl;
|
||||
upstream = "http://127.0.0.1:${toString c.port}";
|
||||
autheliaRules = [
|
||||
{
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
let
|
||||
cfg = config.shb.deluge;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
|
||||
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
||||
in
|
||||
{
|
||||
|
@ -21,6 +23,12 @@ in
|
|||
example = "mydomain.com";
|
||||
};
|
||||
|
||||
ssl = lib.mkOption {
|
||||
description = "Path to SSL files";
|
||||
type = lib.types.nullOr contracts.ssl.certs;
|
||||
default = null;
|
||||
};
|
||||
|
||||
daemonPort = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
description = "Deluge daemon port";
|
||||
|
@ -256,7 +264,7 @@ in
|
|||
|
||||
shb.nginx.autheliaProtect = lib.mkIf config.shb.authelia.enable [
|
||||
{
|
||||
inherit (cfg) subdomain domain authEndpoint;
|
||||
inherit (cfg) subdomain domain authEndpoint ssl;
|
||||
upstream = "http://127.0.0.1:${toString config.services.deluge.web.port}";
|
||||
autheliaRules = [{
|
||||
domain = fqdn;
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
let
|
||||
cfg = config.shb.hledger;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
|
||||
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
||||
in
|
||||
{
|
||||
|
@ -21,6 +23,12 @@ in
|
|||
example = "mydomain.com";
|
||||
};
|
||||
|
||||
ssl = lib.mkOption {
|
||||
description = "Path to SSL files";
|
||||
type = lib.types.nullOr contracts.ssl.certs;
|
||||
default = null;
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
description = "HLedger port";
|
||||
|
@ -74,7 +82,7 @@ in
|
|||
|
||||
shb.nginx.autheliaProtect = [
|
||||
{
|
||||
inherit (cfg) subdomain domain authEndpoint;
|
||||
inherit (cfg) subdomain domain authEndpoint ssl;
|
||||
upstream = "http://${toString config.services.hledger-web.host}:${toString config.services.hledger-web.port}";
|
||||
autheliaRules = [{
|
||||
domain = fqdn;
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
let
|
||||
cfg = config.shb.home-assistant;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
|
||||
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
||||
|
||||
ldap_auth_script_repo = pkgs.fetchFromGitHub {
|
||||
|
@ -33,6 +35,12 @@ in
|
|||
example = "mydomain.com";
|
||||
};
|
||||
|
||||
ssl = lib.mkOption {
|
||||
description = "Path to SSL files";
|
||||
type = lib.types.nullOr contracts.ssl.certs;
|
||||
default = null;
|
||||
};
|
||||
|
||||
ldap = lib.mkOption {
|
||||
description = ''
|
||||
LDAP Integration App. [Manual](https://docs.nextcloud.com/server/latest/admin_manual/configuration_user/user_auth_ldap.html)
|
||||
|
@ -193,10 +201,12 @@ in
|
|||
};
|
||||
|
||||
services.nginx.virtualHosts."${fqdn}" = {
|
||||
forceSSL = lib.mkIf config.shb.ssl.enable true;
|
||||
http2 = true;
|
||||
sslCertificate = lib.mkIf config.shb.ssl.enable "/var/lib/acme/${cfg.domain}/cert.pem";
|
||||
sslCertificateKey = lib.mkIf config.shb.ssl.enable "/var/lib/acme/${cfg.domain}/key.pem";
|
||||
|
||||
forceSSL = !(isNull cfg.ssl);
|
||||
sslCertificate = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.cert;
|
||||
sslCertificateKey = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.key;
|
||||
|
||||
extraConfig = ''
|
||||
proxy_buffering off;
|
||||
'';
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
let
|
||||
cfg = config.shb.jellyfin;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
|
||||
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
||||
|
||||
template = file: newPath: replacements:
|
||||
|
@ -33,6 +35,12 @@ in
|
|||
example = "domain.com";
|
||||
};
|
||||
|
||||
ssl = lib.mkOption {
|
||||
description = "Path to SSL files";
|
||||
type = lib.types.nullOr contracts.ssl.certs;
|
||||
default = null;
|
||||
};
|
||||
|
||||
ldapHost = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "host serving the LDAP server";
|
||||
|
@ -108,10 +116,12 @@ in
|
|||
|
||||
# Take advice from https://jellyfin.org/docs/general/networking/nginx/ and https://nixos.wiki/wiki/Plex
|
||||
services.nginx.virtualHosts."${fqdn}" = {
|
||||
forceSSL = true;
|
||||
forceSSL = !(isNull cfg.ssl);
|
||||
sslCertificate = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.cert;
|
||||
sslCertificateKey = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.key;
|
||||
|
||||
http2 = true;
|
||||
sslCertificate = "/var/lib/acme/${cfg.domain}/cert.pem";
|
||||
sslCertificateKey = "/var/lib/acme/${cfg.domain}/key.pem";
|
||||
|
||||
extraConfig = ''
|
||||
# The default `client_max_body_size` is 1M, this might not be enough for some posters, etc.
|
||||
client_max_body_size 20M;
|
||||
|
|
|
@ -5,6 +5,8 @@ let
|
|||
|
||||
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
|
||||
# Make sure to bump both nextcloudPkg and nextcloudApps at the same time.
|
||||
nextcloudPkg = pkgs.nextcloud27;
|
||||
nextcloudApps = pkgs.nextcloud27Packages.apps;
|
||||
|
@ -27,6 +29,12 @@ in
|
|||
example = "domain.com";
|
||||
};
|
||||
|
||||
ssl = lib.mkOption {
|
||||
description = "Path to SSL files";
|
||||
type = lib.types.nullOr contracts.ssl.certs;
|
||||
default = null;
|
||||
};
|
||||
|
||||
externalFqdn = lib.mkOption {
|
||||
description = "External fqdn used to access Nextcloud. Defaults to <subdomain>.<domain>. This should only be set if you include the port when accessing Nextcloud.";
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
|
@ -146,6 +154,12 @@ in
|
|||
default = "oo";
|
||||
};
|
||||
|
||||
ssl = lib.mkOption {
|
||||
description = "Path to SSL files";
|
||||
type = lib.types.nullOr contracts.ssl.certs;
|
||||
default = null;
|
||||
};
|
||||
|
||||
localNetworkIPRange = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Local network range, to restrict access to Open Office to only those IPs.";
|
||||
|
@ -363,14 +377,14 @@ in
|
|||
webfinger = true;
|
||||
|
||||
# Very important for a bunch of scripts to load correctly. Otherwise you get Content-Security-Policy errors. See https://docs.nextcloud.com/server/13/admin_manual/configuration_server/harden_server.html#enable-http-strict-transport-security
|
||||
https = config.shb.ssl.enable;
|
||||
https = !(isNull cfg.ssl);
|
||||
|
||||
extraApps = if isNull cfg.extraApps then {} else cfg.extraApps nextcloudApps;
|
||||
extraAppsEnable = true;
|
||||
appstoreEnable = true;
|
||||
|
||||
extraOptions = let
|
||||
protocol = if config.shb.ssl.enable then "https" else "http";
|
||||
protocol = if !(isNull cfg.ssl) then "https" else "http";
|
||||
in {
|
||||
"overwrite.cli.url" = "${protocol}://${fqdn}";
|
||||
"overwritehost" = if (isNull cfg.externalFqdn) then fqdn else cfg.externalFqdn;
|
||||
|
@ -382,6 +396,9 @@ in
|
|||
"overwritecondaddr" = ""; # We need to set it to empty otherwise overwriteprotocol does not work.
|
||||
"debug" = cfg.debug;
|
||||
"filelocking.debug" = cfg.debug;
|
||||
|
||||
# Use persistent SQL connections.
|
||||
"dbpersistent" = "true";
|
||||
};
|
||||
|
||||
phpOptions = {
|
||||
|
@ -396,7 +413,7 @@ in
|
|||
"opcache.max_accelerated_files" = "10000";
|
||||
"opcache.memory_consumption" = "128";
|
||||
"opcache.revalidate_freq" = "1";
|
||||
"openssl.cafile" = "/etc/ssl/certs/ca-certificates.crt";
|
||||
"openssl.cafile" = "/etc/ssl/certs/ca-certificates.cert";
|
||||
short_open_tag = "Off";
|
||||
|
||||
output_buffering = "Off";
|
||||
|
@ -424,9 +441,9 @@ in
|
|||
|
||||
services.nginx.virtualHosts.${fqdn} = {
|
||||
# listen = [ { addr = "0.0.0.0"; port = 443; } ];
|
||||
sslCertificate = lib.mkIf config.shb.ssl.enable "/var/lib/acme/${cfg.domain}/cert.pem";
|
||||
sslCertificateKey = lib.mkIf config.shb.ssl.enable "/var/lib/acme/${cfg.domain}/key.pem";
|
||||
forceSSL = lib.mkIf config.shb.ssl.enable true;
|
||||
forceSSL = !(isNull cfg.ssl);
|
||||
sslCertificate = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.cert;
|
||||
sslCertificateKey = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.key;
|
||||
|
||||
# From [1] this should fix downloading of big files. [2] seems to indicate that buffering
|
||||
# happens at multiple places anyway, so disabling one place should be okay.
|
||||
|
@ -491,9 +508,10 @@ in
|
|||
};
|
||||
|
||||
services.nginx.virtualHosts."${cfg.apps.onlyoffice.subdomain}.${cfg.domain}" = {
|
||||
sslCertificate = lib.mkIf config.shb.ssl.enable "/var/lib/acme/${cfg.domain}/cert.pem";
|
||||
sslCertificateKey = lib.mkIf config.shb.ssl.enable "/var/lib/acme/${cfg.domain}/key.pem";
|
||||
forceSSL = lib.mkIf config.shb.ssl.enable true;
|
||||
forceSSL = !(isNull cfg.ssl);
|
||||
sslCertificate = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.cert;
|
||||
sslCertificateKey = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.key;
|
||||
|
||||
locations."/" = {
|
||||
extraConfig = ''
|
||||
allow ${cfg.apps.onlyoffice.localNetworkIPRange};
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
let
|
||||
cfg = config.shb.vaultwarden;
|
||||
|
||||
contracts = pkgs.callPackage ../contracts {};
|
||||
|
||||
fqdn = "${cfg.subdomain}.${cfg.domain}";
|
||||
|
||||
template = file: newPath: replacements:
|
||||
|
@ -33,6 +35,12 @@ in
|
|||
example = "mydomain.com";
|
||||
};
|
||||
|
||||
ssl = lib.mkOption {
|
||||
description = "Path to SSL files";
|
||||
type = lib.types.nullOr contracts.ssl.certs;
|
||||
default = null;
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
description = "Port on which vaultwarden service listens.";
|
||||
|
@ -164,7 +172,7 @@ in
|
|||
|
||||
shb.nginx.autheliaProtect = [
|
||||
{
|
||||
inherit (cfg) subdomain domain authEndpoint;
|
||||
inherit (cfg) subdomain domain authEndpoint ssl;
|
||||
upstream = "http://127.0.0.1:${toString config.services.vaultwarden.config.ROCKET_PORT}";
|
||||
autheliaRules = [
|
||||
{
|
||||
|
|
|
@ -89,6 +89,7 @@ in
|
|||
authEndpoint = "https://oidc.example.com";
|
||||
subdomain = "radarr";
|
||||
upstream = "http://127.0.0.1:7001";
|
||||
ssl = null;
|
||||
}
|
||||
];
|
||||
users.users.radarr.extraGroups = [ "media" ];
|
||||
|
@ -162,6 +163,7 @@ in
|
|||
authEndpoint = "https://oidc.example.com";
|
||||
subdomain = "radarr";
|
||||
upstream = "http://127.0.0.1:7001";
|
||||
ssl = null;
|
||||
}
|
||||
];
|
||||
users.users.radarr.extraGroups = [ "media" ];
|
||||
|
|
|
@ -18,9 +18,11 @@ let
|
|||
services = anyOpt {};
|
||||
shb.authelia = anyOpt {};
|
||||
shb.backup = anyOpt {};
|
||||
shb.ssl = anyOpt {};
|
||||
systemd = anyOpt {};
|
||||
users = anyOpt {};
|
||||
};
|
||||
}
|
||||
../../modules/blocks/ssl.nix
|
||||
../../modules/blocks/nginx.nix
|
||||
m
|
||||
];
|
||||
|
@ -46,36 +48,32 @@ in
|
|||
|
||||
testAuth = {
|
||||
expected = {
|
||||
shb.backup = {};
|
||||
shb.nginx = {
|
||||
accessLog = false;
|
||||
autheliaProtect = [{
|
||||
authEndpoint = "hello";
|
||||
autheliaRules = [{}];
|
||||
subdomain = "my";
|
||||
domain = "example.com";
|
||||
upstream = "http://127.0.0.1:1234";
|
||||
}];
|
||||
debugLog = false;
|
||||
};
|
||||
services.nginx.enable = true;
|
||||
services.nginx.virtualHosts."my.example.com" = {
|
||||
nginx.enable = true;
|
||||
nginx.virtualHosts."my.example.com" = {
|
||||
forceSSL = true;
|
||||
locations."/" = {};
|
||||
locations."/authelia" = {};
|
||||
sslCertificate = "/var/lib/acme/example.com/cert.pem";
|
||||
sslCertificateKey = "/var/lib/acme/example.com/key.pem";
|
||||
sslCertificate = "/var/lib/certs/selfsigned/example.com.cert";
|
||||
sslCertificateKey = "/var/lib/certs/selfsigned/example.com.key";
|
||||
};
|
||||
};
|
||||
expr = testConfig {
|
||||
shb.ssl.enable = true;
|
||||
expr = (testConfig ({ config, ... }: {
|
||||
shb.certs.cas.selfsigned.myca = {};
|
||||
|
||||
shb.certs.certs.selfsigned."example.com" = {
|
||||
ca = config.shb.certs.cas.selfsigned.myca;
|
||||
|
||||
domain = "example.com";
|
||||
};
|
||||
|
||||
shb.nginx.autheliaProtect = [{
|
||||
subdomain = "my";
|
||||
domain = "example.com";
|
||||
ssl = config.shb.certs.certs.selfsigned."example.com";
|
||||
upstream = "http://127.0.0.1:1234";
|
||||
authEndpoint = "hello";
|
||||
autheliaRules = [{}];
|
||||
}];
|
||||
};
|
||||
})).services;
|
||||
};
|
||||
}
|
||||
|
|
76
test/vm/ssl.nix
Normal file
76
test/vm/ssl.nix
Normal file
|
@ -0,0 +1,76 @@
|
|||
{ pkgs, lib, ... }:
|
||||
{
|
||||
test = pkgs.nixosTest {
|
||||
name = "ssl-test";
|
||||
|
||||
nodes.server = { config, pkgs, ... }: {
|
||||
imports = [
|
||||
../../modules/blocks/ssl.nix
|
||||
];
|
||||
|
||||
shb.certs = {
|
||||
cas.selfsigned = {
|
||||
myca = {
|
||||
name = "My CA";
|
||||
};
|
||||
myotherca = {
|
||||
name = "My Other CA";
|
||||
};
|
||||
};
|
||||
certs.selfsigned = {
|
||||
mycert = {
|
||||
ca = config.shb.certs.cas.selfsigned.myca;
|
||||
|
||||
domain = "example.com";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# The configuration below is to create a webserver that uses the server certificate.
|
||||
networking.hosts."127.0.0.1" = [ "example.com" ];
|
||||
|
||||
services.nginx.enable = true;
|
||||
services.nginx.virtualHosts."example.com" =
|
||||
{
|
||||
onlySSL = true;
|
||||
sslCertificate = config.shb.certs.certs.selfsigned.mycert.paths.cert;
|
||||
sslCertificateKey = config.shb.certs.certs.selfsigned.mycert.paths.key;
|
||||
locations."/".extraConfig = ''
|
||||
add_header Content-Type text/plain;
|
||||
return 200 'It works!';
|
||||
'';
|
||||
};
|
||||
systemd.services.nginx = {
|
||||
after = [ config.shb.certs.certs.selfsigned.mycert.systemdService ];
|
||||
requires = [ config.shb.certs.certs.selfsigned.mycert.systemdService ];
|
||||
};
|
||||
};
|
||||
|
||||
# Taken from https://github.com/NixOS/nixpkgs/blob/7f311dd9226bbd568a43632c977f4992cfb2b5c8/nixos/tests/custom-ca.nix
|
||||
testScript = { nodes, ... }:
|
||||
let
|
||||
myca = nodes.server.shb.certs.cas.selfsigned.myca;
|
||||
myotherca = nodes.server.shb.certs.cas.selfsigned.myotherca;
|
||||
mycert = nodes.server.shb.certs.certs.selfsigned.mycert;
|
||||
in
|
||||
''
|
||||
start_all()
|
||||
|
||||
# Make sure certs are generated.
|
||||
server.wait_for_file("${myca.paths.key}")
|
||||
server.wait_for_file("${myca.paths.cert}")
|
||||
server.wait_for_file("${myotherca.paths.key}")
|
||||
server.wait_for_file("${myotherca.paths.cert}")
|
||||
server.wait_for_file("${mycert.paths.key}")
|
||||
server.wait_for_file("${mycert.paths.cert}")
|
||||
|
||||
# Wait for jkkk
|
||||
server.require_unit_state("${nodes.server.shb.certs.systemdService}", "inactive")
|
||||
|
||||
with subtest("Certificate is trusted in curl"):
|
||||
machine.wait_for_unit("nginx")
|
||||
machine.wait_for_open_port(443)
|
||||
machine.succeed("curl --fail-with-body -v https://example.com")
|
||||
'';
|
||||
};
|
||||
}
|
Loading…
Reference in a new issue