about summary refs log tree commit diff
path: root/nixpkgs/nixos/modules/services/networking/lokinet.nix
blob: 8f64d3f0119f90cacd4de7a74635872ec1cf9ab1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
{ config, lib, pkgs, ... }:

let
  cfg = config.services.lokinet;
  dataDir = "/var/lib/lokinet";
  settingsFormat = pkgs.formats.ini { listsAsDuplicateKeys = true; };
  configFile = settingsFormat.generate "lokinet.ini" (lib.filterAttrsRecursive (n: v: v != null) cfg.settings);
in with lib; {
  options.services.lokinet = {
    enable = mkEnableOption (lib.mdDoc "Lokinet daemon");

    package = mkPackageOption pkgs "lokinet" { };

    useLocally = mkOption {
      type = types.bool;
      default = false;
      example = true;
      description = lib.mdDoc "Whether to use Lokinet locally.";
    };

    settings = mkOption {
      type = with types;
        submodule {
          freeformType = settingsFormat.type;

          options = {
            dns = {
              bind = mkOption {
                type = str;
                default = "127.3.2.1";
                description = lib.mdDoc "Address to bind to for handling DNS requests.";
              };

              upstream = mkOption {
                type = listOf str;
                default = [ "9.9.9.10" ];
                example = [ "1.1.1.1" "8.8.8.8" ];
                description = lib.mdDoc ''
                  Upstream resolver(s) to use as fallback for non-loki addresses.
                  Multiple values accepted.
                '';
              };
            };

            network = {
              exit = mkOption {
                type = bool;
                default = false;
                description = lib.mdDoc ''
                  Whether to act as an exit node. Beware that this
                  increases demand on the server and may pose liability concerns.
                  Enable at your own risk.
                '';
              };

              exit-node = mkOption {
                type = nullOr (listOf str);
                default = null;
                example = ''
                  exit-node = [ "example.loki" ];              # maps all exit traffic to example.loki
                  exit-node = [ "example.loki:100.0.0.0/24" ]; # maps 100.0.0.0/24 to example.loki
                '';
                description = lib.mdDoc ''
                  Specify a `.loki` address and an optional ip range to use as an exit broker.
                  See <http://probably.loki/wiki/index.php?title=Exit_Nodes> for
                  a list of exit nodes.
                '';
              };

              keyfile = mkOption {
                type = nullOr str;
                default = null;
                example = "snappkey.private";
                description = lib.mdDoc ''
                  The private key to persist address with. If not specified the address will be ephemeral.
                  This keyfile is generated automatically if the specified file doesn't exist.
                '';
              };
            };
          };
        };
      default = { };
      example = literalExpression ''
        {
          dns = {
            bind = "127.3.2.1";
            upstream = [ "1.1.1.1" "8.8.8.8" ];
          };

          network.exit-node = [ "example.loki" "example2.loki" ];
        }
      '';
      description = lib.mdDoc ''
        Configuration for Lokinet.
        Currently, the best way to view the available settings is by
        generating a config file using `lokinet -g`.
      '';
    };
  };

  config = mkIf cfg.enable {
    networking.resolvconf.extraConfig = mkIf cfg.useLocally ''
      name_servers="${cfg.settings.dns.bind}"
    '';

    systemd.services.lokinet = {
      description = "Lokinet";
      after = [ "network-online.target" "network.target" ];
      wants = [ "network-online.target" "network.target" ];
      wantedBy = [ "multi-user.target" ];

      preStart = ''
        ln -sf ${cfg.package}/share/bootstrap.signed ${dataDir}
        ${pkgs.coreutils}/bin/install -m 600 ${configFile} ${dataDir}/lokinet.ini

        ${optionalString (cfg.settings.network.keyfile != null) ''
          ${pkgs.crudini}/bin/crudini --set ${dataDir}/lokinet.ini network keyfile "${dataDir}/${cfg.settings.network.keyfile}"
        ''}
      '';

      serviceConfig = {
        DynamicUser = true;
        StateDirectory = "lokinet";
        AmbientCapabilities = [ "CAP_NET_ADMIN" "CAP_NET_BIND_SERVICE" ];
        ExecStart = "${cfg.package}/bin/lokinet ${dataDir}/lokinet.ini";
        Restart = "always";
        RestartSec = "5s";

        # hardening
        LockPersonality = true;
        MemoryDenyWriteExecute = true;
        NoNewPrivileges = true;
        PrivateTmp = true;
        PrivateMounts = true;
        ProtectControlGroups = true;
        ProtectHome = true;
        ProtectHostname = true;
        ProtectKernelLogs = true;
        ProtectKernelModules = true;
        ProtectKernelTunables = true;
        ProtectSystem = "strict";
        ReadWritePaths = "/dev/net/tun";
        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
        RestrictNamespaces = true;
        RestrictRealtime = true;
        RestrictSUIDSGID = true;
      };
    };

    environment.systemPackages = [ cfg.package ];
  };
}