diff --git a/nix/pkgs/default.nix b/nix/pkgs/default.nix index 98a8206..951e75e 100644 --- a/nix/pkgs/default.nix +++ b/nix/pkgs/default.nix @@ -40,6 +40,7 @@ let cp secrets-production.nix secrets.nix ''; + network-cypher-graphs = import ./network-cypher-graphs.nix { inherit config pkgs; }; network-graphs = import ./network-graphs.nix { inherit config pkgs; }; mkRootfs = hostName: @@ -92,7 +93,7 @@ let inherit self nixpkgs system; }; in -rootfs-packages // vm-packages // device-templates // network-graphs // starlink // subnetplans // { +rootfs-packages // vm-packages // device-templates // network-graphs // network-cypher-graphs // starlink // subnetplans // { inherit all-rootfs export-openwrt-models export-config dns-slaves encrypt-secrets decrypt-secrets switch-to-production ; diff --git a/nix/pkgs/network-cypher-graphs.nix b/nix/pkgs/network-cypher-graphs.nix new file mode 100644 index 000000000..aabd04e --- /dev/null +++ b/nix/pkgs/network-cypher-graphs.nix @@ -0,0 +1,170 @@ +{ config, pkgs, ... }: +let + inherit (pkgs) lib runCommand curl; + + netColor = net: + if net == "core" + then "grey" + else if net == "mgmt" + then "brown" + else if builtins.elem net [ "c3d2" "serv" "cluster" ] + then "green" + else if builtins.match "up.+" net != null || + builtins.match "anon.+" net != null + then "red" + else if builtins.match "priv.+" net != null + then "blue" + else "black"; + + dedupLinks = links: + builtins.attrValues ( + builtins.foldl' (result: { pair, attrs ? {}, startLabel ? "" }: + let + peer1 = builtins.elemAt pair 0; + peer2 = builtins.elemAt pair 1; + sorted = + if peer1 < peer2 + then { from = peer1; to = peer2; } + else { from = peer2; to = peer1; }; + key = with sorted; "${from} ${to}"; + prevAttrs = + if result ? ${key} + then result.${key}.attrs + else {}; + newAttrs = attrs // ( + if peer1 < peer2 + then { taillabel = startLabel; } + else { headlabel = startLabel; } + ); + in result // { + "${key}" = { + inherit (sorted) from to; + attrs = prevAttrs // newAttrs; + }; + } + ) {} links + ); + + toCypher = { nodes, links, ... }: '' + ${lib.concatMapStringsSep " " (name: + let + nodeAttrs = nodes.${name}; + id = builtins.replaceStrings ["-"] ["_"] name; + nodeType = nodeAttrs.type; + in '' + CREATE (${id}:`${nodeType}` + {label:"${name}", + ${lib.concatMapStringsSep ", " (attr: + let + value = nodeAttrs.${attr}; + in + if builtins.isString value + then "${attr}:\"${value}\"" + else "${attr}:${toString value}" + ) (builtins.attrNames nodeAttrs)} + } + ) + '') (builtins.attrNames nodes)} + + ${lib.concatMapStringsSep " " ({ from, to, attrs }: + let + idFrom = builtins.replaceStrings ["-"] ["_"] from; + idTo = builtins.replaceStrings ["-"] ["_"] to; + in '' + CREATE (${idFrom})-[:CONNECTED_TO + {${lib.concatMapStringsSep ", " (attr: + let + value = attrs.${attr}; + in + if builtins.isString value + then "${attr}:\"${value}\"" + else "${attr}:${toString value}" + ) (builtins.attrNames attrs)} + }]->(${idTo}) + '') (dedupLinks links)} + ''; + + cypherGraph = args@{ name, ... }: + runCommand "${name}.cypher" { + src = builtins.toFile "${name}.cypher" ( + toCypher args + ); + } '' + sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' $src | tr -d '\n' > $out + ''; + +in rec { + # Layer 2 + physical-cypher-graph = cypherGraph { + name = "physical"; + nodes = + builtins.mapAttrs (_: { role, ... }: { + type = { + switch = "Switch"; + server = "Server"; + container = "Container"; + ap = "AccessPoint"; + client = "Client"; + }.${role}; + }) ( + lib.filterAttrs (_: { links, ... }: + links != {} + ) config.site.hosts + ) // builtins.mapAttrs (_: _: { + type = "Other"; + }) ( + lib.filterAttrs (net: _: + builtins.match "up.*" net != null + ) config.site.net + ); + links = + builtins.concatMap (hostName: + map (link: { + pair = [ hostName link ]; + startLabel = ( #lib.optionalString (config.site.hosts.${hostName}.links ? ${link}) ( + lib.concatStringsSep "," + config.site.hosts.${hostName}.links.${link}.ports + ); + }) ( + builtins.filter (link: + config.site.hosts ? ${link} + || + builtins.match "up.*" link != null + ) (builtins.attrNames config.site.hosts.${hostName}.links) + ) + ) (builtins.attrNames config.site.hosts); + }; + # Layer 3 + logical-cypher-graph = + let + containers = + lib.filterAttrs (_: { isRouter, role, ... }: + role == "container" + ) config.site.hosts; + in + cypherGraph { + name = "logical"; + nodes = + builtins.foldl' (result: hostName: + result // { + "${hostName}".type = "Container"; + } // builtins.mapAttrs (_: _: { + type = "Container"; + }) containers.${hostName}.interfaces + ) {} (builtins.attrNames containers); + links = + builtins.concatMap (hostName: + map (net: { + pair = [ hostName net ]; + attrs.color = netColor net; + }) (builtins.attrNames containers.${hostName}.interfaces) + ) (builtins.attrNames containers); + }; + + + import-network-graphs = runCommand "import-network-graphs" {} '' + ${curl}/bin/curl -X POST -H 'Content-type: application/json' http://localhost:7474/db/data/transaction/commit -d "{\"statements\": [{\"statement\": \"$(cat ${physical-cypher-graph})\"}]}" + ${curl}/bin/curl -X POST -H 'Content-type: application/json' http://localhost:7474/db/data/transaction/commit -d "{\"statements\": [{\"statement\": \"$(cat ${logical-cypher-graph})\"}]}" + ''; + +}