diff --git a/default.nix b/default.nix index affa43f..a479d6b 100644 --- a/default.nix +++ b/default.nix @@ -2,82 +2,66 @@ pkgs ? (import <nixpkgs> { }), }: let - moduleEX2300 = import ./moduleMaker.nix [ - "ge-0/0/0" - "ge-0/0/1" - "ge-0/0/2" - "ge-0/0/3" - "ge-0/0/4" - "ge-0/0/5" - "ge-0/0/6" - "ge-0/0/7" - "ge-0/0/8" - "ge-0/0/9" - "ge-0/0/10" - "ge-0/0/11" - "ge-0/0/12" - "ge-0/0/13" - "ge-0/0/14" - "ge-0/0/15" - "ge-0/0/16" - "ge-0/0/17" - "ge-0/0/18" - "ge-0/0/19" - "ge-0/0/20" - "ge-0/0/21" - "ge-0/0/22" - "ge-0/0/23" - "ge-0/0/24" - "ge-0/0/25" - "ge-0/0/26" - "ge-0/0/27" - "ge-0/0/28" - "ge-0/0/29" - "ge-0/0/30" - "ge-0/0/31" - "ge-0/0/32" - "ge-0/0/33" - "ge-0/0/34" - "ge-0/0/35" - "ge-0/0/36" - "ge-0/0/37" - "ge-0/0/38" - "ge-0/0/39" - "ge-0/0/40" - "ge-0/0/41" - "ge-0/0/42" - "ge-0/0/43" - "ge-0/0/44" - "ge-0/0/45" - "ge-0/0/46" - "ge-0/0/47" - - "ge-0/1/0" - "ge-0/1/1" - "ge-0/1/2" - "ge-0/1/3" - - "xe-0/1/0" - "xe-0/1/1" - "xe-0/1/2" - "xe-0/1/3" - - "me0" - ]; + lib = pkgs.lib; + hive_mod = + { + lib, + config, + name, + ... + }: + with lib; + { + options.deployment = { + targetHost = mkOption { type = types.str; }; + rpc = mkOption { + type = types.package; + readOnly = true; + }; + cmd = mkOption { + type = types.package; + readOnly = true; + }; + }; + config.deployment = rec { + rpc = pkgs.writeText "config-${name}_rpc.xml" '' + <rpc> + <edit-config> + <config> + ${config.netconf.xmls.configuration} + </config> + <target> + <candidate/> + </target> + </edit-config> + </rpc> + <rpc> + <commit/> + </rpc> + ''; + cmd = pkgs.writeShellApplication { + name = "deploy-${name}.sh"; + runtimeInputs = with pkgs; [ openssh ]; + text = ''ssh "${config.deployment.targetHost}" -p 830 -s netconf < ${rpc}''; + }; + }; + }; evaluator = name: module_inst: let cfg = pkgs.lib.evalModules { specialArgs = { - inherit pkgs name; + inherit name; }; modules = [ - moduleEX2300 + ./junos + ./ex2300.nix + hive_mod module_inst ]; }; in - "ln -s ${cfg.config.deployement.cmd} $out/${name}"; + "ln -s ${lib.getExe cfg.config.deployment.cmd} $out/${name}"; hive = import ./netconf-hive.nix; cmds = builtins.attrValues (builtins.mapAttrs evaluator hive); in diff --git a/ex2300.nix b/ex2300.nix new file mode 100644 index 0000000..4165b46 --- /dev/null +++ b/ex2300.nix @@ -0,0 +1,62 @@ +{ + netconf.mandatoryInterfaces = [ + "ge-0/0/0" + "ge-0/0/1" + "ge-0/0/2" + "ge-0/0/3" + "ge-0/0/4" + "ge-0/0/5" + "ge-0/0/6" + "ge-0/0/7" + "ge-0/0/8" + "ge-0/0/9" + "ge-0/0/10" + "ge-0/0/11" + "ge-0/0/12" + "ge-0/0/13" + "ge-0/0/14" + "ge-0/0/15" + "ge-0/0/16" + "ge-0/0/17" + "ge-0/0/18" + "ge-0/0/19" + "ge-0/0/20" + "ge-0/0/21" + "ge-0/0/22" + "ge-0/0/23" + "ge-0/0/24" + "ge-0/0/25" + "ge-0/0/26" + "ge-0/0/27" + "ge-0/0/28" + "ge-0/0/29" + "ge-0/0/30" + "ge-0/0/31" + "ge-0/0/32" + "ge-0/0/33" + "ge-0/0/34" + "ge-0/0/35" + "ge-0/0/36" + "ge-0/0/37" + "ge-0/0/38" + "ge-0/0/39" + "ge-0/0/40" + "ge-0/0/41" + "ge-0/0/42" + "ge-0/0/43" + "ge-0/0/44" + "ge-0/0/45" + "ge-0/0/46" + "ge-0/0/47" + + "ge-0/1/0" + "ge-0/1/1" + "ge-0/1/2" + "ge-0/1/3" + + "xe-0/1/0" + "xe-0/1/1" + "xe-0/1/2" + "xe-0/1/3" + ]; +} diff --git a/junos/default.nix b/junos/default.nix new file mode 100644 index 0000000..a3e9d20 --- /dev/null +++ b/junos/default.nix @@ -0,0 +1,36 @@ +{ + name, + lib, + config, + ... +}: +with lib; +{ + imports = [ + ./protocols.nix + ./interfaces.nix + ./vlans.nix + ]; + options = { + netconf.xmls.configuration = mkOption { + type = types.str; + readOnly = true; + }; + netconf.mandatoryInterfaces = mkOption { type = types.listOf types.str; }; + }; + config.interfaces = + let + mkIntf = name: { + inherit name; + value.enable = mkDefault false; + }; + in + listToAttrs (map mkIntf config.netconf.mandatoryInterfaces); + config.netconf.xmls.configuration = '' + <configuration> + ${config.netconf.xmls.interfaces} + ${config.netconf.xmls.protocols} + ${config.netconf.xmls.vlans} + </configuration> + ''; +} diff --git a/junos/interfaces.nix b/junos/interfaces.nix new file mode 100644 index 0000000..9872378 --- /dev/null +++ b/junos/interfaces.nix @@ -0,0 +1,131 @@ +{ lib, config, ... }: +with lib; +let + interface = + { name, config, ... }: + let + intf-name = name; + unit = + { name, config, ... }: + { + options = { + enable = mkEnableOption "the logical interface ${intf-name}.${name}" // { + default = true; + }; + family = { + ethernet-switching = { + enable = mkEnableOption "the ethernet on the logical interface ${intf-name}.${name}"; + interface-mode = mkOption { + type = types.nullOr ( + types.enum [ + "trunk" + "access" + ] + ); + default = null; + }; + vlans = mkOption { + type = types.listOf (types.either types.str types.ints.unsigned); + default = [ ]; + }; + }; + #TODO : DHCP + inet = { + enable = mkEnableOption "the IPv4 configuration of the logical interface ${intf-name}.${name}"; + address = mkOption { + type = types.listOf types.str; + default = [ ]; + }; + }; + inet6 = { + enable = mkEnableOption "the IPv6 configuration of the logical interface ${intf-name}.${name}"; + address = mkOption { + type = types.listOf types.str; + default = [ ]; + }; + }; + }; + xml = mkOption { + type = types.str; + visible = false; + readOnly = true; + }; + }; + config.xml = + let + members = map ( + vlan: "<members>${builtins.toString vlan}</members>" + ) config.family.ethernet-switching.vlans; + eth = optionalString config.family.ethernet-switching.enable '' + <ethernet-switching> + <interface-mode>${config.family.ethernet-switching.interface-mode}</interface-mode> + <vlan>${builtins.concatStringsSep "" members}</vlan> + <storm-control><profile-name>default</profile-name></storm-control> + </ethernet-switching> + ''; + + addr4 = map (addr: "<name>${addr}</name>") config.family.inet.address; + inet = optionalString config.family.inet.enable '' + <inet> + <address>${builtins.concatStringsSep "" addr4}</address> + </inet> + ''; + + addr6 = map (addr: "<name>${addr}</name>") config.family.inet6.address; + inet6 = optionalString config.family.inet6.enable '' + <inet6> + <address>${builtins.concatStringsSep "" addr6}</address> + </inet6> + ''; + in + '' + <unit> + <name>${name}</name> + ${optionalString (!config.enable) "<disable/>"} + <family> + ${eth}${inet}${inet6} + </family> + </unit>''; + }; + in + { + options = { + enable = mkEnableOption "the physical interface ${intf-name}"; + unit = mkOption { + type = types.attrsOf (types.submodule unit); + default = { }; + }; + xml = mkOption { + type = types.str; + visible = false; + readOnly = true; + }; + }; + config.xml = + let + units = attrsets.mapAttrsToList (_: unit: unit.xml) config.unit; + in + '' + <interface> + <name>${name}</name> + ${optionalString (!config.enable) "<disable/>"} + ${builtins.concatStringsSep "" units} + </interface> + ''; + }; +in +{ + options = { + interfaces = mkOption { type = types.attrsOf (types.submodule interface); }; + netconf.xmls.interfaces = mkOption { + type = types.str; + visible = false; + readOnly = true; + }; + }; + config.netconf.xmls.interfaces = '' + <interfaces operation="replace"> + ${builtins.concatStringsSep "" (attrsets.mapAttrsToList (_: intf: intf.xml) config.interfaces)} + </interfaces> + ''; +} diff --git a/junos/protocols.nix b/junos/protocols.nix new file mode 100644 index 0000000..334d6d5 --- /dev/null +++ b/junos/protocols.nix @@ -0,0 +1,23 @@ +{ lib, config, ... }: +with lib; +{ + options = { + protocols.rstp = mkOption { type = types.listOf types.str; }; + netconf.xmls.protocols = mkOption { + type = types.str; + visible = false; + readOnly = true; + }; + }; + config.netconf.xmls.protocols = + let + rstps = map (intf: "<interface><name>${intf}</name></interface>") config.protocols.rstp; + in + '' + <protocols> + <rstp operation="replace"> + ${concatStringsSep "" rstps} + </rstp> + </protocols> + ''; +} diff --git a/junos/vlans.nix b/junos/vlans.nix new file mode 100644 index 0000000..f98c3b9 --- /dev/null +++ b/junos/vlans.nix @@ -0,0 +1,76 @@ +{ lib, config, ... }: +with lib; +let + vlan = + { name, config, ... }: + { + options = { + id = mkOption { + type = types.nullOr types.ints.unsigned; + default = null; + }; + id-list = mkOption { + type = + let + range_type = + { config, ... }: + { + config.__toString = _: "${toString config.begin}-${toString config.end}"; + options = { + begin = mkOption { type = types.ints.unsigned; }; + end = mkOption { type = types.ints.unsigned; }; + __toString = mkOption { + visible = false; + internal = true; + readOnly = true; + type = types.unspecified; + }; + }; + }; + in + types.listOf (types.either types.ints.unsigned (types.submodule range_type)); + default = [ ]; + }; + l3-interface = mkOption { + type = types.nullOr types.str; + default = null; + }; + xml = mkOption { + type = types.str; + readOnly = true; + visible = false; + }; + }; + config.xml = + let + id = optionalString (!isNull config.id) "<vlan-id>${toString config.id}</vlan-id>"; + id-list = concatStringsSep "" ( + map (vlan: "<vlan-id-list>${toString vlan}</vlan-id-list>") config.id-list + ); + l3-intf = optionalString ( + !isNull config.l3-interface + ) "<l3-interface>${config.l3-interface}</l3-interface>"; + in + '' + <vlan> + <name>${name}</name> + ${id}${id-list}${l3-intf} + </vlan> + ''; + }; +in +{ + options = { + vlans = mkOption { type = types.attrsOf (types.submodule vlan); }; + netconf.xmls.vlans = mkOption { + type = types.str; + visible = false; + readOnly = true; + }; + }; + config.netconf.xmls.vlans = '' + <vlans operation="replace"> + ${builtins.concatStringsSep "" (attrsets.mapAttrsToList (_: vlan: vlan.xml) config.vlans)} + </vlans> + ''; +} diff --git a/moduleMaker.nix b/moduleMaker.nix deleted file mode 100644 index dfbe1c5..0000000 --- a/moduleMaker.nix +++ /dev/null @@ -1,241 +0,0 @@ -interfaces: -{ - name, - lib, - pkgs, - config, - ... -}: -let - cfg = config; -in -with lib; -{ - options = { - deployement = { - targetHost = mkOption { type = types.str; }; - cmd = mkOption { - type = types.package; - readOnly = true; - }; - }; - vlans = - let - range_type.options = { - begin = mkOption { type = types.ints.unsigned; }; - end = mkOption { type = types.ints.unsigned; }; - }; - vlan_type.options = { - ids = mkOption { - type = types.either types.ints.unsigned ( - types.listOf (types.either types.ints.unsigned (types.submodule range_type)) - ); - default = [ ]; - }; - management = mkOption { - # FIXME : support ipv4, either static or dhcp (with the coffee) - type = types.nullOr types.str; - default = null; - description = '' - IP address with wich to permit management on this vlan. - Only one vlan can set an IP (this module limitation, not switch). - ''; - }; - }; - in - mkOption { type = types.attrsOf (types.submodule vlan_type); }; - interfaces = - let - template = name: { - enable = mkEnableOption "the interface ${name}"; - interface-mode = mkOption { - type = types.nullOr ( - types.enum [ - "trunk" - "access" - ] - ); - default = null; - }; - vlans = mkOption { - type = - let - vlan_type = types.either (types.strMatching "[^\n\r]+") (types.ints.unsigned); - in - types.listOf vlan_type; - default = [ ]; - }; - # TODO: use this option - dhcp_trusted = mkOption { - type = types.bool; - default = false; - }; - management = mkOption { - # FIXME : support ipv6, either static or dhcp (with the coffee) - type = types.nullOr types.str; - default = null; - }; - }; - in - builtins.listToAttrs ( - map (name: { - inherit name; - value = template name; - }) interfaces - ); - }; - - config.deployement.cmd = - let - intf_xmlGen = - name: - let - disable_flag = if !cfg.interfaces.${name}.enable then "<disable/>" else ""; - # FIXME : need to enforce address in reality - mgmt_fam = - if !builtins.isNull cfg.interfaces.${name}.management then - '' - <inet> - <address> - <name>${cfg.interfaces.${name}.management}</name> - </address> - </inet>'' - else - ""; - members = map (vlan: "<members>${builtins.toString vlan}</members>") cfg.interfaces.${name}.vlans; - eth_switch = - if builtins.isNull cfg.interfaces.${name}.interface-mode then - "" - else - '' - <ethernet-switching> - <interface-mode>${cfg.interfaces.${name}.interface-mode}</interface-mode> - <vlan>${builtins.concatStringsSep "" members}</vlan> - <storm-control><profile-name>default</profile-name></storm-control> - </ethernet-switching>''; - in - '' - <interface> - <name>${name}</name> - ${disable_flag} - <unit> - <name>0</name> - <family> - ${mgmt_fam} - ${eth_switch} - </family> - </unit> - </interface> - ''; - interface_xmls = map intf_xmlGen interfaces; - rstp_gen = - name: - if cfg.interfaces.${name}.enable && !builtins.isNull cfg.interfaces.${name}.interface-mode then - "<interface><name>${name}</name></interface>" - else - ""; - rstps = map rstp_gen interfaces; - vlan_trust_table = - let - vlan_map = - inter: vlan: - if builtins.isString vlan && cfg.interfaces.${inter}.enable then - if cfg.interfaces.${inter}.dhcp_trusted then - { ${vlan}.trust = inter; } - else - { ${vlan}.notrust = inter; } - else - { }; - int_map = inter: map (vlan_map inter) cfg.interfaces.${inter}.vlans; - in - builtins.zipAttrsWith (vlan: values: builtins.zipAttrsWith (_: ints: ints) values) ( - builtins.concatMap int_map interfaces - ); - vlans = - let - id_map = - id: - let - list = - if builtins.isInt id then - builtins.toString id - else - "${builtins.toString id.begin}-${builtins.toString id.end}"; - in - ''<vlan-id-list>${list}</vlan-id-list>''; - vlan_map = - vlan: - let - ids = - if !builtins.isList cfg.vlans.${vlan}.ids then - [ "<vlan-id>${builtins.toString cfg.vlans.${vlan}.ids}</vlan-id>" ] - else - map id_map cfg.vlans.${vlan}.ids; - mgmt_flag = - if !builtins.isNull cfg.vlans.${vlan}.management then "<l3-interface>irb.0</l3-interface>" else ""; - in - '' - <vlan> - <name>${vlan}</name> - ${mgmt_flag} - ${builtins.concatStringsSep "\n" ids} - </vlan>''; - in - map vlan_map (builtins.attrNames cfg.vlans); - irb_intf = - let - addresses = map (vlan: vlan.management) (builtins.attrValues cfg.vlans); - addr = builtins.foldl' (acc: addr: if !builtins.isNull addr then addr else acc) null addresses; - in - if !builtins.isNull addr then - '' - <interface> - <name>irb</name> - <unit> - <name>0</name> - <family> - <inet6> - <address><name>${addr}</name></address> - </inet6> - </family> - </unit> - </interface> - '' - else - ""; - config = '' - <interfaces operation="replace"> - ${builtins.concatStringsSep "\n" interface_xmls} - ${irb_intf} - </interfaces> - <protocols> - <rstp operation="replace"> - ${builtins.concatStringsSep "\n" rstps} - </rstp> - </protocols> - <vlans operation="replace"> - ${builtins.concatStringsSep "\n" vlans} - </vlans> - ''; - rpc_requests = pkgs.writeText "config-${name}_rpc.xml" '' - <rpc> - <edit-config> - <config> - <configuration> - ${config} - </configuration> - </config> - <target> - <candidate/> - </target> - </edit-config> - </rpc> - <rpc> - <commit/> - </rpc> - ''; - in - pkgs.writeShellScript "deploy-${name}.sh" '' - ${pkgs.openssh}/bin/ssh ${cfg.deployement.targetHost} -p 830 -s netconf < ${rpc_requests} - ''; -} diff --git a/netconf-hive.nix b/netconf-hive.nix index 4a531ea..ef8ec55 100644 --- a/netconf-hive.nix +++ b/netconf-hive.nix @@ -1,103 +1,203 @@ let - vlansPlan = mgmt: { - "uplink-cri".ids = 223; + vlansPlan = { + "uplink-cri".id = 223; "admin-core" = { - ids = 3000; - management = mgmt; + id = 3000; + l3-interface = "irb.0"; }; - "admin-ap".ids = 3001; - "users".ids = [ + "admin-ap".id = 3001; + "users".id-list = [ { begin = 3045; end = 4094; } ]; - "ap-staging".ids = 2000; + "ap-staging".id = 2000; }; AP = { enable = true; - interface-mode = "trunk"; - vlans = [ - "users" - "admin-ap" - ]; + unit."0".family.ethernet-switching = { + enable = true; + interface-mode = "trunk"; + vlans = [ + "users" + "admin-ap" + ]; + }; }; AP-staging = { enable = true; - interface-mode = "access"; - vlans = [ "ap-staging" ]; + unit."0".family.ethernet-switching = { + enable = true; + interface-mode = "access"; + vlans = [ "ap-staging" ]; + }; }; in { - netcore01 = { - deployement.targetHost = "jourdan01.dgn"; + netcore02 = { + deployment.targetHost = "netcore02.dgn"; + vlans = vlansPlan; + protocols.rstp = [ + "ge-0/0/0" + "ge-0/0/1" + "ge-0/0/2" + "ge-0/0/3" + "ge-0/0/4" + "ge-0/0/5" + "ge-0/0/6" + "ge-0/0/7" + "ge-0/0/8" + "ge-0/0/9" + "ge-0/0/10" + "ge-0/0/11" + "ge-0/0/12" + "ge-0/0/13" + "ge-0/0/14" + "ge-0/0/15" + "ge-0/0/16" + "ge-0/0/17" + "ge-0/0/42" + "ge-0/0/43" + "ge-0/0/47" - vlans = vlansPlan "fd26:baf9:d250:8000::1001/64"; + "xe-0/1/0" + "xe-0/1/1" + "ge-0/1/3" + ]; interfaces = { - "ge-0/0/12" = AP; - "ge-0/0/13" = AP; - "ge-0/0/14" = AP; - "ge-0/0/15" = AP; - "ge-0/0/16" = AP; - "ge-0/0/17" = AP; + "ge-0/0/0" = AP-staging; + "ge-0/0/1" = AP-staging; + "ge-0/0/2" = AP-staging; + "ge-0/0/3" = AP-staging; + "ge-0/0/4" = AP-staging; + "ge-0/0/5" = AP-staging; + "ge-0/0/6" = AP-staging; + "ge-0/0/7" = AP-staging; + "ge-0/0/8" = AP-staging; + "ge-0/0/9" = AP-staging; + "ge-0/0/10" = AP-staging; + "ge-0/0/11" = AP-staging; + "ge-0/0/12" = AP-staging; + "ge-0/0/13" = AP-staging; + "ge-0/0/14" = AP-staging; + "ge-0/0/15" = AP-staging; + "ge-0/0/16" = AP-staging; + "ge-0/0/17" = AP-staging; "ge-0/0/42" = { enable = true; - interface-mode = "access"; - vlans = [ "admin-core" ]; + unit."0".family.ethernet-switching = { + enable = true; + interface-mode = "trunk"; + vlans = [ "all" ]; + }; }; "ge-0/0/43" = AP-staging; "ge-0/0/47" = { # ilo enable = true; - interface-mode = "access"; - vlans = [ "admin-core" ]; + unit."0".family.ethernet-switching = { + enable = true; + interface-mode = "access"; + vlans = [ "admin-core" ]; + }; }; "xe-0/1/0" = { enable = true; - interface-mode = "trunk"; - vlans = [ "all" ]; - dhcp_trusted = true; + unit."0".family.ethernet-switching = { + enable = true; + interface-mode = "trunk"; + vlans = [ "all" ]; + }; }; "xe-0/1/1" = { enable = true; - interface-mode = "trunk"; - vlans = [ - "users" - "admin-ap" - "admin-core" - ]; + unit."0".family.ethernet-switching = { + enable = true; + interface-mode = "trunk"; + vlans = [ + "users" + "admin-ap" + "admin-core" + ]; + }; }; "ge-0/1/3" = { enable = true; - interface-mode = "trunk"; - vlans = [ "uplink-cri" ]; + unit."0".family.ethernet-switching = { + enable = true; + interface-mode = "trunk"; + vlans = [ "uplink-cri" ]; + }; }; "me0" = { enable = true; - management = "192.168.42.6/24"; + unit."0".family.inet = { + enable = true; + address = [ "192.168.42.6/24" ]; + }; + }; + + "irb" = { + enable = true; + unit."0".family.inet6 = { + enable = true; + address = [ "fd26:baf9:d250:8000::1001/64" ]; + }; }; }; }; netaccess01 = { - deployement.targetHost = "root@192.168.42.6"; + deployment.targetHost = "netaccess01.dgn"; - vlans = vlansPlan "fd26:baf9:d250:8000::2001/64"; + vlans = vlansPlan; + + protocols.rstp = [ + "ge-0/0/0" + "ge-0/0/1" + "ge-0/0/2" + "ge-0/0/3" + "ge-0/0/4" + "ge-0/0/5" + + "xe-0/1/0" + ]; interfaces = { + "ge-0/0/0" = AP-staging; + "ge-0/0/1" = AP-staging; + "ge-0/0/2" = AP-staging; + "ge-0/0/3" = AP-staging; + "ge-0/0/4" = AP-staging; + "ge-0/0/5" = AP-staging; + "xe-0/1/0" = { enable = true; - interface-mode = "trunk"; - vlans = [ "all" ]; - dhcp_trusted = true; + unit."0".family.ethernet-switching = { + enable = true; + interface-mode = "trunk"; + vlans = [ "all" ]; + }; }; "me0" = { enable = true; - management = "192.168.42.6/24"; + unit."0".family.inet = { + enable = true; + address = [ "192.168.42.6/24" ]; + }; + }; + + "irb" = { + enable = true; + unit."0".family.inet6 = { + enable = true; + address = [ "fd26:baf9:d250:8000::2001/64" ]; + }; }; }; };