Skip to content

Commit

Permalink
Nginx: JSON config and reload production-ready
Browse files Browse the repository at this point in the history
* make emailACME optional in json config
* enableACME is activated automatically if one of forceSSL, onlySSL or addSSL
  is set to true.
* JSON config can be now be split into multiple files
  * merge all snippets into a single set
  * prevents and reports duplicate vhost names
* use nginx reload code from upstream PR NixOS/nixpkgs#24476
  * our reload code is superseded by the changes in nixpkgs
  * reloading on fc-manage now works for json config
  * changes to *.conf are picked up after systemctl reload nginx
* add nginx config helper commands
  * nginx-show-config shows the currently activated nginx config
  * nginx-check-config runs nginx -t for the current config
* update readme, json config is preferred now
* include modified nginx test from nixpkgs

Case 110209
  • Loading branch information
dpausp committed Jul 30, 2019
1 parent 975ce32 commit cc2b52d
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 68 deletions.
19 changes: 19 additions & 0 deletions nixos/lib/attrsets.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{ lib, ...}:

with lib;

rec {
# Returns a list of attribute names that appear in more than one attrset.
# Useful for checking if merging the attrsets would overwrite a previous value.
#
# duplicateAttrNames [ { a = 1; } { b = 2; } { a = 3; } ]
# => [ "a" ]
duplicateAttrNames = listOfAttrs:
attrNames
(filterAttrs
(n: a: a > 1)
(foldAttrs
(n: acc: acc + 1)
0
listOfAttrs));
}
7 changes: 4 additions & 3 deletions nixos/lib/default.nix
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{ config, pkgs, lib, ... }:

let
files = import ./files.nix { inherit pkgs lib; };
attrsets = import ./attrsets.nix { inherit config lib; };
files = import ./files.nix { inherit config pkgs lib; };
math = import ./math.nix { inherit pkgs lib; };
network = import ./network.nix { inherit config pkgs lib; };
system = import ./system.nix { inherit config pkgs lib; };
Expand All @@ -19,7 +20,7 @@ in

config = {
fclib =
{ inherit files math network system utils; }
// files // math // network // system // utils;
{ inherit attrsets files math network system utils; }
// attrsets // files // math // network // system // utils;
};
}
33 changes: 32 additions & 1 deletion nixos/lib/files.nix
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
{ lib, ... }:
{ config, lib, ... }:

let
fclib = config.fclib;
in

with lib;

Expand Down Expand Up @@ -26,4 +30,31 @@ rec {
jsonFromFile = file: default:
builtins.fromJSON (configFromFile file default);

# Reads JSON config snippets from a directory and merges them into one object.
# Each snippet must contain a single top-level object.
# The keys in the top-level objects must be unique for all snippets.
# Throws an error if duplicate keys are found.
jsonFromDir = path: let
objects =
map
(filename: builtins.fromJSON (readFile filename))
(filter
(filename: hasSuffix "json" filename)
(files path));

mergedObject =
fold
(obj: acc: acc // obj)
{}
objects;

duplicates = fclib.duplicateAttrNames objects;
in
if duplicates == []
then mergedObject
else throw ''
Top-level JSON config keys are not unique in ${path}!
Duplicate keys: ${concatStringsSep ", " duplicates}
''
;
}
71 changes: 44 additions & 27 deletions nixos/services/nginx/README.txt
Original file line number Diff line number Diff line change
@@ -1,38 +1,30 @@
Nginx is enabled on this machine.

Manual configuration
--------------------

Put your site configuration into this directory as `*.conf`. You may
add other files, like SSL keys, as well.

If you want to authenticate against the Flying Circus users with login permission,
use the following snippet, and *USE SSL*:

auth_basic "FCIO user";
auth_basic_user_file "/etc/local/nginx/htpasswd_fcio_users";

There is also an `example-configuration` here. Copy to some file ending with
*.conf and adapt.

Changes to *.conf will cause nginx only to reload, not to restart, on the next
nixos-rebuild run.
We provide basic config. You can use two ways to add custom configuration.

The combined nginx config file can be shown with: `nginx-show-config`

Structured configuration
------------------------

Alternatively, you can place virtual host definition in
`/etc/local/nginx/vhosts.json` which resembles NixOS nginx virtualHosts options
You can place virtual host definitions in `/etc/local/nginx/*.json`
that are managed by NixOS. They are built into the combined config file by fc-manage.

Changes to these files will cause nginx to reload without downtime on the next
fc-manage run if the config is valid. It will display a warning if invalid
settings are found in the nginx config.

Example vhosts.json containing a virtual with static root directory, a bit of
custom configuration, and Let's encrypt SSL:
The config snippets must contain a single JSON object that defines one or
more virtual hosts. Multiple JSON config files will be merged into one object.
It's a good idea to use one config file per virtual host.

example.json containing a virtual with static root directory, a bit of
custom configuration, and SSL with a default certificate from Let's Encrypt:

```
{
"www.example.org": {
"serverAliases": ["example.org"],
"acmeEmail"
"forceSSL": true,
"root": "/srv/webroot",
"extraConfig": "add_header Strict-Transport-Security max-age=31536000; rewrite ^/old_url /new_url redirect;",
Expand All @@ -45,9 +37,34 @@ custom configuration, and Let's encrypt SSL:
}
```

All options are documented in
https://nixos.org/nixos/options.html#services.nginx.virtualhosts. Note that an
non-standard attribute "acmeEmail" must be set to a contact mail address
in order to activate Let's encrypt.
All options are documented at
https://nixos.org/nixos/options.html#services.nginx.virtualhosts.%3Cname%3E

For SSL support with redirection from HTTP to HTTPS, use `forceSSL`.
Let's Encrypt (`enableACME`) is activated automatically if one of `forceSSL`, `onlySSL` or `addSSL`
is set to true.
To use a custom certificate, set the certificate options and set `"enableACME" = false`.

We support the custom option `emailACME` to set the contact address for Let's Encrypt.

Manual configuration
--------------------

You can also use plain nginx config files /etc/local/nginx/*.conf for configuration.
These config files are not managed by NixOS. You are on your own.

You may add other files to the directory, like SSL keys, as well.

If you want to authenticate against the Flying Circus users with login permission,
use the following snippet, and *USE SSL*:

auth_basic "FCIO user";
auth_basic_user_file "/etc/local/nginx/htpasswd_fcio_users";

There is also an `example-configuration` here. Copy to some file ending with
*.conf and adapt.

You can check if the config is valid with: `nginx-check-config`

Changes to *.json will cause nginx to restart on the next nixos-rebuild run.
Changes to *.conf will be picked up after a service reload: `systemctl reload nginx`
Nginx will not reload if config errors are present.
57 changes: 22 additions & 35 deletions nixos/services/nginx/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,21 @@ let
package = config.services.nginx.package;
currentConf = "/etc/current-config/nginx.conf";

vhostsJSON = fclib.jsonFromFile "/etc/local/nginx/vhosts.json" "{}";
vhostsJSON = fclib.jsonFromDir "/etc/local/nginx";

virtualHosts = lib.mapAttrs (
_: val:
(lib.optionalAttrs
((val ? addSSL || val ? onlySSL || val ? forceSSL) && val ? acmeEmail)
{ enableACME = true; }) // removeAttrs val [ "acmeEmail" ])
((val ? addSSL || val ? onlySSL || val ? forceSSL))
{ enableACME = true; }) // removeAttrs val [ "emailACME" ])
vhostsJSON;

acmeEmails =
lib.mapAttrs (name: val: { email = val.acmeEmail; })
(lib.filterAttrs (_: val: val ? acmeEmail ) vhostsJSON);
# only email setting supported at the moment
acmeSettings =
lib.mapAttrs (name: val: { email = val.emailACME; })
(lib.filterAttrs (_: val: val ? emailACME ) vhostsJSON);

localConfig =
lib.optionalString (pathExists "/etc/local/nginx") "${/etc/local/nginx}";
acmeVhosts = (lib.filterAttrs (_: val: val ? enableACME ) vhostsJSON);

baseConfig = ''
worker_processes ${toString (fclib.current_cores config 1)};
Expand Down Expand Up @@ -169,11 +170,11 @@ in
notification = "HTTPS cert for ${name} (Let's encrypt)";
command = "check_http -H ${name} -p 443 -S -C 5";
interval = 600;
})) acmeEmails);
})) acmeVhosts);

networking.firewall.allowedTCPPorts = [ 80 443 ];

security.acme.certs = acmeEmails;
security.acme.certs = acmeSettings;

services.nginx = {
enable = true;
Expand Down Expand Up @@ -209,35 +210,21 @@ in
"d /etc/local/nginx/modsecurity 2775 nginx service"
];

flyingcircus.activationScripts = {

nginx-reload = lib.stringAfter [ "etc" ] ''
if [[ ! -e ${stateDir}/reload.conf ]]; then
install -D /dev/null ${stateDir}/reload.conf
fi
# reload not possible when structured vhosts have changed -> skip
if [[ -n "$(find -L /etc/local/nginx -type f -name '*.json' \
-newer ${stateDir}/reload.conf)" ]]; then
echo -n "${localConfig}" > ${stateDir}/reload.conf
# check if local config has changed
elif [[ "$(< ${stateDir}/reload.conf )" != "${localConfig}" ]]; then
echo "nginx: config change detected, reloading"
if ${package}/bin/nginx -t -c ${currentConf} -p ${stateDir}; then
${pkgs.systemd}/bin/systemctl reload nginx.service || true
echo -n "${localConfig}" > ${stateDir}/reload.conf
else
echo "nginx: not reloading due to error"
false
fi
fi
'';
};

flyingcircus.localConfigDirs.nginx = {
dir = "/etc/local/nginx";
user = "nginx";
};


environment.systemPackages = [
(pkgs.writeScriptBin "nginx-check-config" ''
$(systemctl cat nginx | grep "X-CheckConfigCmd" | cut -d= -f2)
'')

(pkgs.writeScriptBin "nginx-show-config" ''
cat $(systemctl cat nginx | grep "X-ConfigFile" | cut -d= -f2)
'')
];

})

{
Expand Down
1 change: 1 addition & 0 deletions tests/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ in {
mongodb34 = callTest ./mongodb.nix { rolename = "mongodb34"; };
network = callSubTests ./network {};
nginx = callTest ./nginx.nix {};
nginx_reload = callTest (nixpkgs + /nixos/tests/nginx.nix) {};
openvpn = callTest ./openvpn.nix {};
postgresql95 = callTest ./postgresql.nix { rolename = "postgresql95"; };
postgresql96 = callTest ./postgresql.nix { rolename = "postgresql96"; };
Expand Down
4 changes: 2 additions & 2 deletions versions.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"nixpkgs": {
"owner": "flyingcircusio",
"repo": "nixpkgs",
"rev": "fd0aff0181edef0bad830af8e70f664e85838f19",
"sha256": "0j25wbr00hzqvv3pnji1qif3w6f8wqmp7lw8a9ih5xxrcwzf1sgw"
"rev": "9bc51a466db15db1d791a066981538d556f135b8",
"sha256": "0hz5bpihckghjdq30bcwxwb5x7i41gpaywi8gbhq17v7xv7h2cjh"
},
"nixos-18.09": {
"owner": "nixos",
Expand Down

0 comments on commit cc2b52d

Please sign in to comment.