From 318f6927c16497cefa5f1a35fbf8b7ec95d9093f Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Mon, 9 Dec 2024 11:55:57 +0100 Subject: [PATCH] feat(lib): Init netconf-junos The code is copied from netconf-module, `with lib;` have been replaced by inherits --- lib/netconf-junos/default.nix | 74 ++++++++++++++ lib/netconf-junos/interfaces.nix | 168 +++++++++++++++++++++++++++++++ lib/netconf-junos/poe.nix | 49 +++++++++ lib/netconf-junos/protocols.nix | 33 ++++++ lib/netconf-junos/system.nix | 91 +++++++++++++++++ lib/netconf-junos/vlans.nix | 131 ++++++++++++++++++++++++ 6 files changed, 546 insertions(+) create mode 100644 lib/netconf-junos/default.nix create mode 100644 lib/netconf-junos/interfaces.nix create mode 100644 lib/netconf-junos/poe.nix create mode 100644 lib/netconf-junos/protocols.nix create mode 100644 lib/netconf-junos/system.nix create mode 100644 lib/netconf-junos/vlans.nix diff --git a/lib/netconf-junos/default.nix b/lib/netconf-junos/default.nix new file mode 100644 index 0000000..142c45c --- /dev/null +++ b/lib/netconf-junos/default.nix @@ -0,0 +1,74 @@ +{ config, lib, ... }: + +let + inherit (lib) mapAttrs mkOption; + + inherit (lib.types) + attrsOf + bool + str + submodule + ; + + mandatory.options = { + supportPoE = mkOption { + type = bool; + example = true; + description = '' + Wether this interface supports PoE. + ''; + }; + }; +in +{ + imports = [ + ./interfaces.nix + ./poe.nix + ./protocols.nix + ./system.nix + ./vlans.nix + ]; + + options = { + netconf.xmls.configuration = mkOption { + type = str; + readOnly = true; + description = '' + The full configuration to send to a JunOS. + ''; + }; + netconf.mandatoryInterfaces = mkOption { + type = attrsOf (submodule mandatory); + example = { + "ge-0/0/0" = { + supportPoE = true; + }; + "ge-0/0/1" = { + supportPoE = true; + }; + "xe-0/0/0" = { + supportPoE = false; + }; + }; + description = '' + JunOS require some interfaces to always be configured (even if they are disabled), + which correspond to physical interfaces of the switch. They have to be declared here + with some information about it (only if it supports PoE for now). + ''; + }; + }; + config.interfaces = + let + mkIntf = _: _: { }; + in + mapAttrs mkIntf config.netconf.mandatoryInterfaces; + config.netconf.xmls.configuration = with config.netconf.xmls; '' + + ${system} + ${interfaces} + ${protocols} + ${vlans} + ${poe} + + ''; +} diff --git a/lib/netconf-junos/interfaces.nix b/lib/netconf-junos/interfaces.nix new file mode 100644 index 0000000..69b3af0 --- /dev/null +++ b/lib/netconf-junos/interfaces.nix @@ -0,0 +1,168 @@ +{ config, lib, ... }: + +let + inherit (lib) + mapAttrsToList + mkEnableOption + mkOption + optionalString + ; + + inherit (lib.types) + attrsOf + either + enum + ints + listOf + str + submodule + ; + + interface = + { name, config, ... }: + let + unit = + { name, config, ... }: + { + options = { + enable = mkEnableOption "this logical interface" // { + default = true; + example = false; + }; + family = { + ethernet-switching = { + enable = mkEnableOption "the ethernet switching on this logical interface"; + interface-mode = mkOption { + type = enum [ + "trunk" + "access" + ]; + description = '' + Mode of operation for vlan addressing of this interface. + "trunk" means that the traffic is tagged, "access" means the + traffic is tagged by the switch. + ''; + }; + vlans = mkOption { + type = listOf (either str ints.unsigned); + default = [ ]; + description = '' + Vlans that can be used on this interface. + Only one ID should be here for "access" mode of operation. + ''; + }; + }; + #TODO : DHCP + inet = { + enable = mkEnableOption "the IPv4 configuration of this logical interface"; + addresses = mkOption { + type = listOf str; + default = [ ]; + description = '' + ipv4 addresses of this interface. + ''; + }; + }; + inet6 = { + enable = mkEnableOption "the IPv6 configuration of this logical interface"; + addresses = mkOption { + type = listOf str; + default = [ ]; + description = '' + ipv6 addresses of this interface. + ''; + }; + }; + }; + xml = mkOption { + type = str; + visible = false; + readOnly = true; + }; + }; + config.xml = + let + members = map ( + vlan: "${builtins.toString vlan}" + ) config.family.ethernet-switching.vlans; + eth = optionalString config.family.ethernet-switching.enable '' + + ${config.family.ethernet-switching.interface-mode} + ${builtins.concatStringsSep "" members} + default + + ''; + + addr4 = map (addr: "${addr}") config.family.inet.addresses; + inet = optionalString config.family.inet.enable '' + +
${builtins.concatStringsSep "" addr4}
+
+ ''; + + addr6 = map (addr: "${addr}") config.family.inet6.addresses; + inet6 = optionalString config.family.inet6.enable '' + +
${builtins.concatStringsSep "" addr6}
+
+ ''; + in + '' + + ${name} + ${optionalString (!config.enable) ""} + + ${eth}${inet}${inet6} + + ''; + }; + in + { + options = { + enable = mkEnableOption "this physical interface"; + unit = mkOption { + type = attrsOf (submodule unit); + default = { }; + description = '' + Configuration of the logical interfaces on this physical interface. + ''; + }; + xml = mkOption { + type = str; + visible = false; + readOnly = true; + }; + }; + config.xml = + let + units = mapAttrsToList (_: unit: unit.xml) config.unit; + in + '' + + ${name} + ${optionalString (!config.enable) ""} + ${builtins.concatStringsSep "" units} + + ''; + }; +in +{ + options = { + interfaces = mkOption { + type = attrsOf (submodule interface); + description = '' + The interfaces configuration. + ''; + }; + netconf.xmls.interfaces = mkOption { + type = str; + visible = false; + readOnly = true; + }; + }; + config.netconf.xmls.interfaces = '' + + ${builtins.concatStringsSep "" (mapAttrsToList (_: intf: intf.xml) config.interfaces)} + + ''; +} diff --git a/lib/netconf-junos/poe.nix b/lib/netconf-junos/poe.nix new file mode 100644 index 0000000..c1b4bbc --- /dev/null +++ b/lib/netconf-junos/poe.nix @@ -0,0 +1,49 @@ +{ config, lib, ... }: + +let + inherit (lib) + mapAttrsToList + mkEnableOption + mkOption + optionalString + ; + + inherit (lib.types) attrsOf str submodule; + + interface-module = + { name, config, ... }: + { + options = { + enable = mkEnableOption "the PoE for this interface"; + xml = mkOption { + type = str; + visible = false; + readOnly = true; + }; + }; + config.xml = '' + ${name}${optionalString (!config.enable) ""} + ''; + }; +in +{ + options = { + poe.interfaces = mkOption { + type = attrsOf (submodule interface-module); + default = { }; + description = '' + PoE configuration of interfaces. + ''; + }; + netconf.xmls.poe = mkOption { + type = str; + visible = false; + readOnly = true; + }; + }; + config.netconf.xmls.poe = '' + + ${builtins.concatStringsSep "" (mapAttrsToList (_: intf: intf.xml) config.poe.interfaces)} + + ''; +} diff --git a/lib/netconf-junos/protocols.nix b/lib/netconf-junos/protocols.nix new file mode 100644 index 0000000..6feac1e --- /dev/null +++ b/lib/netconf-junos/protocols.nix @@ -0,0 +1,33 @@ +{ config, lib, ... }: + +let + inherit (lib) concatMapStringsSep mkOption; + + inherit (lib.types) listOf str; +in + +{ + options = { + protocols.rstp = mkOption { + type = listOf str; + description = '' + List of interfaces on which Rapid Spanning Tree Protocol should be enabled. + ''; + }; + netconf.xmls.protocols = mkOption { + type = str; + visible = false; + readOnly = true; + }; + }; + + config.netconf.xmls.protocols = '' + + + ${ + concatMapStringsSep "" (intf: "${intf}") config.protocols.rstp + } + + + ''; +} diff --git a/lib/netconf-junos/system.nix b/lib/netconf-junos/system.nix new file mode 100644 index 0000000..5045481 --- /dev/null +++ b/lib/netconf-junos/system.nix @@ -0,0 +1,91 @@ +{ config, lib, ... }: + +let + inherit (lib) + concatStrings + concatStringsSep + filter + hasPrefix + length + mkOption + splitString + ; + + inherit (lib.types) + enum + listOf + port + str + ; +in + +{ + options = { + system = { + host-name = mkOption { + type = str; + description = "The hostname of the switch."; + }; + root-authentication = { + hashedPasswd = mkOption { + type = str; + description = "Hashed password for root."; + }; + ssh-keys = mkOption { + type = listOf str; + description = "ssh keys for root user."; + default = [ ]; + }; + }; + services = { + ssh.root-login = mkOption { + type = enum [ + "allow" + "deny" + "deny-password" + ]; + description = "Login policy to use for root."; + }; + netconf.port = mkOption { + type = port; + description = "Port to use for netconf."; + default = 830; + }; + }; + }; + netconf.xmls.system = mkOption { + type = str; + visible = false; + readOnly = true; + }; + }; + config.netconf.xmls.system = + let + ssh-keys1 = map (splitString " ") config.system.root-authentication.ssh-keys; + ssh-keys2 = map (key: if length key < 3 then key ++ [ "foo@bar" ] else key) ssh-keys1; + ssh-keys = map (concatStringsSep " ") ssh-keys2; + edsca = map (key: "${key}") ( + filter (hasPrefix "ssh-edsca ") ssh-keys + ); + rsa = map (key: "${key}") (filter (hasPrefix "ssh-rsa ") ssh-keys); + ed25519 = map (key: "${key}") ( + filter (hasPrefix "ssh-ed25519 ") ssh-keys + ); + in + '' + + ${config.system.host-name} + + ${config.system.root-authentication.hashedPasswd} + ${concatStrings (edsca ++ rsa ++ ed25519)} + + + ${config.system.services.ssh.root-login} + + ${toString config.system.services.netconf.port} + + + + + ''; +} diff --git a/lib/netconf-junos/vlans.nix b/lib/netconf-junos/vlans.nix new file mode 100644 index 0000000..dedc21e --- /dev/null +++ b/lib/netconf-junos/vlans.nix @@ -0,0 +1,131 @@ +{ config, lib, ... }: + +let + inherit (lib) + assertMsg + concatStringsSep + mapAttrsToList + mkOption + optionalString + ; + + inherit (lib.types) + attrsOf + either + ints + listOf + nullOr + str + submodule + unspecified + ; + + vlan = + { name, config, ... }: + { + options = { + id = mkOption { + type = nullOr ints.unsigned; + default = null; + description = '' + The ID of this vlan, `null` means no ID. + Incompatible with vlans.${name}.id-list. + ''; + }; + id-list = mkOption { + type = + let + range_type = + { config, ... }: + { + config.__toString = _: "${toString config.begin}-${toString config.end}"; + options = { + begin = mkOption { + type = ints.unsigned; + visible = false; + }; + end = mkOption { + type = ints.unsigned; + visible = false; + }; + __toString = mkOption { + visible = false; + internal = true; + readOnly = true; + type = unspecified; + }; + }; + }; + in + listOf (either ints.unsigned (submodule range_type)); + default = [ ]; + example = [ + 42 + { + begin = 100; + end = 200; + } + ]; + description = '' + List of IDs or IDs range to classify as this vlan. + Incompatible with vlans.${name}.id. + ''; + }; + l3-interface = mkOption { + type = nullOr str; + default = null; + example = "irb.0"; + description = '' + Switch's logical interface to connect directly to this vlan. + This allows to communicate with the switch from a vlan without + having a cable looping back on it's management interface. + ''; + }; + xml = mkOption { + type = str; + readOnly = true; + visible = false; + }; + }; + config.xml = + let + id = optionalString (config.id != null) "${toString config.id}"; + id-list = concatStringsSep "" ( + map (vlan: "${toString vlan}") config.id-list + ); + l3-intf = optionalString ( + config.l3-interface != null + ) "${config.l3-interface}"; + in + assert assertMsg ( + config.id == null || config.id-list == [ ] + ) "vlans.${name}.id and vlans.${name}.id-list are incompatible."; + '' + + ${name} + ${id}${id-list}${l3-intf} + + ''; + }; +in +{ + options = { + vlans = mkOption { + type = attrsOf (submodule vlan); + description = '' + Named vlans configuration. Allows to name vlans inside interface configuration, + instead of just using their IDs. + ''; + }; + netconf.xmls.vlans = mkOption { + type = str; + visible = false; + readOnly = true; + }; + }; + config.netconf.xmls.vlans = '' + + ${builtins.concatStringsSep "" (mapAttrsToList (_: vlan: vlan.xml) config.vlans)} + + ''; +}