Compare commits

..

16 Commits

Author SHA1 Message Date
John Lancaster
ddc4b4e5a4 added keep monthly 2026-03-15 18:47:06 -05:00
John Lancaster
0830a8d0a4 fixed rp-test 2026-03-15 18:46:27 -05:00
John Lancaster
e83f6939e8 started mtls stuff 2026-03-15 18:15:45 -05:00
John Lancaster
f5ae40c3e7 added ssh host cert renewal 2026-03-15 17:05:58 -05:00
John Lancaster
cd13e56e15 added login-text to janus 2026-03-15 16:54:48 -05:00
John Lancaster
3339cd6b0c removed default for principals option 2026-03-15 16:21:02 -05:00
John Lancaster
6315ac0143 provisioner explicitly defined 2026-03-15 16:16:59 -05:00
John Lancaster
b3bcfdcfcb step-ssh-user 2026-03-15 16:15:27 -05:00
John Lancaster
2ace9cd2dd formatting 2026-03-15 16:15:17 -05:00
John Lancaster
8735ef24d5 hostKeyFile variable 2026-03-15 15:52:45 -05:00
John Lancaster
85a1127e1d userCAPath variable 2026-03-15 15:51:09 -05:00
John Lancaster
9c26c962ff slight reorg 2026-03-15 15:44:24 -05:00
John Lancaster
da2de12193 moved sign-ssh-cert 2026-03-15 15:20:34 -05:00
John Lancaster
ff9a817ef8 better known_hosts 2026-03-15 15:06:48 -05:00
John Lancaster
ec501e3029 sign ssh cert working 2026-03-15 14:58:23 -05:00
John Lancaster
e1b093929c ssh certificates on desktop 2026-03-15 14:45:33 -05:00
9 changed files with 281 additions and 120 deletions

View File

@@ -8,20 +8,28 @@ in
modules = with inputs.self.modules; [ modules = with inputs.self.modules; [
nixos.lxc nixos.lxc
nixos.sops nixos.sops
nixos.step-client nixos.step-ssh-host
inputs.home-manager.nixosModules.home-manager inputs.home-manager.nixosModules.home-manager
nixos."${username}" nixos."${username}"
nixos.zsh nixos.zsh
nixos.docker nixos.docker
nixos.login-text
{ {
step-client.hostname = hostname; networking.hostName = hostname;
step-ssh-host.hostname = hostname;
home-manager.users."${username}" = { home-manager.users."${username}" = {
imports = with inputs.self.modules.homeManager; [ imports = with inputs.self.modules.homeManager; [
sops sops
step-ssh-user
]; ];
shell.program = "zsh"; shell.program = "zsh";
docker.enable = true; docker.enable = true;
step-ssh-user = {
enable = true;
principals = [ "${hostname}" ];
};
ssh.matchSets = { ssh.matchSets = {
certs = true; certs = true;
homelab = true; homelab = true;

View File

@@ -1,80 +1,109 @@
{ inputs, ... }: { inputs, ... }:
let let
username = "john"; username = "john";
hostname = "john-pc-ubuntu"; hostname = "john-pc-ubuntu";
testTarget = "fded:fb16:653e:25da:be24:11ff:fea0:753f"; testTarget = "fded:fb16:653e:25da:be24:11ff:fea0:753f";
in in
{ {
flake.modules.homeManager."${hostname}" = { pkgs, config, ... }: flake.modules.homeManager."${hostname}" = { pkgs, config, ... }:
let let
flakeDir = "${config.xdg.configHome}/home-manager/jsl-dendritic"; flakeDir = "${config.xdg.configHome}/home-manager/jsl-dendritic";
in certDir = "${config.home.homeDirectory}/.step/certs";
{ CACert = "${certDir}/root_ca.crt";
imports = with inputs.self.modules.homeManager; [ tlsKey = "${certDir}/key.pem";
rebuild tlsCert = "${certDir}/cert.pem";
john mtlsCert = "${certDir}/mtls.pem";
resticprofile in
sops {
docker imports = with inputs.self.modules.homeManager; [
desktop rebuild
]; john
targets.genericLinux.enable = true; resticprofile
sops
docker
desktop
step-ssh-user
];
targets.genericLinux.enable = true;
shell.program = "zsh"; shell.program = "zsh";
home.username = "${username}"; home.username = "${username}";
home.homeDirectory = "/home/${username}"; home.homeDirectory = "/home/${username}";
home.packages = with pkgs; [ home.packages = with pkgs; [
nixos-rebuild nixos-rebuild
(writeShellScriptBin "test-push" '' (writeShellScriptBin "test-push" ''
nixos-rebuild switch --flake ${flakeDir}#janus --target-host root@${testTarget} nixos-rebuild switch --flake ${flakeDir}#janus --target-host root@${testTarget}
'') '')
]; (writeShellScriptBin "mtls-generate" ''
# TODO: Add host-specific settings here: ${lib.getExe pkgs.step-cli} ca certificate \
# - sops secret for `restic_password/john_ubuntu` john-pc-ubuntu ${tlsCert} ${tlsKey} \
# - resticprofile profile definition --provisioner admin \
# - zsh RESTIC* session variables --san 192.168.1.85 \
--san spiffe://john-stream.com/ubuntu
cat ${tlsCert} ${tlsKey} > ${mtlsCert}
'')
(writeShellScriptBin "mtls-check" ''
${lib.getExe pkgs.openssl} x509 \
-noout -subject -issuer \
-ext subjectAltName,extendedKeyUsage \
-enddate -in ${mtlsCert}
'')
];
# TODO: Add host-specific settings here:
# - sops secret for `restic_password/john_ubuntu`
# - resticprofile profile definition
# - zsh RESTIC* session variables
# TODO: make this more restrictive, rather than allowing all unfree packages # TODO: make this more restrictive, rather than allowing all unfree packages
nixpkgs.config.allowUnfree = true; nixpkgs.config.allowUnfree = true;
nixpkgs.config.permittedInsecurePackages = [ "openssl-1.1.1w" ]; nixpkgs.config.permittedInsecurePackages = [ "openssl-1.1.1w" ];
homeManagerFlakeDir = flakeDir; homeManagerFlakeDir = flakeDir;
docker.enable = true; docker.enable = true;
ssh.matchSets = {
certs = true;
appdaemon = true;
homelab = true;
dev = true;
};
sops.secrets."restic_password/john_ubuntu" = {
path = "${config.xdg.configHome}/resticprofile/password.txt";
};
programs.resticprofile = {
enable= true;
profiles = {
default = {
"inherit" = "base";
repository = "rest:https://soteria.john-stream.com/john-ubuntu";
# cacert = "${config.home.homeDirectory}/.step/certs/root_ca.crt";
# tls-client-cert = "${config.home.homeDirectory}/.step/certs/mtls.pem";
backup = {
source = [
"${config.xdg.userDirs.documents}"
"/conf"
];
schedule = "*-*-* *:15,30,45:00";
};
};
};
};
};
flake.homeConfigurations."${hostname}" = inputs.home-manager.lib.homeManagerConfiguration { step-ssh-user = {
pkgs = import inputs.nixpkgs { system = "x86_64-linux"; }; enable = true;
modules = with inputs.self.modules; [ principals = ["root" "${username}" "appdaemon"];
homeManager."${hostname}" provisioner = "admin";
]; };
}; ssh = {
certificates.enable = true;
matchSets = {
certs = true;
appdaemon = true;
homelab = true;
dev = true;
};
};
sops.secrets."restic_password/john_ubuntu" = {
path = "${config.xdg.configHome}/resticprofile/password.txt";
};
programs.resticprofile = {
enable= true;
profiles = {
default = {
"inherit" = "base";
repository = "rest:https://soteria.john-stream.com/john-ubuntu";
cacert = "${CACert}";
tls-client-cert = "${mtlsCert}";
backup = {
source = [
"${config.xdg.userDirs.documents}"
"/conf"
];
schedule = "*-*-* *:15,30,45:00";
};
};
};
};
};
flake.homeConfigurations."${hostname}" = inputs.home-manager.lib.homeManagerConfiguration {
pkgs = import inputs.nixpkgs { system = "x86_64-linux"; };
modules = with inputs.self.modules; [
homeManager."${hostname}"
];
};
} }

View File

@@ -8,7 +8,7 @@ in
modules = with inputs.self.modules; [ modules = with inputs.self.modules; [
nixos.lxc nixos.lxc
nixos.sops nixos.sops
nixos.step-client nixos.step-ssh-host
inputs.home-manager.nixosModules.home-manager inputs.home-manager.nixosModules.home-manager
nixos."${username}" nixos."${username}"
nixos.zsh nixos.zsh

View File

@@ -0,0 +1,45 @@
{ inputs, ... }: {
flake.modules.nixos.login-text = { config, ... }: {
programs.rust-motd = {
enable = true;
refreshInterval = "*:0/5";
order = [
"global"
"last_login"
"service_status"
"uptime"
"memory"
"filesystems"
];
settings = {
global = {
time_format = "%Y-%m-%d %H:%M:%S %Z";
};
last_login = {
john = 5;
root = 3;
};
service_status = {
SSH = "sshd.socket";
"SSH Certs" = "step-ssh-host-renew.timer";
Docker = "docker";
};
uptime = {
prefix = "Uptime";
};
memory = {
swap_pos = "beside";
};
filesystems = {
root = "/";
nix = "/nix/store";
};
};
};
};
}

View File

@@ -7,9 +7,9 @@ in
# #
# NixOS Module # NixOS Module
# #
flake.modules.nixos.step-client = { config, pkgs, lib, ... }: flake.modules.nixos.step-ssh-host = { config, pkgs, lib, ... }:
let let
cfg = config.step-client; cfg = config.step-ssh-host;
stepBin = lib.getExe pkgs.step-cli; stepBin = lib.getExe pkgs.step-cli;
rootCertPath = "/etc/step/certs/root_ca.crt"; rootCertPath = "/etc/step/certs/root_ca.crt";
provisionerPasswordPath = config.sops.secrets."janus/admin_jwk".path; provisionerPasswordPath = config.sops.secrets."janus/admin_jwk".path;
@@ -18,7 +18,10 @@ in
in in
{ {
# NixOS Options # NixOS Options
options.step-client = { options.step-ssh-host = {
hostname = lib.mkOption {
type = lib.types.str;
};
caURL = lib.mkOption { caURL = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "${caURL}"; default = "${caURL}";
@@ -32,27 +35,21 @@ in
type = lib.types.str; type = lib.types.str;
default = "admin"; default = "admin";
}; };
hostname = lib.mkOption {
type = lib.types.str;
};
}; };
imports = with inputs.self.modules.nixos; [ ssh ]; imports = with inputs.self.modules.nixos; [ ssh ];
# NixOS Config # NixOS Config
config = { config = {
ssh.certificates.enable = true; ssh.certificates.enable = true;
home-manager.sharedModules = with inputs.self.modules; [
homeManager.step-client
];
sops.secrets."janus/admin_jwk" = { sops.secrets."janus/admin_jwk" = {
owner = "root"; owner = "root";
group = "root"; group = "root";
mode = "0400"; mode = "0400";
}; };
networking.nameservers = [ "192.168.1.150" ];
networking.dhcpcd.extraConfig = "nohook resolv.conf";
environment.etc."step/certs/root_ca.crt".source = cfg.rootCertFile; environment.etc."step/certs/root_ca.crt".source = cfg.rootCertFile;
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
step-cli step-cli
(writeShellScriptBin "ssh-host-cert-renew" '' (writeShellScriptBin "ssh-host-cert-renew" ''
@@ -66,25 +63,69 @@ in
--principal "${cfg.hostname}.john-stream.com" \ --principal "${cfg.hostname}.john-stream.com" \
"${cfg.hostname}" "${sshKeyPath}.pub" "${cfg.hostname}" "${sshKeyPath}.pub"
'') '')
(writeShellScriptBin "ssh-host-cert-check" '' (writeShellScriptBin "ssh-host-cert-check" "${lib.getExe' pkgs.openssh "ssh-keygen"} -Lf ${sshCertPath}")
ssh-keygen -Lf ${sshCertPath}
'')
]; ];
networking.nameservers = [ "192.168.1.150" ];
networking.dhcpcd.extraConfig = "nohook resolv.conf";
systemd.services.step-ssh-host-renew = {
description = "Renew Step SSH host certificate if needed";
wantedBy = [ ];
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
path = [ pkgs.step-cli pkgs.openssh pkgs.coreutils pkgs.systemd ];
serviceConfig = {
Type = "oneshot";
User = "root";
Group = "root";
};
script = ''
set -euo pipefail
if ${stepBin} ssh needs-renewal "${sshCertPath}" --expires-in "4h"; then
echo "Renewing SSH host certificate"
else
rc=$?
if [ "$rc" -eq 1 ]; then
echo "SSH host cert does not need renewal"
exit 0
fi
if [ "$rc" -eq 2 ]; then
echo "SSH host cert missing: ${sshCertPath}" >&2
exit 1
fi
echo "step ssh needs-renewal failed with rc=$rc" >&2
exit "$rc"
fi
'';
};
systemd.timers.step-ssh-host-renew = {
description = "Periodic Step SSH host certificate renewal";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "5m";
OnUnitActiveSec = "4h";
RandomizedDelaySec = "15m";
Persistent = true;
Unit = "step-ssh-host-renew.service";
};
};
}; };
}; };
# #
# Home Manager Module # Home Manager Module
# #
flake.modules.homeManager.step-client = { config, pkgs, lib, ... }: flake.modules.homeManager.step-ssh-user = { config, pkgs, lib, ... }:
let let
cfg = config.step-client; cfg = config.step-ssh-user;
firstPrincipal = lib.head cfg.principals;
principalArgs = lib.concatMapStringsSep " "
(principal: "--principal \"${principal}\"") cfg.principals;
in in
{ {
options.step-client = { options.step-ssh-user = {
enable = lib.mkEnableOption "opionated step client config for SSH certs"; enable = lib.mkEnableOption "opionated step client config for SSH certs";
caURL = lib.mkOption { caURL = lib.mkOption {
type = lib.types.str; type = lib.types.str;
@@ -97,7 +138,7 @@ in
rootCertFile = { rootCertFile = {
path = lib.mkOption { path = lib.mkOption {
type = lib.types.str; type = lib.types.str;
description = "Path to where the root_ca.crt file will be stored for the user"; description = "String path to where the root_ca.crt file will be stored for the user";
default = "${config.home.homeDirectory}/.step/certs/root_ca.crt"; default = "${config.home.homeDirectory}/.step/certs/root_ca.crt";
}; };
source = lib.mkOption { source = lib.mkOption {
@@ -106,14 +147,33 @@ in
default = ../../keys/root_ca.crt; default = ../../keys/root_ca.crt;
}; };
}; };
provisioner = lib.mkOption {
type = lib.types.str;
default = "admin";
};
principals = lib.mkOption {
type = lib.types.listOf lib.types.str;
# default = [ ];
};
}; };
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
home.file.".step/certs/root_ca.crt".source = cfg.rootCertFile; home.file.".step/certs/root_ca.crt".source = cfg.rootCertFile.source;
home.file.".step/config/defaults.json".text = builtins.toJSON { home.file.".step/config/defaults.json".text = builtins.toJSON {
"ca-url" = cfg.caURL; "ca-url" = cfg.caURL;
fingerprint = cfg.fingerprint; fingerprint = cfg.fingerprint;
root = "${cfg.rootCertFile.path}"; root = "${cfg.rootCertFile.path}";
}; };
sops.secrets."janus/admin_jwk".mode = "0400";
home.packages = with pkgs; [
(writeShellScriptBin "sign-ssh-cert" ''
${lib.getExe pkgs.step-cli} ssh certificate \
--sign \
${principalArgs} \
--provisioner "${cfg.provisioner}" \
--provisioner-password-file "${config.sops.secrets."janus/admin_jwk".path}" \
"${firstPrincipal}" "${config.ssh.IdentityFile}.pub"
'')
];
}; };
}; };
} }

View File

@@ -8,6 +8,7 @@ base:
keep-hourly: '8' keep-hourly: '8'
keep-daily: '14' keep-daily: '14'
keep-weekly: '8' keep-weekly: '8'
keep-monthyl: '6'
backup: backup:
verbose: true verbose: true
exclude: exclude:

View File

@@ -74,7 +74,7 @@
sudo ${resticprofileBin} --config "${config.xdg.configHome}/resticprofile/profiles.yaml" $@ sudo ${resticprofileBin} --config "${config.xdg.configHome}/resticprofile/profiles.yaml" $@
''; '';
rpbackupScript = pkgs.writeShellScriptBin "rp-backup" '' rpbackupScript = pkgs.writeShellScriptBin "rp-backup" ''
${lib.getExe rpScript} run-schedule backup@default ${lib.getExe rpScript} run-schedule backup@default "$@"
''; '';
in { in {
programs.resticprofile.package = resticprofilePackage; programs.resticprofile.package = resticprofilePackage;

View File

@@ -1,16 +1,19 @@
{inputs, ... }: {inputs, ... }:
let let
userName = "john"; userName = "john";
sshHostCAPubKey = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNug18oLH0vZxnibXJzMJvTWFPZTnSlhCDDVi+rHhgnIum6ZXQ4SF+VHOOAM5BbzZmMKitNJ5lcrGP15Eur7DzQ=";
in in
{ {
flake.modules.nixos.ssh = { pkgs, config, lib, ... }: flake.modules.nixos.ssh = { pkgs, config, lib, ... }:
let let
cfg = config.ssh; cfg = config.ssh;
userCAPath = "ssh/ssh_user_ca.pub";
hostKeyFile = "ssh/ssh_host_ed25519_key";
in in
{ {
options.ssh = { options.ssh = {
certificates = { certificates = {
enable = lib.mkEnableOption "Enable SSH certificates"; enable = lib.mkEnableOption "Enable SSH host certificates";
userCA = lib.mkOption { userCA = lib.mkOption {
type = lib.types.path; type = lib.types.path;
default = ../../keys/ssh_user_ca.pub; default = ../../keys/ssh_user_ca.pub;
@@ -26,23 +29,27 @@ in
{ {
PasswordAuthentication = false; PasswordAuthentication = false;
KbdInteractiveAuthentication = false; KbdInteractiveAuthentication = false;
HostKey = "/etc/${hostKeyFile}";
} }
(lib.mkIf cfg.certificates.enable { (lib.mkIf cfg.certificates.enable {
TrustedUserCAKeys = "/etc/ssh/ssh_user_ca.pub"; TrustedUserCAKeys = "/etc/${userCAPath}";
HostKey = "/etc/ssh/ssh_host_ed25519_key"; HostCertificate = "/etc/${hostKeyFile}-cert.pub";
HostCertificate = "/etc/ssh/ssh_host_ed25519_key-cert.pub";
}) })
]; ];
}; };
environment.etc."ssh/ssh_user_ca.pub" = lib.mkIf cfg.certificates.enable { environment.etc."${userCAPath}" = lib.mkIf cfg.certificates.enable {
source = cfg.certificates.userCA; source = cfg.certificates.userCA;
}; };
programs.ssh.knownHosts = lib.mkIf cfg.certificates.enable { programs.ssh.knownHosts = lib.mkIf cfg.certificates.enable {
"192.168.1.*" = { "192.168.1.*" = {
certAuthority = true; certAuthority = true;
publicKey = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNug18oLH0vZxnibXJzMJvTWFPZTnSlhCDDVi+rHhgnIum6ZXQ4SF+VHOOAM5BbzZmMKitNJ5lcrGP15Eur7DzQ="; publicKey = sshHostCAPubKey;
};
"*.john-stream.com" = {
certAuthority = true;
publicKey = sshHostCAPubKey;
}; };
}; };
}; };
@@ -58,6 +65,19 @@ in
description = "Path to the SSH identity file."; description = "Path to the SSH identity file.";
}; };
certificates = {
enable = lib.mkEnableOption "Enable SSH user certificates";
# sshCertProvisioner = lib.mkOption {
# type = lib.types.str;
# default = "admin";
# };
};
knownHostsFile = lib.mkOption {
type = lib.types.str;
default = "${config.home.homeDirectory}/.ssh/known_hosts";
};
matchSets = { matchSets = {
appdaemon = lib.mkEnableOption "Enable AppDaemon SSH targets"; appdaemon = lib.mkEnableOption "Enable AppDaemon SSH targets";
certs = lib.mkEnableOption "Enable Janus and Soteria SSH targets"; certs = lib.mkEnableOption "Enable Janus and Soteria SSH targets";
@@ -68,23 +88,21 @@ in
# All this stuff has to be wrapped in a config attribute because of the presence of the options here? # All this stuff has to be wrapped in a config attribute because of the presence of the options here?
config = let config = let
identityFile = config.ssh.IdentityFile; cfg = config.ssh;
identityFile = cfg.IdentityFile;
publicKeyFile = "${identityFile}.pub"; publicKeyFile = "${identityFile}.pub";
certificateFile = "${identityFile}-cert.pub"; certificateFile = "${identityFile}-cert.pub";
userKnownHostsFile = "${config.home.homeDirectory}/.ssh/known_hosts"; provisionerPasswordPath = config.sops.secrets."janus/admin_jwk".path;
in { in {
home.packages = [ home.file.".ssh/known_hosts" = {
(pkgs.writeShellScriptBin "sign-ssh-cert" '' text = lib.concatStringsSep "\n" (
echo "Signing ${publicKeyFile}" [
echo "Copy the Step-CA JWK Provisioner password from 1password" "fded:fb16:653e:25da:be24:11ff:fea0:753f ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ9ZqiWPrCwHjxFCiu0lT4rlQs7KyMapxKJQQ5PJP1eh"
step ssh certificate --sign \ ]
--principal root \ ++ (lib.optional cfg.certificates.enable "@cert-authority 192.168.1.* ${sshHostCAPubKey}")
--principal ${userName} \ ++ (lib.optional cfg.certificates.enable "@cert-authority *.john-stream.com ${sshHostCAPubKey}")
--principal appdaemon \ );
--provisioner admin \ };
${userName} ${publicKeyFile}
'')
];
programs.ssh = { programs.ssh = {
enable = true; enable = true;
@@ -107,13 +125,13 @@ in
inherit identityFile certificateFile; inherit identityFile certificateFile;
hashKnownHosts = false; hashKnownHosts = false;
userKnownHostsFile = "${userKnownHostsFile}"; userKnownHostsFile = cfg.knownHostsFile;
addKeysToAgent = "yes"; addKeysToAgent = "yes";
forwardAgent = false; forwardAgent = false;
}; };
} }
(lib.mkIf config.ssh.matchSets.appdaemon { (lib.mkIf cfg.matchSets.appdaemon {
"appdaemon" = { "appdaemon" = {
hostname = "192.168.1.242"; hostname = "192.168.1.242";
user = "appdaemon"; user = "appdaemon";
@@ -123,7 +141,7 @@ in
user = "appdaemon"; user = "appdaemon";
}; };
}) })
(lib.mkIf config.ssh.matchSets.certs { (lib.mkIf cfg.matchSets.certs {
"janus" = { "janus" = {
hostname = "janus.john-stream.com"; hostname = "janus.john-stream.com";
user = "root"; user = "root";
@@ -133,7 +151,7 @@ in
user = "john"; user = "john";
}; };
}) })
(lib.mkIf config.ssh.matchSets.homelab { (lib.mkIf cfg.matchSets.homelab {
"docs" = { "docs" = {
hostname = "192.168.1.110"; hostname = "192.168.1.110";
user = "root"; user = "root";
@@ -151,7 +169,7 @@ in
user = "panoptes"; user = "panoptes";
}; };
}) })
(lib.mkIf config.ssh.matchSets.dev { (lib.mkIf cfg.matchSets.dev {
"test-nix" = { "test-nix" = {
hostname = "fded:fb16:653e:25da:be24:11ff:fea0:753f"; hostname = "fded:fb16:653e:25da:be24:11ff:fea0:753f";
user = "john"; user = "john";

View File

@@ -12,7 +12,7 @@ in
keygrip = [ keygrip = [
]; ];
authorizedKeys = [ authorizedKeys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIAUa4dcg1TWc4pW++uodyhX4eOqrX/QYIxFWtEP7HFJ john@john-pc-ubuntu" # "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIAUa4dcg1TWc4pW++uodyhX4eOqrX/QYIxFWtEP7HFJ john@john-pc-ubuntu"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMOkGLo4N/L3RYvaIZ1FmePlxa1HK0fMciZxKtRhN58F root@janus" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMOkGLo4N/L3RYvaIZ1FmePlxa1HK0fMciZxKtRhN58F root@janus"
]; ];
}; };