summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorLinus Heckemann <git@sphalerite.org>2020-12-14 21:52:57 +0100
committerGitHub <noreply@github.com>2020-12-14 21:52:57 +0100
commitc40f06022a83d6f286dd2a906e1d5fefcc05dfa1 (patch)
tree0e889334622e07e20dcddb064af47f458e02c0da /nixos
parent7a6203fffc16227e5a0f4477fff050c32ddd27b0 (diff)
parent6cd975094d9ea1bd39a9bd47307008e31fadfebe (diff)
downloadnixpkgs-c40f06022a83d6f286dd2a906e1d5fefcc05dfa1.tar
nixpkgs-c40f06022a83d6f286dd2a906e1d5fefcc05dfa1.tar.gz
nixpkgs-c40f06022a83d6f286dd2a906e1d5fefcc05dfa1.tar.bz2
nixpkgs-c40f06022a83d6f286dd2a906e1d5fefcc05dfa1.tar.lz
nixpkgs-c40f06022a83d6f286dd2a906e1d5fefcc05dfa1.tar.xz
nixpkgs-c40f06022a83d6f286dd2a906e1d5fefcc05dfa1.tar.zst
nixpkgs-c40f06022a83d6f286dd2a906e1d5fefcc05dfa1.zip
Merge pull request #106073 from minijackson/tinc-rfc42-and-tests
nixos/tinc: rfc42 and tests
Diffstat (limited to 'nixos')
-rw-r--r--nixos/modules/services/networking/tinc.nix237
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/tinc/default.nix139
-rw-r--r--nixos/tests/tinc/snakeoil-keys.nix157
4 files changed, 521 insertions, 13 deletions
diff --git a/nixos/modules/services/networking/tinc.nix b/nixos/modules/services/networking/tinc.nix
index b4b544a2413..b6afd83a9ab 100644
--- a/nixos/modules/services/networking/tinc.nix
+++ b/nixos/modules/services/networking/tinc.nix
@@ -1,13 +1,156 @@
 { config, lib, pkgs, ... }:
 
 with lib;
-
 let
-
   cfg = config.services.tinc;
 
-in
+  mkValueString = value:
+    if value == true then "yes"
+    else if value == false then "no"
+    else generators.mkValueStringDefault { } value;
+
+  toTincConf = generators.toKeyValue {
+    listsAsDuplicateKeys = true;
+    mkKeyValue = generators.mkKeyValueDefault { inherit mkValueString; } "=";
+  };
+
+  tincConfType = with types;
+    let
+      valueType = oneOf [ bool str int ];
+    in
+    attrsOf (either valueType (listOf valueType));
+
+  addressSubmodule = {
+    options = {
+      address = mkOption {
+        type = types.str;
+        description = "The external IP address or hostname where the host can be reached.";
+      };
+
+      port = mkOption {
+        type = types.nullOr types.port;
+        default = null;
+        description = ''
+          The port where the host can be reached.
+
+          If no port is specified, the default Port is used.
+        '';
+      };
+    };
+  };
+
+  subnetSubmodule = {
+    options = {
+      address = mkOption {
+        type = types.str;
+        description = ''
+          The subnet of this host.
+
+          Subnets can either be single MAC, IPv4 or IPv6 addresses, in which case
+          a subnet consisting of only that single address is assumed, or they can
+          be a IPv4 or IPv6 network address with a prefix length.
+
+          IPv4 subnets are notated like 192.168.1.0/24, IPv6 subnets are notated
+          like fec0:0:0:1::/64. MAC addresses are notated like 0:1a:2b:3c:4d:5e.
+
+          Note that subnets like 192.168.1.1/24 are invalid.
+        '';
+      };
+
+      prefixLength = mkOption {
+        type = with types; nullOr (addCheck int (n: n >= 0 && n <= 128));
+        default = null;
+        description = ''
+          The prefix length of the subnet.
+
+          If null, a subnet consisting of only that single address is assumed.
+
+          This conforms to standard CIDR notation as described in RFC1519.
+        '';
+      };
+
+      weight = mkOption {
+        type = types.ints.unsigned;
+        default = 10;
+        description = ''
+          Indicates the priority over identical Subnets owned by different nodes.
+
+          Lower values indicate higher priority. Packets will be sent to the
+          node with the highest priority, unless that node is not reachable, in
+          which case the node with the next highest priority will be tried, and
+          so on.
+        '';
+      };
+    };
+  };
+
+  hostSubmodule = { config, ... }: {
+    options = {
+      addresses = mkOption {
+        type = types.listOf (types.submodule addressSubmodule);
+        default = [ ];
+        description = ''
+          The external address where the host can be reached. This will set this
+          host's <option>settings.Address</option> option.
+
+          This variable is only required if you want to connect to this host.
+        '';
+      };
+
+      subnets = mkOption {
+        type = types.listOf (types.submodule subnetSubmodule);
+        default = [ ];
+        description = ''
+          The subnets which this tinc daemon will serve. This will set this
+          host's <option>settings.Subnet</option> option.
 
+          Tinc tries to look up which other daemon it should send a packet to by
+          searching the appropriate subnet. If the packet matches a subnet, it
+          will be sent to the daemon who has this subnet in his host
+          configuration file.
+        '';
+      };
+
+      rsaPublicKey = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Legacy RSA public key of the host in PEM format, including start and
+          end markers.
+
+          This will be appended as-is in the host's configuration file.
+
+          The ed25519 public key can be specified using the
+          <option>settings.Ed25519PublicKey</option> option instead.
+        '';
+      };
+
+      settings = mkOption {
+        default = { };
+        type = types.submodule { freeformType = tincConfType; };
+        description = ''
+          Configuration for this host.
+
+          See <link xlink:href="https://tinc-vpn.org/documentation-1.1/Host-configuration-variables.html"/>
+          for supported values.
+        '';
+      };
+    };
+
+    config.settings = {
+      Address = mkDefault (map
+        (address: "${address.address} ${toString address.port}")
+        config.addresses);
+
+      Subnet = mkDefault (map
+        (subnet:
+          if subnet.prefixLength == null then "${subnet.address}#${toString subnet.weight}"
+          else "${subnet.address}/${toString subnet.prefixLength}#${toString subnet.weight}")
+        config.subnets);
+    };
+  };
+
+in
 {
 
   ###### interface
@@ -18,7 +161,7 @@ in
 
       networks = mkOption {
         default = { };
-        type = with types; attrsOf (submodule {
+        type = with types; attrsOf (submodule ({ config, ... }: {
           options = {
 
             extraConfig = mkOption {
@@ -26,6 +169,9 @@ in
               type = types.lines;
               description = ''
                 Extra lines to add to the tinc service configuration file.
+
+                Note that using the declarative <option>service.tinc.networks.&lt;name&gt;.settings</option>
+                option is preferred.
               '';
             };
 
@@ -72,6 +218,40 @@ in
               description = ''
                 The name of the host in the network as well as the configuration for that host.
                 This name should only contain alphanumerics and underscores.
+
+                Note that using the declarative <option>service.tinc.networks.&lt;name&gt;.hostSettings</option>
+                option is preferred.
+              '';
+            };
+
+            hostSettings = mkOption {
+              default = { };
+              example = literalExample ''
+                {
+                  host1 = {
+                    addresses = [
+                      { address = "192.168.1.42"; }
+                      { address = "192.168.1.42"; port = 1655; }
+                    ];
+                    subnets = [ { address = "10.0.0.42"; } ];
+                    rsaPublicKey = "...";
+                    settings = {
+                      Ed25519PublicKey = "...";
+                    };
+                  };
+                  host2 = {
+                    subnets = [ { address = "10.0.1.0"; prefixLength = 24; weight = 2; } ];
+                    rsaPublicKey = "...";
+                    settings = {
+                      Compression = 10;
+                    };
+                  };
+                }
+              '';
+              type = types.attrsOf (types.submodule hostSubmodule);
+              description = ''
+                The name of the host in the network as well as the configuration for that host.
+                This name should only contain alphanumerics and underscores.
               '';
             };
 
@@ -79,7 +259,7 @@ in
               default = "tun";
               type = types.enum [ "tun" "tap" ];
               description = ''
-                The type of virtual interface used for the network connection
+                The type of virtual interface used for the network connection.
               '';
             };
 
@@ -118,8 +298,44 @@ in
                 Note that tinc can't run scripts anymore (such as tinc-down or host-up), unless it is setup to be runnable inside chroot environment.
               '';
             };
+
+            settings = mkOption {
+              default = { };
+              type = types.submodule { freeformType = tincConfType; };
+              example = literalExample ''
+                {
+                  Interface = "custom.interface";
+                  DirectOnly = true;
+                  Mode = "switch";
+                }
+              '';
+              description = ''
+                Configuration of the Tinc daemon for this network.
+
+                See <link xlink:href="https://tinc-vpn.org/documentation-1.1/Main-configuration-variables.html"/>
+                for supported values.
+              '';
+            };
+          };
+
+          config = {
+            hosts = mapAttrs
+              (hostname: host: ''
+                ${toTincConf host.settings}
+                ${host.rsaPublicKey}
+              '')
+              config.hostSettings;
+
+            settings = {
+              DeviceType = mkDefault config.interfaceType;
+              Name = mkDefault (if config.name == null then "$HOST" else config.name);
+              Ed25519PrivateKeyFile = mkIf (config.ed25519PrivateKeyFile != null) (mkDefault config.ed25519PrivateKeyFile);
+              PrivateKeyFile = mkIf (config.rsaPrivateKeyFile != null) (mkDefault config.rsaPrivateKeyFile);
+              ListenAddress = mkIf (config.listenAddress != null) (mkDefault config.listenAddress);
+              BindToAddress = mkIf (config.bindToAddress != null) (mkDefault config.bindToAddress);
+            };
           };
-        });
+        }));
 
         description = ''
           Defines the tinc networks which will be started.
@@ -144,13 +360,7 @@ in
           "tinc/${network}/tinc.conf" = {
             mode = "0444";
             text = ''
-              Name = ${if data.name == null then "$HOST" else data.name}
-              DeviceType = ${data.interfaceType}
-              ${optionalString (data.ed25519PrivateKeyFile != null) "Ed25519PrivateKeyFile = ${data.ed25519PrivateKeyFile}"}
-              ${optionalString (data.rsaPrivateKeyFile != null) "PrivateKeyFile = ${data.rsaPrivateKeyFile}"}
-              ${optionalString (data.listenAddress != null) "ListenAddress = ${data.listenAddress}"}
-              ${optionalString (data.bindToAddress != null) "BindToAddress = ${data.bindToAddress}"}
-              Interface = tinc.${network}
+              ${toTincConf ({ Interface = "tinc.${network}"; } // data.settings)}
               ${data.extraConfig}
             '';
           };
@@ -222,4 +432,5 @@ in
 
   };
 
+  meta.maintainers = with maintainers; [ minijackson ];
 }
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index e4f4b891ceb..3e134c7544a 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -375,6 +375,7 @@ in
   telegraf = handleTest ./telegraf.nix {};
   tiddlywiki = handleTest ./tiddlywiki.nix {};
   timezone = handleTest ./timezone.nix {};
+  tinc = handleTest ./tinc {};
   tinydns = handleTest ./tinydns.nix {};
   tor = handleTest ./tor.nix {};
   # traefik test relies on docker-containers
diff --git a/nixos/tests/tinc/default.nix b/nixos/tests/tinc/default.nix
new file mode 100644
index 00000000000..31b675ad35c
--- /dev/null
+++ b/nixos/tests/tinc/default.nix
@@ -0,0 +1,139 @@
+import ../make-test-python.nix ({ lib, ... }:
+  let
+    snakeoil-keys = import ./snakeoil-keys.nix;
+
+    hosts = lib.attrNames snakeoil-keys;
+
+    subnetOf = name: config:
+      let
+        subnets = config.services.tinc.networks.myNetwork.hostSettings.${name}.subnets;
+      in
+      (builtins.head subnets).address;
+
+    makeTincHost = name: { subnet, extraConfig ? { } }: lib.mkMerge [
+      {
+        subnets = [{ address = subnet; }];
+        settings = {
+          Ed25519PublicKey = snakeoil-keys.${name}.ed25519Public;
+        };
+        rsaPublicKey = snakeoil-keys.${name}.rsaPublic;
+      }
+      extraConfig
+    ];
+
+    makeTincNode = { config, ... }: name: extraConfig: lib.mkMerge [
+      {
+        services.tinc.networks.myNetwork = {
+          inherit name;
+          rsaPrivateKeyFile =
+            builtins.toFile "rsa.priv" snakeoil-keys.${name}.rsaPrivate;
+          ed25519PrivateKeyFile =
+            builtins.toFile "ed25519.priv" snakeoil-keys.${name}.ed25519Private;
+
+          hostSettings = lib.mapAttrs makeTincHost {
+            static = {
+              subnet = "10.0.0.11";
+              # Only specify the addresses in the node's vlans, Tinc does not
+              # seem to try each one, unlike the documentation suggests...
+              extraConfig.addresses = map
+                (vlan: { address = "192.168.${toString vlan}.11"; port = 655; })
+                config.virtualisation.vlans;
+            };
+            dynamic1 = { subnet = "10.0.0.21"; };
+            dynamic2 = { subnet = "10.0.0.22"; };
+          };
+        };
+
+        networking.useDHCP = false;
+
+        networking.interfaces."tinc.myNetwork" = {
+          virtual = true;
+          virtualType = "tun";
+          ipv4.addresses = [{
+            address = subnetOf name config;
+            prefixLength = 24;
+          }];
+        };
+
+        # Prevents race condition between NixOS service and tinc creating the
+        # interface.
+        # See: https://github.com/NixOS/nixpkgs/issues/27070
+        systemd.services."tinc.myNetwork" = {
+          after = [ "network-addresses-tinc.myNetwork.service" ];
+          requires = [ "network-addresses-tinc.myNetwork.service" ];
+        };
+
+        networking.firewall.allowedTCPPorts = [ 655 ];
+        networking.firewall.allowedUDPPorts = [ 655 ];
+      }
+      extraConfig
+    ];
+
+  in
+  {
+    name = "tinc";
+    meta.maintainers = with lib.maintainers; [ minijackson ];
+
+    nodes = {
+
+      static = { ... } @ args:
+        makeTincNode args "static" {
+          virtualisation.vlans = [ 1 2 ];
+
+          networking.interfaces.eth1.ipv4.addresses = [{
+            address = "192.168.1.11";
+            prefixLength = 24;
+          }];
+
+          networking.interfaces.eth2.ipv4.addresses = [{
+            address = "192.168.2.11";
+            prefixLength = 24;
+          }];
+        };
+
+
+      dynamic1 = { ... } @ args:
+        makeTincNode args "dynamic1" {
+          virtualisation.vlans = [ 1 ];
+        };
+
+      dynamic2 = { ... } @ args:
+        makeTincNode args "dynamic2" {
+          virtualisation.vlans = [ 2 ];
+        };
+
+    };
+
+    testScript = ''
+      start_all()
+
+      static.wait_for_unit("tinc.myNetwork.service")
+      dynamic1.wait_for_unit("tinc.myNetwork.service")
+      dynamic2.wait_for_unit("tinc.myNetwork.service")
+
+      # Static is accessible by the other hosts
+      dynamic1.succeed("ping -c5 192.168.1.11")
+      dynamic2.succeed("ping -c5 192.168.2.11")
+
+      # The other hosts are in separate vlans
+      dynamic1.fail("ping -c5 192.168.2.11")
+      dynamic2.fail("ping -c5 192.168.1.11")
+
+      # Each host can ping themselves through Tinc
+      static.succeed("ping -c5 10.0.0.11")
+      dynamic1.succeed("ping -c5 10.0.0.21")
+      dynamic2.succeed("ping -c5 10.0.0.22")
+
+      # Static is accessible by the other hosts through Tinc
+      dynamic1.succeed("ping -c5 10.0.0.11")
+      dynamic2.succeed("ping -c5 10.0.0.11")
+
+      # Static can access the other hosts through Tinc
+      static.succeed("ping -c5 10.0.0.21")
+      static.succeed("ping -c5 10.0.0.22")
+
+      # The other hosts in separate vlans can access each other through Tinc
+      dynamic1.succeed("ping -c5 10.0.0.22")
+      dynamic2.succeed("ping -c5 10.0.0.21")
+    '';
+  })
diff --git a/nixos/tests/tinc/snakeoil-keys.nix b/nixos/tests/tinc/snakeoil-keys.nix
new file mode 100644
index 00000000000..650e57d61d4
--- /dev/null
+++ b/nixos/tests/tinc/snakeoil-keys.nix
@@ -0,0 +1,157 @@
+{
+  static = {
+    ed25519Private = ''
+      -----BEGIN ED25519 PRIVATE KEY-----
+      IPR+ur5LfVdm6VlR1+FGIkbkL8Enkb9sejBa/JP6tXkg/vHoraIp70srb6jAUFm5
+      3YbCJiBjLW3dy16qM5PovBoWtr5hoqYYA9dFLOys8FBUFFsIGfKhnbk7g25iwxbO
+      -----END ED25519 PRIVATE KEY-----
+    '';
+
+    ed25519Public = "AqV7aeIqKGGQfXxijMLfRAVRBLixnS45G5OoduIc8mD";
+
+    rsaPrivate = ''
+      -----BEGIN RSA PRIVATE KEY-----
+      MIIEpAIBAAKCAQEAxDHl0TIhhT2yH5rT+Q7MLnj+Ir8bbs3uaPqnzcxWzN1EfVP8
+      TWt5fSTrF2Dc78Kyu5ZNALrp7tUj0GZAegp1YeYJ28p3qTwCveywtCwbB4dI987S
+      yJwq95kE9aoyLa+cT99VwSTdb2YowQv2tWj/idxE3oJ+qZjy9tE5mysXm7jmTQDx
+      +U0XmNe6MHjKXc01Ener41u0ykJLeUfdgJ1zEyM2rQGtaHpIXfMT6kmxCaMcAMLg
+      YFpI38/1pQGQtROKdGOaUomx2m058bkMsJhTiBjESiLRDElRGxmMJ732crGJP0GR
+      ChJkaX/CnxHq7R0daZfwoTVHRu6N7WDbFQL5twIDAQABAoIBAQCM/fLTIHyYXRr5
+      vXFhxXGUYBz56W6UdWdEiAU5TwR92vFSQ53IIVlARtyvg0ui/b8mMcAKq0hb+03u
+      gN0LFyL+BKvHCLxvoRGzXTorcJrIET+t3jL6OchjANNgnDvNOytQ9wWQdKaxXLAi
+      8y8LdXZWozXW1d6ikKjiGL+WNCSWIcq83ktSJZcohihptU9Un16FYQzdolSC8RtI
+      XyT7i1ye6hW/wJTJxqZ4taX3EPat85kXS234VGSqg9bb2A1yE+U8Rq37bf8AKldJ
+      NUQB3JyxnkYGJcqvzDmz139+744VWxDRvXDA5vU29LC6f8bGBvwEttD98QW+pgmB
+      1NBU1Uo5AoGBAOzUk6k74h1RarwXaftjh/9Pures0CfNNnrkJApzFCh4bAoHNxq6
+      SSXqLcc/vvX2+YaZ72nn5YTo+JLQP6evM9oUaqRMAxa3nzoNCtF8U2r48UWmoUQE
+      aZCYbD3m7IVWFacCKRVaVTMZMTTicypSnXcbCSIEH8PRs9+L4jkHgql9AoGBANQT
+      TZECVhIaQnyRiKWlUE8G1QKzXIxjmfyirBe+ftlIG2XMXasAtQ4VRxpnorgqUnIH
+      BVrIbvRx21zlqwZbrZvyb1jHWRoyi1cqBPijpYBUm5LbV2jgHPhnfhRVqdD4CDKj
+      NQzIQrNymFaMWAoOQv/DE3g+Txr0fm9Ztu8ZRXZDAoGAHh3SQT0aPfwyhIS9t3gq
+      vS7YYa8aMVWJTgthAessbxERPB06xq1Vy/qBo8rZb9HeXV2J8n/I0iQGKDVPQvWm
+      tF7QSOBZrDPhjbJG4+jZesr5c5ADBfFBs1+OtDh/b11JF5nQu6RnHT5g4YbCemlT
+      GOhZOvgnSfGK3CyfsfzggskCgYEAmpKDK5kPUNxw70hH16v5L9Bj+zbt0qlZ+Ag8
+      9IV1ATuMNJNTBitay6v4iidVM3QtaUzyuytxq5s87qW7FMRHcm2ueH+70ttaMiq/
+      OtZT74g7aDuUpy0KEIemHn4dauENYJMSPIHOE+sHW7WpCZNBhBcUHsUTdSsU6GX0
+      bqr1tO8CgYBpZdR2OoX/rn8nwjmtBOH38aPnCpaAfdI2Eq2Lg6DjksP6TBt53a+R
+      m1lk6Kt37BPPZQ85SBr7ywvDgUzfoD7uSmHujF2JUHPsdrg9nx7pNIGlW6DlS9OU
+      oNXGAJ/6/y6F8uDbToUfrwFq5tKMypEEa32kFtxb9f0XQ5fSgHrBEw==
+      -----END RSA PRIVATE KEY-----
+    '';
+
+    rsaPublic = ''
+      -----BEGIN RSA PUBLIC KEY-----
+      MIIBCgKCAQEAxDHl0TIhhT2yH5rT+Q7MLnj+Ir8bbs3uaPqnzcxWzN1EfVP8TWt5
+      fSTrF2Dc78Kyu5ZNALrp7tUj0GZAegp1YeYJ28p3qTwCveywtCwbB4dI987SyJwq
+      95kE9aoyLa+cT99VwSTdb2YowQv2tWj/idxE3oJ+qZjy9tE5mysXm7jmTQDx+U0X
+      mNe6MHjKXc01Ener41u0ykJLeUfdgJ1zEyM2rQGtaHpIXfMT6kmxCaMcAMLgYFpI
+      38/1pQGQtROKdGOaUomx2m058bkMsJhTiBjESiLRDElRGxmMJ732crGJP0GRChJk
+      aX/CnxHq7R0daZfwoTVHRu6N7WDbFQL5twIDAQAB
+      -----END RSA PUBLIC KEY-----
+    '';
+  };
+
+  dynamic1 = {
+    ed25519Private = ''
+      -----BEGIN ED25519 PRIVATE KEY-----
+      wHNC2IMXfYtL4ehdsCX154HBvlIZYEiTOnXtckWMUtEAiX9fu7peyBkp9q+yOy9c
+      xsNyssLL78lt0GoweCxlu3Sza2oBQAcwb+6tuv7P/bqzcG005uCwquyCz8LVymXA
+      -----END ED25519 PRIVATE KEY-----
+    '';
+
+    ed25519Public = "t0smNaAEAH8mver77+z/m6MnBNdurAsqrswM/Sls5FA";
+
+    rsaPrivate = ''
+      -----BEGIN RSA PRIVATE KEY-----
+      MIIEpAIBAAKCAQEApukYNGFNWvVlmx75LyOE7MEcd/ViV+yEyk+4cIBXYJ3Ouw+/
+      oEuh8ghQfsiUtbUPR6hPYhX2ZV8XGhuU2nAXVQV0sfZ8pdkbHQ6wHUqFcUIQAVvS
+      Wpm2DvZM8jkbCPP64/x5nukPwQ8VoNnb62rWGzbcj7rOeb7ndMK0TpX5Wwv8F297
+      nKTNCEDbK3DLTj3VD+QGnw6AoEt5i44vViAWZBXuHLHWTDC0Nq8GG+9TKODkEwt5
+      4dgN2X9f+WTVAYhZT3SayHLqIFIMQunN89RpWwhHSW+JIRfAfuT1TbP+wA5ptDeI
+      ktCkJwWyv4hK6l800BJ9GW1nbId5LPa58ipaVwIDAQABAoIBAHcw3WgKVAMwWm57
+      n9ZZtwKapInFYYUIEYungj5UaBFGn+pVRLJjUDJWXaUr94YK1e6F8qpIpLufPBAY
+      wiN7CC5exwaOzlRgxUvqwTkpjkFiu6s8tuqb+baVjD0tKnEqSW+lS/R+2hEzhG5p
+      JPLoSB0HAFpjPC8UdJSctcWos3if3mvOGkGCKyTkrwaJgECDfD+lZ+NBIAiYLSps
+      jWLE+XlY1+nfPdLUQ+TRSv3IikJ/CWbvJLl9EE1tKhkY564KytwZrkIdJlc7NyRO
+      HpzhyMzHu1GLsr+OsBZByNNUxEPU+bzkDQluRXUSIUs9zZoBiCQr3o04qGPTEX9n
+      pNU60gECgYEA3Uf+c80eqzjDxv+O0YzC+9x6A+yMrV56siGkKRPMlrSqjX7iE2Yg
+      tUjD25kEvtaFuB3f/7zp3h4O/VLZgXreRtXHvdrfoyyJGHvHIyCGm8sw8CEWsKo4
+      1LgZUzdPJRkXJq1zOgS0r1xsA1UDC4s02Ww2HwNeVWtmLUyCpA+B/ccCgYEAwRk9
+      tbe82eq1a85zZiPVXP2qvDH5+Vz9YiMky8xsBnoxmz2siR+NdvWBLcE2VDIY8MK1
+      9a1dz2a7cAHQBrtWtACFVY4zvr69DumApjbQRClDYpJ42tp2VbzlMcUDIoKudRQV
+      CObhrE4w4yfVizXFyH9+4Tsg5NzVYuGg9fUJ/vECgYEAoRz7KouNqfMhsLF/5hkM
+      Gt9zw4mm/9ALm8kcwn/U9WHD0FQy/Rbd98BsQmaOavi80cqGvqhoyz2tgkqhbUHt
+      tzuOPDCxphgWFcqBupTDDYoLLruYzraRvGfyoIFj0coL7jBZ9kNY31l2l5J9LhmE
+      OE4utbP5Kk6RTagocpWL+x8CgYB48CwcIcWf3kZeDOFtuUeqhB1o3Qwox7rSuhwT
+      oCaQL/vdtNTY1PAu7zhGxdoXBYFlWS3JfxlgCoGedyQo8zAscJ8RpIx4DNIwAsLW
+      V0I9TnKry/zxZR30OOh7MV7zQFGvdjJubtwspJQt0QcHt1f2aRO4UOYbMMxcr9+1
+      7BCkoQKBgQDBEtg1hx9zYGg1WN2TBSvh6NShi9S23r6IZ3Up8vz6Z2rcwB3UuhKi
+      xluI2ZFwM9s+7UOpaGC+hnc1aMHDEguYOPXoIzvebbYAdN4AkrsJ5d0r1GoEe64E
+      UXxrfuv5LeJ/vkUgWof+U3/jGOVvrjzi5y1xOC0r3kiSpMa85s1dhQ==
+      -----END RSA PRIVATE KEY-----
+    '';
+
+    rsaPublic = ''
+      -----BEGIN RSA PUBLIC KEY-----
+      MIIBCgKCAQEApukYNGFNWvVlmx75LyOE7MEcd/ViV+yEyk+4cIBXYJ3Ouw+/oEuh
+      8ghQfsiUtbUPR6hPYhX2ZV8XGhuU2nAXVQV0sfZ8pdkbHQ6wHUqFcUIQAVvSWpm2
+      DvZM8jkbCPP64/x5nukPwQ8VoNnb62rWGzbcj7rOeb7ndMK0TpX5Wwv8F297nKTN
+      CEDbK3DLTj3VD+QGnw6AoEt5i44vViAWZBXuHLHWTDC0Nq8GG+9TKODkEwt54dgN
+      2X9f+WTVAYhZT3SayHLqIFIMQunN89RpWwhHSW+JIRfAfuT1TbP+wA5ptDeIktCk
+      JwWyv4hK6l800BJ9GW1nbId5LPa58ipaVwIDAQAB
+      -----END RSA PUBLIC KEY-----
+    '';
+  };
+
+  dynamic2 = {
+    ed25519Private = ''
+      -----BEGIN ED25519 PRIVATE KEY-----
+      oUx9JdIstZLMj3ZPD8mP3ITsUscCTIXhNF3VKFUVi/ma5uk50/1vrEohfDraiMxj
+      gAWthpkhnFzUbp+YlOHE7/Z3h1a/br2/tk8DoZ5PV6ufoV1MaBlGdu+TZgeZou0t
+      -----END ED25519 PRIVATE KEY-----
+    '';
+
+    ed25519Public = "f2dYt2/2q9fLJ/AaW+Tlu7HaVNjWQpRnr/UGoXGqLdL";
+
+    rsaPrivate = ''
+      -----BEGIN RSA PRIVATE KEY-----
+      MIIEpAIBAAKCAQEAtQfijPX3BwOAs2Y0EuNjcBmsI90uYqNAonrFgTtcVwERIVE6
+      p6alSEakazhByujBg3jI8oPKC8eO0IJ7x/BWcgxqaw8hsPfJZFnRlwEcU5kK4c+j
+      UNS+hJOXp0x97T1edLpSFHDK9bZ2necblHKG5MsI4UsxEa+CZ0yoIybwWCDmYuya
+      PvE7CeNNa+CIOUbtPVoN4p/aBj0vZeerNBBuodNkglKRxj4l9wD9uOx4S9sdK5lu
+      q/rkxlViBoXRAshT+G2d/u/7/WPoiKB3QJcF33z8UfrlsTRnDDqOMSGisTPSv2LK
+      4QLN4hWOGXAYQqZcxTkvvjl62mCDuoy0TM+CKQIDAQABAoIBAFKpMAxXf52nPswr
+      /dkmFVCpmE2kADsv+iJ21tpkpYxgw1aoRZUp5cyz3P3MaVZio4IJ1A/Ql6B7Vb3l
+      5ulr170p6CnMdgDdlAsLbEV8T1foyOxFKHiPPBNDZXsR1WpPnGLGdRY6TqKV12HQ
+      lmpZRTkRcJOXBufhcTUD7r5mWFaUoZ7so6VxR4L4Tzcgv1Rl4S6jgnHOQdO6lj47
+      BaPjpBb+hplJ4wsRm91dQ7JApYq25XZwyxnBwQ2zAwb46wsuFxDPHlSc4wU7qTt6
+      x2omm33Xy2cm8L1XQhrassZzldSnAyaLBh9DC3+vFPLODDxdz5M2kpHujYYctRhv
+      CICMYJUCgYEA7mWVYuw0S8FNjaLx6n9Q1hr9d9vAFDd3NEaegH586xvhYNxf6n+C
+      2zZloVLEsX0UnBU/6ZtLAUfxUIqlvDS2r1VjSYG5SNxM6/vyGl17Niu1jC8nzf7M
+      V1WtDCHhT4ikZCuNkAldtgI7CXVdCVO/fTqVhjk4hDblJo7VsCZSZysCgYEAwmXp
+      TwlDHapDqA8UxClZuxS8k+2hthny3ihRPCuT34yqAz074zYG97ZBKwIa4Lm1vnkc
+      mwU7yR2aK7IYeU4ScfWm1mLjkW5iaNV/sG7iTz/RP4mBAs3KSGmuhhz8sFWcXByU
+      IZyvMJvC+FpgJQJn/Xc8ZmdImvXlZd6k8v4/kfsCgYEA6VzFPB2OH63slb4w42SX
+      o86t2dtiDigxZxnN5GhtLdSP7borpigF10JLf/y+kCOpvhRLCQk8Bdf/z+C41iAf
+      yEhktbrnvfvwzHxHhSmHCAMHZ19trodCTiePCrZLkQhoK6o6nAmfEyDh26NoXE3/
+      v71OSyLOQRZfgDwHz7PjrBsCgYAe0zojpjxWP+FqjLmmQUhROgCNFGlIDuVMBOic
+      uexAznVG/ja42KBSNzwuLa9FYy1Gfr3idvn78g24UA1BbvfNyj4iUJv1O6OvK+uL
+      dom8N0pe4NbsMuWYhel+qqoG7AxXLtDuY4IEGy7XYr1MIQ2MS5PwSQBiUguGE7/k
+      KBy8cQKBgQCyC9R8VWJxQLqJxZGa9Ful01bSuntB5OLRfEjFCCuGiY/3Vj+mCiQL
+      GOfMOi2jrcnSNgUm0uevmiFCq9m7QiPiAcSYKXPWhsz/55jJIGcZy8bwyhZ2s2Mg
+      BGeZgj4RFORidqkt5g/KJz0+Wp6Ks4sLoCvOzkpeXvLzFVyzGkihrw==
+      -----END RSA PRIVATE KEY-----
+    '';
+
+    rsaPublic = ''
+      -----BEGIN RSA PUBLIC KEY-----
+      MIIBCgKCAQEAtQfijPX3BwOAs2Y0EuNjcBmsI90uYqNAonrFgTtcVwERIVE6p6al
+      SEakazhByujBg3jI8oPKC8eO0IJ7x/BWcgxqaw8hsPfJZFnRlwEcU5kK4c+jUNS+
+      hJOXp0x97T1edLpSFHDK9bZ2necblHKG5MsI4UsxEa+CZ0yoIybwWCDmYuyaPvE7
+      CeNNa+CIOUbtPVoN4p/aBj0vZeerNBBuodNkglKRxj4l9wD9uOx4S9sdK5luq/rk
+      xlViBoXRAshT+G2d/u/7/WPoiKB3QJcF33z8UfrlsTRnDDqOMSGisTPSv2LK4QLN
+      4hWOGXAYQqZcxTkvvjl62mCDuoy0TM+CKQIDAQAB
+      -----END RSA PUBLIC KEY-----
+    '';
+  };
+}