at master 15 kB view raw
1{ pkgs, lib, ... }: 2let 3 4 # We'll need to be able to trade cert files between nodes via scp. 5 inherit (import ./ssh-keys.nix pkgs) 6 snakeOilPrivateKey 7 snakeOilPublicKey 8 ; 9 10 makeNebulaNode = 11 { config, ... }: 12 name: extraConfig: 13 lib.mkMerge [ 14 { 15 # Expose nebula for doing cert signing. 16 environment.systemPackages = [ 17 pkgs.dig 18 pkgs.nebula 19 ]; 20 users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; 21 services.openssh.enable = true; 22 networking.firewall.enable = true; # Implicitly true, but let's make sure. 23 networking.interfaces.eth1.useDHCP = false; 24 25 services.nebula.networks.smoke = { 26 # Note that these paths won't exist when the machine is first booted. 27 ca = "/etc/nebula/ca.crt"; 28 cert = "/etc/nebula/${name}.crt"; 29 key = "/etc/nebula/${name}.key"; 30 listen = { 31 host = "0.0.0.0"; 32 port = 33 if 34 ( 35 config.services.nebula.networks.smoke.isLighthouse || config.services.nebula.networks.smoke.isRelay 36 ) 37 then 38 4242 39 else 40 0; 41 }; 42 }; 43 } 44 extraConfig 45 ]; 46 47in 48{ 49 name = "nebula"; 50 51 nodes = { 52 53 lighthouse = 54 { ... }@args: 55 makeNebulaNode args "lighthouse" { 56 networking.firewall.allowedUDPPorts = [ 53 ]; 57 networking.interfaces.eth1.ipv4.addresses = lib.mkForce [ 58 { 59 address = "192.168.1.1"; 60 prefixLength = 24; 61 } 62 ]; 63 64 services.nebula.networks.smoke = { 65 isLighthouse = true; 66 isRelay = true; 67 firewall = { 68 outbound = [ 69 { 70 port = "any"; 71 proto = "any"; 72 host = "any"; 73 } 74 ]; 75 inbound = [ 76 { 77 port = "any"; 78 proto = "any"; 79 host = "any"; 80 } 81 ]; 82 }; 83 lighthouse = { 84 dns = { 85 enable = true; 86 host = "10.0.100.1"; # bind to lighthouse interface 87 port = 53; # answer on standard DNS port 88 }; 89 }; 90 }; 91 }; 92 93 allowAny = 94 { ... }@args: 95 makeNebulaNode args "allowAny" { 96 networking.interfaces.eth1.ipv4.addresses = lib.mkForce [ 97 { 98 address = "192.168.1.2"; 99 prefixLength = 24; 100 } 101 ]; 102 103 services.nebula.networks.smoke = { 104 staticHostMap = { 105 "10.0.100.1" = [ "192.168.1.1:4242" ]; 106 }; 107 isLighthouse = false; 108 lighthouses = [ "10.0.100.1" ]; 109 relays = [ "10.0.100.1" ]; 110 firewall = { 111 outbound = [ 112 { 113 port = "any"; 114 proto = "any"; 115 host = "any"; 116 } 117 ]; 118 inbound = [ 119 { 120 port = "any"; 121 proto = "any"; 122 host = "any"; 123 } 124 ]; 125 }; 126 }; 127 }; 128 129 allowFromLighthouse = 130 { ... }@args: 131 makeNebulaNode args "allowFromLighthouse" { 132 networking.interfaces.eth1.ipv4.addresses = lib.mkForce [ 133 { 134 address = "192.168.1.3"; 135 prefixLength = 24; 136 } 137 ]; 138 139 services.nebula.networks.smoke = { 140 staticHostMap = { 141 "10.0.100.1" = [ "192.168.1.1:4242" ]; 142 }; 143 isLighthouse = false; 144 lighthouses = [ "10.0.100.1" ]; 145 relays = [ "10.0.100.1" ]; 146 firewall = { 147 outbound = [ 148 { 149 port = "any"; 150 proto = "any"; 151 host = "any"; 152 } 153 ]; 154 inbound = [ 155 { 156 port = "any"; 157 proto = "any"; 158 host = "lighthouse"; 159 } 160 ]; 161 }; 162 }; 163 }; 164 165 allowToLighthouse = 166 { ... }@args: 167 makeNebulaNode args "allowToLighthouse" { 168 networking.interfaces.eth1.ipv4.addresses = lib.mkForce [ 169 { 170 address = "192.168.1.4"; 171 prefixLength = 24; 172 } 173 ]; 174 175 services.nebula.networks.smoke = { 176 enable = true; 177 staticHostMap = { 178 "10.0.100.1" = [ "192.168.1.1:4242" ]; 179 }; 180 isLighthouse = false; 181 lighthouses = [ "10.0.100.1" ]; 182 relays = [ "10.0.100.1" ]; 183 firewall = { 184 outbound = [ 185 { 186 port = "any"; 187 proto = "any"; 188 host = "lighthouse"; 189 } 190 ]; 191 inbound = [ 192 { 193 port = "any"; 194 proto = "any"; 195 host = "any"; 196 } 197 ]; 198 }; 199 }; 200 }; 201 202 disabled = 203 { ... }@args: 204 makeNebulaNode args "disabled" { 205 networking.interfaces.eth1.ipv4.addresses = lib.mkForce [ 206 { 207 address = "192.168.1.5"; 208 prefixLength = 24; 209 } 210 ]; 211 212 services.nebula.networks.smoke = { 213 enable = false; 214 staticHostMap = { 215 "10.0.100.1" = [ "192.168.1.1:4242" ]; 216 }; 217 isLighthouse = false; 218 lighthouses = [ "10.0.100.1" ]; 219 relays = [ "10.0.100.1" ]; 220 firewall = { 221 outbound = [ 222 { 223 port = "any"; 224 proto = "any"; 225 host = "lighthouse"; 226 } 227 ]; 228 inbound = [ 229 { 230 port = "any"; 231 proto = "any"; 232 host = "any"; 233 } 234 ]; 235 }; 236 }; 237 }; 238 239 }; 240 241 testScript = 242 let 243 244 setUpPrivateKey = name: '' 245 ${name}.start() 246 ${name}.succeed( 247 "mkdir -p /root/.ssh", 248 "chmod 700 /root/.ssh", 249 "cat '${snakeOilPrivateKey}' > /root/.ssh/id_snakeoil", 250 "chmod 600 /root/.ssh/id_snakeoil", 251 "mkdir -p /root" 252 ) 253 ''; 254 255 # From what I can tell, StrictHostKeyChecking=no is necessary for ssh to work between machines. 256 sshOpts = "-oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -oIdentityFile=/root/.ssh/id_snakeoil"; 257 258 restartAndCheckNebula = name: ip: '' 259 ${name}.systemctl("restart nebula@smoke.service") 260 ${name}.succeed("ping -c5 ${ip}") 261 ''; 262 263 # Create a keypair on the client node, then use the public key to sign a cert on the lighthouse. 264 signKeysFor = name: ip: '' 265 lighthouse.wait_for_unit("sshd.service") 266 ${name}.wait_for_unit("sshd.service") 267 ${name}.succeed( 268 "mkdir -p /etc/nebula", 269 "nebula-cert keygen -out-key /etc/nebula/${name}.key -out-pub /etc/nebula/${name}.pub", 270 "scp ${sshOpts} /etc/nebula/${name}.pub root@192.168.1.1:/root/${name}.pub", 271 ) 272 lighthouse.succeed( 273 'nebula-cert sign -duration $((365*24*60))m -ca-crt /etc/nebula/ca.crt -ca-key /etc/nebula/ca.key -name "${name}" -groups "${name}" -ip "${ip}" -in-pub /root/${name}.pub -out-crt /root/${name}.crt' 274 ) 275 ${name}.succeed( 276 "scp ${sshOpts} root@192.168.1.1:/root/${name}.crt /etc/nebula/${name}.crt", 277 "scp ${sshOpts} root@192.168.1.1:/etc/nebula/ca.crt /etc/nebula/ca.crt", 278 '(id nebula-smoke >/dev/null && chown -R nebula-smoke:nebula-smoke /etc/nebula) || true' 279 ) 280 ''; 281 282 getPublicIp = node: '' 283 ${node}.succeed("ip --brief addr show eth1 | awk '{print $3}' | tail -n1 | cut -d/ -f1").strip() 284 ''; 285 286 # Never do this for anything security critical! (Thankfully it's just a test.) 287 # Restart Nebula right after the mutual block and/or restore so the state is fresh. 288 blockTrafficBetween = nodeA: nodeB: '' 289 node_a = ${getPublicIp nodeA} 290 node_b = ${getPublicIp nodeB} 291 ${nodeA}.succeed("iptables -I INPUT -s " + node_b + " -j DROP") 292 ${nodeB}.succeed("iptables -I INPUT -s " + node_a + " -j DROP") 293 ${nodeA}.systemctl("restart nebula@smoke.service") 294 ${nodeB}.systemctl("restart nebula@smoke.service") 295 ''; 296 allowTrafficBetween = nodeA: nodeB: '' 297 node_a = ${getPublicIp nodeA} 298 node_b = ${getPublicIp nodeB} 299 ${nodeA}.succeed("iptables -D INPUT -s " + node_b + " -j DROP") 300 ${nodeB}.succeed("iptables -D INPUT -s " + node_a + " -j DROP") 301 ${nodeA}.systemctl("restart nebula@smoke.service") 302 ${nodeB}.systemctl("restart nebula@smoke.service") 303 ''; 304 in 305 '' 306 # Create the certificate and sign the lighthouse's keys. 307 ${setUpPrivateKey "lighthouse"} 308 lighthouse.succeed( 309 "mkdir -p /etc/nebula", 310 'nebula-cert ca -duration $((10*365*24*60))m -name "Smoke Test" -out-crt /etc/nebula/ca.crt -out-key /etc/nebula/ca.key', 311 'nebula-cert sign -duration $((365*24*60))m -ca-crt /etc/nebula/ca.crt -ca-key /etc/nebula/ca.key -name "lighthouse" -groups "lighthouse" -ip "10.0.100.1/24" -out-crt /etc/nebula/lighthouse.crt -out-key /etc/nebula/lighthouse.key', 312 'chown -R nebula-smoke:nebula-smoke /etc/nebula' 313 ) 314 315 # Reboot the lighthouse and verify that the nebula service comes up on boot. 316 # Since rebooting takes a while, we'll just restart the service on the other nodes. 317 lighthouse.shutdown() 318 lighthouse.start() 319 lighthouse.wait_for_unit("nebula@smoke.service") 320 lighthouse.succeed("ping -c5 10.0.100.1") 321 322 # Create keys for allowAny's nebula service and test that it comes up. 323 ${setUpPrivateKey "allowAny"} 324 ${signKeysFor "allowAny" "10.0.100.2/24"} 325 ${restartAndCheckNebula "allowAny" "10.0.100.2"} 326 327 # Create keys for allowFromLighthouse's nebula service and test that it comes up. 328 ${setUpPrivateKey "allowFromLighthouse"} 329 ${signKeysFor "allowFromLighthouse" "10.0.100.3/24"} 330 ${restartAndCheckNebula "allowFromLighthouse" "10.0.100.3"} 331 332 # Create keys for allowToLighthouse's nebula service and test that it comes up. 333 ${setUpPrivateKey "allowToLighthouse"} 334 ${signKeysFor "allowToLighthouse" "10.0.100.4/24"} 335 ${restartAndCheckNebula "allowToLighthouse" "10.0.100.4"} 336 337 # Create keys for disabled's nebula service and test that it does not come up. 338 ${setUpPrivateKey "disabled"} 339 ${signKeysFor "disabled" "10.0.100.5/24"} 340 disabled.fail("systemctl status nebula@smoke.service") 341 disabled.fail("ping -c5 10.0.100.5") 342 343 # The lighthouse can ping allowAny and allowFromLighthouse but not disabled 344 lighthouse.succeed("ping -c3 10.0.100.2") 345 lighthouse.succeed("ping -c3 10.0.100.3") 346 lighthouse.fail("ping -c3 10.0.100.5") 347 348 # allowAny can ping the lighthouse, but not allowFromLighthouse because of its inbound firewall 349 allowAny.succeed("ping -c3 10.0.100.1") 350 allowAny.fail("ping -c3 10.0.100.3") 351 # allowAny can also resolve DNS on lighthouse 352 allowAny.succeed("dig @10.0.100.1 allowToLighthouse | grep -E 'allowToLighthouse\.\s+[0-9]+\s+IN\s+A\s+10\.0\.100\.4'") 353 354 # allowFromLighthouse can ping the lighthouse and allowAny 355 allowFromLighthouse.succeed("ping -c3 10.0.100.1") 356 allowFromLighthouse.succeed("ping -c3 10.0.100.2") 357 358 # block allowFromLighthouse <-> allowAny, and allowFromLighthouse -> allowAny should still work. 359 ${blockTrafficBetween "allowFromLighthouse" "allowAny"} 360 allowFromLighthouse.succeed("ping -c10 10.0.100.2") 361 ${allowTrafficBetween "allowFromLighthouse" "allowAny"} 362 allowFromLighthouse.succeed("ping -c10 10.0.100.2") 363 364 # allowToLighthouse can ping the lighthouse but not allowAny or allowFromLighthouse 365 allowToLighthouse.succeed("ping -c3 10.0.100.1") 366 allowToLighthouse.fail("ping -c3 10.0.100.2") 367 allowToLighthouse.fail("ping -c3 10.0.100.3") 368 369 # allowAny can ping allowFromLighthouse now that allowFromLighthouse pinged it first 370 allowAny.succeed("ping -c3 10.0.100.3") 371 372 # block allowAny <-> allowFromLighthouse, and allowAny -> allowFromLighthouse should still work. 373 ${blockTrafficBetween "allowAny" "allowFromLighthouse"} 374 allowFromLighthouse.succeed("ping -c10 10.0.100.2") 375 allowAny.succeed("ping -c10 10.0.100.3") 376 ${allowTrafficBetween "allowAny" "allowFromLighthouse"} 377 allowFromLighthouse.succeed("ping -c10 10.0.100.2") 378 allowAny.succeed("ping -c10 10.0.100.3") 379 380 # allowToLighthouse can ping allowAny if allowAny pings it first 381 allowAny.succeed("ping -c3 10.0.100.4") 382 allowToLighthouse.succeed("ping -c3 10.0.100.2") 383 384 # block allowToLighthouse <-> allowAny, and allowAny <-> allowToLighthouse should still work. 385 ${blockTrafficBetween "allowAny" "allowToLighthouse"} 386 allowAny.succeed("ping -c10 10.0.100.4") 387 allowToLighthouse.succeed("ping -c10 10.0.100.2") 388 ${allowTrafficBetween "allowAny" "allowToLighthouse"} 389 allowAny.succeed("ping -c10 10.0.100.4") 390 allowToLighthouse.succeed("ping -c10 10.0.100.2") 391 392 # block lighthouse <-> allowFromLighthouse and allowAny <-> allowFromLighthouse; allowFromLighthouse won't get to allowAny 393 ${blockTrafficBetween "allowFromLighthouse" "lighthouse"} 394 ${blockTrafficBetween "allowFromLighthouse" "allowAny"} 395 allowFromLighthouse.fail("ping -c3 10.0.100.2") 396 ${allowTrafficBetween "allowFromLighthouse" "lighthouse"} 397 ${allowTrafficBetween "allowFromLighthouse" "allowAny"} 398 allowFromLighthouse.succeed("ping -c3 10.0.100.2") 399 400 # block lighthouse <-> allowAny, allowAny <-> allowFromLighthouse, and allowAny <-> allowToLighthouse; it won't get to allowFromLighthouse or allowToLighthouse 401 ${blockTrafficBetween "allowAny" "lighthouse"} 402 ${blockTrafficBetween "allowAny" "allowFromLighthouse"} 403 ${blockTrafficBetween "allowAny" "allowToLighthouse"} 404 allowFromLighthouse.fail("ping -c3 10.0.100.2") 405 allowAny.fail("ping -c3 10.0.100.3") 406 allowAny.fail("ping -c3 10.0.100.4") 407 ${allowTrafficBetween "allowAny" "lighthouse"} 408 ${allowTrafficBetween "allowAny" "allowFromLighthouse"} 409 ${allowTrafficBetween "allowAny" "allowToLighthouse"} 410 allowFromLighthouse.succeed("ping -c3 10.0.100.2") 411 allowAny.succeed("ping -c3 10.0.100.3") 412 allowAny.succeed("ping -c3 10.0.100.4") 413 414 # block lighthouse <-> allowToLighthouse and allowToLighthouse <-> allowAny; it won't get to allowAny 415 ${blockTrafficBetween "allowToLighthouse" "lighthouse"} 416 ${blockTrafficBetween "allowToLighthouse" "allowAny"} 417 allowAny.fail("ping -c3 10.0.100.4") 418 allowToLighthouse.fail("ping -c3 10.0.100.2") 419 ${allowTrafficBetween "allowToLighthouse" "lighthouse"} 420 ${allowTrafficBetween "allowToLighthouse" "allowAny"} 421 allowAny.succeed("ping -c3 10.0.100.4") 422 allowToLighthouse.succeed("ping -c3 10.0.100.2") 423 ''; 424}