system services on nixos

10 oct 2011

#how to integrate a daemon into nixos

in this short article i want to show how services can be added/used in nixos. i personally find the nixos way a quite easy and ‘clean’ approach!

as i integrated cntlm my goals were:

  • easy to use
  • password must be stored in a safe place
  • service should not be run as root but as a special user: cntlm

it helped a lot to look at similar scripts like the sshd integration but i also used “NixOS: A Purely functional Linux Distribution” [1] which is describing most aspects which are needed to get a service up and running.

seen as a developer one has to write two nix expressions:

  • cntlm/default.nix the script which describes the software cntlm
  • nixos/modules/services/networking/cntlm.nix the script which descirbes the service

seen as a user one has to modify only one nix expression:

  • /etc/nixos/configuration.nix the place where all the configuration happens

/etc/nixos/nixpkgs/pkgs/tools/networking/cntlm/default.nix

this nix expression describes the cntlm software and is quite simple as it basically fetches the software/compiles it/installs it into the system. however, a user does not have to install this software using:

# nix-env -i cntlm

still this would be possible, and a normal user/root user could use the software in his profile this way.

anyway here is the script:

     1  { stdenv, fetchurl, which}:
     2
     3  stdenv.mkDerivation {
     4    name = "cntlm-0.35.1";
     5
     6    src = fetchurl {
     7      url = mirror://sourceforge/cntlm/cntlm-0.35.1.tar.gz;
     8      sha256 = "7b3fb7184e72cc3f1743bb8e503a5305e96458bc630a7e1ebfc9f3c07ffa6c5e";
     9    };
    10
    11    buildInputs = [ which ];
    12
    13    installPhase = ''
    14      ensureDir $out/bin; cp cntlm $out/bin/;
    15      ensureDir $out/share/; cp COPYRIGHT README VERSION doc/cntlm.conf $out/share/;
    16      ensureDir $out/man/; cp doc/cntlm.1 $out/man/;
    17    '';
    18
    19    meta = {
    20      description = "Cntlm is an NTLM/NTLMv2 authenticating HTTP proxy";
    21      homepage = http://cntlm.sourceforge.net/;
    22      license = stdenv.lib.licenses.gpl2;
    23      maintainers = [ stdenv.lib.maintainers.qknight ];
    24    };
    25  }

the only point of interest might be the buildInputs (line 11) which includes which. in this build script ‘which gcc’ is used to test if gcc is installed.

/etc/nixos/nixos/modules/services/networking/cntlm.nix

this expression is used to integrate cntlm as a system service.

     1  { config, pkgs, ... }:
     2
     3  with pkgs.lib;
     4
     5  let
     6
     7    cfg = config.services.cntlm;
     8    uid = config.ids.uids.cntlm;
     9
    10  in
    11
    12  {
    13
    14    options = {
    15
    16      services.cntlm= {
    17
    18        enable = mkOption {
    19          default = false;
    20          description = ''
    21            Whether to enable the cntlm, which start a local proxy.
    22          '';
    23        };
    24
    25        username = mkOption {
    26          description = ''
    27            Proxy account name, without the possibility to include domain name ('at' sign is interpreted literally).
    28          '';
    29        };
    30
    31        domain = mkOption {
    32          description = ''Proxy account domain/workgroup name.'';
    33        };
    34
    35        password = mkOption {
    36          default = "/etc/cntlm.password";
    37          type = with pkgs.lib.types; string;
    38          description = ''Proxy account password. Note: use chmod 0600 on /etc/cntlm.password for security.'';
    39        };
    40
    41        netbios_hostname = mkOption {
    42          default = config.networking.hostName;
    43          description = ''
    44            The hostname of your workstation.
    45          '';
    46        };
    47
    48        proxy = mkOption {
    49          description = ''
    50            A list of NTLM/NTLMv2 authenticating HTTP proxies.
    51
    52            Parent proxy, which requires authentication. The same as proxy on the command-line, can be used more than  once  to  specify  unlimited
    53            number  of  proxies.  Should  one proxy fail, cntlm automatically moves on to the next one. The connect request fails only if the whole
    54            list of proxies is scanned and (for each request) and found to be invalid. Command-line takes precedence over the configuration file.
    55          '';
    56        };
    57
    58        port = mkOption {
    59          default = [3128];
    60          description = "Specifies on which ports the cntlm daemon listens.";
    61        };
    62
    63       extraConfig = mkOption {
    64          default = "";
    65          description = "Verbatim contents of cntlm.conf.";
    66       };
    67
    68      };
    69
    70    };
    71
    72
    73    ###### implementation
    74
    75    config = mkIf config.services.cntlm.enable {
    76      users.extraUsers = singleton {
    77          name = "cntlm";
    78          description = "cntlm system-wide daemon";
    79          home = "/var/empty";
    80      };
    81
    82      jobs.cntlm = {
    83          description = "cntlm is an NTLM / NTLM Session Response / NTLMv2 authenticating HTTP proxy.";
    84          startOn = "started network-interfaces";
    85          environment = {
    86          };
    87
    88      preStart = '' '';
    89
    90      daemonType = "fork";
    91
    92      exec =
    93        ''
    94          ${pkgs.cntlm}/bin/cntlm -U cntlm \
    95          -c ${pkgs.writeText "cntlm_config" cfg.extraConfig}
    96        '';
    97      };
    98
    99      services.cntlm.extraConfig =
   100        ''
   101          # Cntlm Authentication Proxy Configuration
   102          Username        ${cfg.username}
   103          Domain          ${cfg.domain}
   104          Password        ${cfg.password}
   105          Workstation     ${cfg.netbios_hostname}
   106          ${concatMapStrings (entry: "Proxy ${entry}\n") cfg.proxy}
   107
   108          ${concatMapStrings (port: ''
   109            Listen ${toString port}
   110          '') cfg.port}
   111        '';
   112    };
   113  }

notable parts are:

  • (line 1-13) if interested in general nix language: read [1] page 6,7 (and 5 might also be interesting)
  • (line 18-66) where a** list of options is declared** (some with default arguments; some without default argument which will enforce the user to set them)
  • (line 76-79) where the service is started as a different user (security measure)
  • (line 92-97) where the configuration is generated on the fly (yes cntlm.conf is generated everytime the configuration in /etc/nixos/configuration.nix is changed). a user using the cntlm service on nixos does not change the cntlm.conf manually)
  • (line 99-111) where a minimal configuration (extract of the example cntlm.conf) is parameterized.
  • (line 106) where a list of items is transformed into a multi line structure where:

cfg.proxy = [ “foo” “bar” “baz” ];

is transformed into:

  • Proxy foo
  • Proxy bar
  • Proxy baz

how to make use of the above expressions

a user has to append this configuration into /etc/nixos/configuration.nix and cntlm will be installed/configured and started

  services.cntlm = {
    enable=true;
    username="myusername";
    domain="mydomain";
    proxy=[ "192.168.3.5:1234" ];
  };

summary

in contrast to most other distributions nixos makes not only packaging subject to a ‘clean’ package management but also configuration management (/etc stuff) and runtime management. this is a very clean design helping to avoid lots of pitfalls.

article source