at master 6.6 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8with lib; 9let 10 cfg = config.services.tayga; 11 12 # Converts an address set to a string 13 strAddr = addr: "${addr.address}/${toString addr.prefixLength}"; 14 15 configFile = pkgs.writeText "tayga.conf" '' 16 tun-device ${cfg.tunDevice} 17 18 ipv4-addr ${cfg.ipv4.address} 19 ${optionalString (cfg.ipv6.address != null) "ipv6-addr ${cfg.ipv6.address}"} 20 21 prefix ${strAddr cfg.ipv6.pool} 22 dynamic-pool ${strAddr cfg.ipv4.pool} 23 data-dir ${cfg.dataDir} 24 25 ${concatStringsSep "\n" (mapAttrsToList (ipv4: ipv6: "map " + ipv4 + " " + ipv6) cfg.mappings)} 26 27 ${optionalString ((builtins.length cfg.log) > 0) '' 28 log ${concatStringsSep " " cfg.log} 29 ''} 30 31 wkpf-strict ${if cfg.wkpfStrict then "yes" else "no"} 32 ''; 33 34 addrOpts = 35 v: 36 assert v == 4 || v == 6; 37 { 38 options = { 39 address = mkOption { 40 type = types.str; 41 description = "IPv${toString v} address."; 42 }; 43 44 prefixLength = mkOption { 45 type = types.ints.between 0 (if v == 4 then 32 else 128); 46 description = '' 47 Subnet mask of the interface, specified as the number of 48 bits in the prefix ("${if v == 4 then "24" else "64"}"). 49 ''; 50 }; 51 }; 52 }; 53 54 versionOpts = v: { 55 options = { 56 router = { 57 address = mkOption { 58 type = types.str; 59 description = "The IPv${toString v} address of the router."; 60 }; 61 }; 62 63 address = mkOption { 64 type = types.nullOr types.str; 65 default = null; 66 description = "The source IPv${toString v} address of the TAYGA server."; 67 }; 68 69 pool = mkOption { 70 type = with types; nullOr (submodule (addrOpts v)); 71 description = "The pool of IPv${toString v} addresses which are used for translation."; 72 }; 73 }; 74 }; 75in 76{ 77 options = { 78 services.tayga = { 79 enable = mkEnableOption "Tayga"; 80 81 package = mkPackageOption pkgs "tayga" { }; 82 83 ipv4 = mkOption { 84 type = types.submodule (versionOpts 4); 85 description = "IPv4-specific configuration."; 86 example = literalExpression '' 87 { 88 address = "192.0.2.0"; 89 router = { 90 address = "192.0.2.1"; 91 }; 92 pool = { 93 address = "192.0.2.1"; 94 prefixLength = 24; 95 }; 96 } 97 ''; 98 }; 99 100 ipv6 = mkOption { 101 type = types.submodule (versionOpts 6); 102 description = "IPv6-specific configuration."; 103 example = literalExpression '' 104 { 105 address = "2001:db8::1"; 106 router = { 107 address = "64:ff9b::1"; 108 }; 109 pool = { 110 address = "64:ff9b::"; 111 prefixLength = 96; 112 }; 113 } 114 ''; 115 }; 116 117 dataDir = mkOption { 118 type = types.path; 119 default = "/var/lib/tayga"; 120 description = "Directory for persistent data."; 121 }; 122 123 tunDevice = mkOption { 124 type = types.str; 125 default = "nat64"; 126 description = "Name of the nat64 tun device."; 127 }; 128 129 mappings = mkOption { 130 type = types.attrsOf types.str; 131 default = { }; 132 description = "Static IPv4 -> IPv6 host mappings."; 133 example = literalExpression '' 134 { 135 "192.168.5.42" = "2001:db8:1:4444::1"; 136 "192.168.5.43" = "2001:db8:1:4444::2"; 137 "192.168.255.2" = "2001:db8:1:569::143"; 138 } 139 ''; 140 }; 141 142 log = mkOption { 143 type = types.listOf types.str; 144 default = [ ]; 145 description = "Packet errors to log (drop, reject, icmp, self)"; 146 example = literalExpression '' 147 [ "drop" "reject" "icmp" "self" ] 148 ''; 149 }; 150 151 wkpfStrict = mkOption { 152 type = types.bool; 153 default = true; 154 description = "Enable restrictions on the use of the well-known prefix (64:ff9b::/96) - prevents translation of non-global IPv4 ranges when using the well-known prefix. Must be enabled for RFC 6052 compatibility."; 155 }; 156 }; 157 }; 158 159 config = mkIf cfg.enable { 160 assertions = [ 161 { 162 assertion = allUnique (attrValues cfg.mappings); 163 message = "Neither the IPv4 nor the IPv6 addresses must be entered twice in the mappings."; 164 } 165 ]; 166 167 networking.interfaces."${cfg.tunDevice}" = { 168 virtual = true; 169 virtualType = "tun"; 170 virtualOwner = mkIf config.networking.useNetworkd ""; 171 ipv4 = { 172 addresses = [ 173 { 174 address = cfg.ipv4.router.address; 175 prefixLength = 32; 176 } 177 ]; 178 routes = [ 179 cfg.ipv4.pool 180 ]; 181 }; 182 ipv6 = { 183 addresses = [ 184 { 185 address = cfg.ipv6.router.address; 186 prefixLength = 128; 187 } 188 ]; 189 routes = [ 190 cfg.ipv6.pool 191 ]; 192 }; 193 }; 194 195 environment.etc."tayga.conf".source = configFile; 196 197 systemd.services.tayga = { 198 description = "Stateless NAT64 implementation"; 199 wantedBy = [ "multi-user.target" ]; 200 after = [ "network.target" ]; 201 202 reloadTriggers = [ configFile ]; 203 serviceConfig = { 204 ExecStart = "${cfg.package}/bin/tayga -d --nodetach --config /etc/tayga.conf"; 205 ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID"; 206 Restart = "always"; 207 208 # Hardening Score: 209 # - nixos-scripts: 2.1 210 # - systemd-networkd: 1.6 211 ProtectHome = true; 212 SystemCallFilter = [ 213 "@network-io" 214 "@system-service" 215 "~@privileged" 216 "~@resources" 217 ]; 218 ProtectKernelLogs = true; 219 AmbientCapabilities = [ 220 "CAP_NET_ADMIN" 221 ]; 222 CapabilityBoundingSet = ""; 223 RestrictAddressFamilies = [ 224 "AF_INET" 225 "AF_INET6" 226 "AF_NETLINK" 227 ]; 228 StateDirectory = "tayga"; 229 DynamicUser = mkIf config.networking.useNetworkd true; 230 MemoryDenyWriteExecute = true; 231 RestrictRealtime = true; 232 RestrictSUIDSGID = true; 233 ProtectHostname = true; 234 ProtectKernelModules = true; 235 ProtectKernelTunables = true; 236 RestrictNamespaces = true; 237 NoNewPrivileges = true; 238 ProtectControlGroups = true; 239 SystemCallArchitectures = "native"; 240 PrivateTmp = true; 241 LockPersonality = true; 242 ProtectSystem = true; 243 PrivateUsers = true; 244 ProtectProc = "invisible"; 245 }; 246 }; 247 }; 248}