From 7cfda39c2c0a42efb69c7c70f9c309d14bee6296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ryan4yin=20=7C=20=E4=BA=8C=E8=8A=B1?= Date: Fri, 6 Nov 2026 03:26:59 -0600 Subject: [PATCH] feat: log in and sudo over SSH with a CanoKey touch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CanoKey resident SSH key (`ryan@canokey-moe`, the backup once a new primary key is enrolled) is now authorized for ryan and root on every host, and sudo on server and agent hosts accepts its touch through the forwarded agent (`pam_rssh`). Without the key or a forwarded agent, sudo asks for the password and login falls back to the file keys. Only `canokeySshKeys` can satisfy sudo. Risks to weigh: - Colmena deploys need one touch per host connection, since the agent offers the CanoKey key first. - Desktops now forward the agent to every `hostsAddr` alias, as they already did for `192.168.*`; while a session is open, root there can request signatures, each needing a touch. ### Verification - `just test` exits 0; CI passed on the first commit. - Evaluated: sudo PAM stack `rssh` → `unix` → `deny` on shoryu, kana, akane and k3s-test-1-master-1, ai-niri keeps `pam_u2f`; shoryu root and ryan list `ryan@canokey-moe`. - Not yet verified on a running host. ### Manual step after merge Deploy the servers, then `just niri`; close old multiplexed connections (`ssh -O exit `) and try `sudo true` over SSH. AI assistance: DeepSeek Harness (claude-opus-5-5). Assisted-by: DeepSeek Harness (claude-opus-5-5) --- docs/SECURITY.md | 32 ++++++------- docs/security-keys.md | 62 +++++++++++++++---------- home/base/tui/ssh.nix | 10 +++- modules/nixos/server/server-aarch64.nix | 1 + modules/nixos/server/server.nix | 1 + modules/nixos/server/ssh-agent-sudo.nix | 20 ++++++++ vars/default.nix | 15 +++++- 7 files changed, 98 insertions(+), 43 deletions(-) create mode 100644 modules/nixos/server/ssh-agent-sudo.nix diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 82efe44f..89bfaa29 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -33,19 +33,19 @@ small, reversible controls and narrow exceptions; benchmark before adopting expe ## Architecture and trust boundaries -| Boundary | Current design | Limitation | -| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Administration | Key-based SSH; root key login retained for Colmena/MicroVM deployment; only keys declared in Nix are honoured, and no password or keyboard-interactive login; remote-builder host keys only reach `nix-daemon` | Deployment credentials are root-equivalent file keys, usable by any same-user process through the desktop SSH agent; one stolen key reaches every host | -| Host network | Shared nftables firewall trusts the home LAN, tailnet interface and local Podman bridge; IPv6 link-local is accepted | These are broad trust zones, not service-level least privilege; router exposure and tailnet ACLs require separate verification | -| Monitoring | node/smartctl exporters bind the host LAN IPv4 (9100/9633); firewall rules restrict exporter ports 9100, 9835 and 9633 to the monitoring host over IPv4. RAPL counters are exposed only on x86 server hosts, to `node-exporter` through the `rapl` group. | nvidia_gpu_exporter (9835) still binds wildcard; the IPv6 denial for those three ports is in the shared firewall; current running hosts require separate verification. The node_exporter account can measure package energy for local side channels; interactive users, desktops, and guests keep the kernel-default root-only permission. | -| VM hosts | shoryu, shushou and youko run guests behind `br0`; ruby/kana are libvirt guests | A bridge is not network isolation; host compromise affects its guests | -| k3s | Three control-plane and three worker MicroVMs; Cilium, API VIP and NFS CSI | Cluster-admin, privileged/host-mounted pods and writable NFS data are powerful trust boundaries | -| MicroVM storage | Host store shared read-only through virtiofs; guest `/etc`, `/var` and `/home` persisted in separate images | Read-only store does not isolate the host-side virtiofs implementation or protect writable guest state | -| Desktop apps | Selected apps use nixpak/bubblewrap; Podman is rootless | Rules and shared directories vary per app; user namespaces are needed for these sandboxes | -| Security keys | idols-ai/shoukei use a CanoKey for touch-based sudo (pam_u2f, password fallback), one resident `ed25519-sk` SSH key for GitHub and all hosts, FIDO2 LUKS unlock on shoukei and on-card OpenPGP subkeys; key management happens only in the phone app ([setup](./security-keys.md)) | Touch-only sudo can be triggered by any same-user process; per-host file SSH keys and passphrases stay valid as fallback, so their weaknesses remain until retired | -| Secrets | agenix declarations here, encrypted data in the private nix-secrets repository | Recipients and plaintext file permissions must be reviewed; root on a recipient can read its decrypted secrets | -| Supply chain | Locked flake inputs; explicit binary-cache trust keys on NixOS; normal user is not a Nix trusted-user | A trusted cache or imported Nix module can supply code executed as root; a lock file is not a security audit | -| Recovery | NixOS generations, btrbk/restic, hourly read-only snapshots of the REST store, and documented backup procedures | A rollback does not undo compromised data or secrets; backup configuration alone does not prove restore works; the offsite immutable copy is still planned | +| Boundary | Current design | Limitation | +| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Administration | Key-based SSH; root key login retained for Colmena/MicroVM deployment; only keys declared in Nix are honoured, and no password or keyboard-interactive login; remote-builder host keys only reach `nix-daemon` | Deployment credentials are root-equivalent file keys, usable by any same-user process through the desktop SSH agent; one stolen key reaches every host | +| Host network | Shared nftables firewall trusts the home LAN, tailnet interface and local Podman bridge; IPv6 link-local is accepted | These are broad trust zones, not service-level least privilege; router exposure and tailnet ACLs require separate verification | +| Monitoring | node/smartctl exporters bind the host LAN IPv4 (9100/9633); firewall rules restrict exporter ports 9100, 9835 and 9633 to the monitoring host over IPv4. RAPL counters are exposed only on x86 server hosts, to `node-exporter` through the `rapl` group. | nvidia_gpu_exporter (9835) still binds wildcard; the IPv6 denial for those three ports is in the shared firewall; current running hosts require separate verification. The node_exporter account can measure package energy for local side channels; interactive users, desktops, and guests keep the kernel-default root-only permission. | +| VM hosts | shoryu, shushou and youko run guests behind `br0`; ruby/kana are libvirt guests | A bridge is not network isolation; host compromise affects its guests | +| k3s | Three control-plane and three worker MicroVMs; Cilium, API VIP and NFS CSI | Cluster-admin, privileged/host-mounted pods and writable NFS data are powerful trust boundaries | +| MicroVM storage | Host store shared read-only through virtiofs; guest `/etc`, `/var` and `/home` persisted in separate images | Read-only store does not isolate the host-side virtiofs implementation or protect writable guest state | +| Desktop apps | Selected apps use nixpak/bubblewrap; Podman is rootless | Rules and shared directories vary per app; user namespaces are needed for these sandboxes | +| Security keys | idols-ai/shoukei use a CanoKey for touch-based sudo (pam_u2f, password fallback; servers via pam_rssh and the forwarded agent), one resident `ed25519-sk` SSH key for GitHub and all hosts, FIDO2 LUKS unlock on shoukei and on-card OpenPGP subkeys; key management happens only in the phone app ([setup](./security-keys.md)) | Touch-only sudo can be triggered by any same-user process, and on a server by root through the forwarded agent; per-host file SSH keys and passphrases stay valid as fallback, so their weaknesses remain until retired | +| Secrets | agenix declarations here, encrypted data in the private nix-secrets repository | Recipients and plaintext file permissions must be reviewed; root on a recipient can read its decrypted secrets | +| Supply chain | Locked flake inputs; explicit binary-cache trust keys on NixOS; normal user is not a Nix trusted-user | A trusted cache or imported Nix module can supply code executed as root; a lock file is not a security audit | +| Recovery | NixOS generations, btrbk/restic, hourly read-only snapshots of the REST store, and documented backup procedures | A rollback does not undo compromised data or secrets; backup configuration alone does not prove restore works; the offsite immutable copy is still planned | Sources: [host layout](../hosts/README.md), [k3s deployment](../hosts/k8s/README.md), [firewall](../modules/nixos/base/networking/firewall.nix), [SSH](../modules/nixos/base/ssh.nix), @@ -322,9 +322,9 @@ isolation. Source review and socket metadata—not reads of private credentials - The current desktop session's SSH-agent socket is owned by and writable to the current UID. Same-user subprocesses can potentially use its signing interface; read-only mounts do not prevent socket use. Private-key bytes were not inspected and no signing operation was requested. -- `ssh -G` reports forwarding for explicit `192.168.*` destinations, but not the sampled `shoryu` - alias or GitHub destination. Do not treat forwarding as enabled for every LAN alias, or remove it - without checking deployment/build workflows. +- Agent forwarding is on for `192.168.*` destinations and the `hostsAddr` aliases (servers use it + for pam_rssh sudo), not for GitHub or other hosts. Do not remove it without checking + deployment/build workflows. - All nine desktop wrappers use shared nixpak policies: cleared environment, filtered session D-Bus and selected mounts. The [permission inventory](../hardening/README.md#app-permissions) lists each app's file, network, credential and media grants. diff --git a/docs/security-keys.md b/docs/security-keys.md index 11b69e34..3e73396c 100644 --- a/docs/security-keys.md +++ b/docs/security-keys.md @@ -4,7 +4,7 @@ How the CanoKey (or a YubiKey) is set up and used on the desktops: initial setup key, and recovery. Commands here are the single source; the Nix files only point to this page. Scope: **idols-ai** and **shoukei** (`modules.desktop.canokey.enable`). frieren only shares the -GnuPG `scdaemon` setting; servers never use a key. +GnuPG `scdaemon` setting. Servers never see a key; they only ask the forwarded agent for sudo. ## Principle @@ -18,13 +18,14 @@ Management and use happen on different devices. is unreachable from them ([hardening](../hardening/README.md)). Unsandboxed software is trusted; if it is malicious, the PC is compromised regardless. -| Use | Config | Daily use | Fallback without the key | -| ------------ | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------- | ----------------------------------------- | -| Browsers | [nixpak FIDO2 passthrough](../hardening/nixpaks/default.nix) | FIDO2 PIN and/or touch, as the site asks | site recovery codes, other registered key | -| sudo | [canokey.nix](../modules/nixos/desktop/canokey.nix), registrations in [vars](../vars/default.nix) `canokeyU2fKeys` | touch | password | -| SSH | [ssh.nix](../home/base/tui/ssh.nix), public keys in [vars](../vars/default.nix) `mainSshAuthorizedKeys` | touch per connection | per-host file keys | -| shoukei LUKS | [hardware-configuration.nix](../hosts/12kingdoms-shoukei/hardware-configuration.nix) `crypttabExtraOpts` | FIDO2 PIN + touch | passphrase (prompted after 10 s) | -| pass / GPG | [gpg](../home/base/tui/gpg/default.nix) `scdaemonSettings.disable-ccid` | OpenPGP PIN once per plug-in, then touch | off-card subkey backup | +| Use | Config | Daily use | Fallback without the key | +| ------------ | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | ----------------------------------------- | +| Browsers | [nixpak FIDO2 passthrough](../hardening/nixpaks/default.nix) | FIDO2 PIN and/or touch, as the site asks | site recovery codes, other registered key | +| sudo | [canokey.nix](../modules/nixos/desktop/canokey.nix), registrations in [vars](../vars/default.nix) `canokeyU2fKeys` | touch | password | +| sudo via SSH | [ssh-agent-sudo.nix](../modules/nixos/server/ssh-agent-sudo.nix), keys in [vars](../vars/default.nix) `canokeySshKeys` | touch | password | +| SSH | [ssh.nix](../home/base/tui/ssh.nix), public keys in [vars](../vars/default.nix) `mainSshAuthorizedKeys` | touch per connection | per-host file keys | +| shoukei LUKS | [hardware-configuration.nix](../hosts/12kingdoms-shoukei/hardware-configuration.nix) `crypttabExtraOpts` | FIDO2 PIN + touch | passphrase (prompted after 10 s) | +| pass / GPG | [gpg](../home/base/tui/gpg/default.nix) `scdaemonSettings.disable-ccid` | OpenPGP PIN once per plug-in, then touch | off-card subkey backup | Login (greetd) stays password-based: the password also unlocks GNOME Keyring. @@ -97,6 +98,7 @@ smartcard-backed OpenPGP operations; the sound comes from Noctalia's notificatio | -------------------------------------- | -------- | ----------------------------------------------------- | | sudo (pam-u2f) | yes | CTAPHID keepalive / U2F `0x6985` from the FIDO hidraw | | ssh/scp/rsync/git over ssh (FIDO2 key) | yes | same | +| sudo on a server (pam_rssh) | yes | same; the forwarded agent signs on the local key | | WebAuthn in browsers | yes | same; browsers read the same hidraw interface | | pass / GPG decrypt and sign | yes | gpg-agent proxy; heuristic, see below | | PIV / OATH | no | the detector has no source for those applets | @@ -141,12 +143,24 @@ empty, sudo is unchanged. Every host shares the origin `pam://ryan4yin`, so one registration covers idols-ai and shoukei. +### sudo via SSH + +Servers and agent hosts (`modules/nixos/server/ssh-agent-sudo.nix`) run pam_rssh: sudo there sends a +challenge back through the forwarded agent, and the CanoKey signs it after a touch. Nothing to +enroll beyond the SSH key below. Only the `canokeySshKeys` count, so a file key in the agent cannot +satisfy sudo. Agent forwarding is on for `192.168.*` and the `hostsAddr` aliases +([ssh.nix](../home/base/tui/ssh.nix)); without a forwarded agent, or with the key unplugged, sudo +asks for the password. + +While a session is open, root on that server can also use the forwarded agent. Each signature still +needs a touch, so an unexpected blink is the signal to refuse it. + ### SSH One SSH key, used for GitHub and every host, lives on the key as a resident credential: ```bash -ssh-keygen -t ed25519-sk -O resident -O application=ssh:ryan4yin -C ryan@canokey +ssh-keygen -t ed25519-sk -O resident -O application=ssh:ryan4yin -C ryan@canokey- ``` This writes `~/.ssh/id_ed25519_sk` on the machine that runs it. On each other desktop, export the @@ -174,10 +188,10 @@ How it works, and what the prompts mean: connection: `AddKeysToAgent yes` keeps the unlocked handle in the agent, which still asks the key for each signature. -Add the public key to `mainSshAuthorizedKeys` in [vars/default.nix](../vars/default.nix) and to -GitHub. GitHub tries `id_ed25519_sk` before the per-host key; other hosts try it as a default -identity. Keys already loaded in `ssh-agent` are offered first, so the per-host keys can still win -until they are retired. +Add the public key to `canokeySshKeys` in [vars/default.nix](../vars/default.nix), which is part of +`mainSshAuthorizedKeys`, and to GitHub. GitHub tries `id_ed25519_sk` before the per-host key; other +hosts try it as a default identity. Keys already loaded in `ssh-agent` are offered first, so the +per-host keys can still win until they are retired. ### shoukei LUKS @@ -227,27 +241,27 @@ it is. On a YubiKey, manage settings with Yubico Authenticator or `ykman`; OpenP | Device | Section 1 again | | Browsers | Register it on every site as an additional security key | | sudo | `pamu2fcfg --nouser --origin=pam://ryan4yin` with only the backup plugged in; append to `canokeyU2fKeys` | -| SSH | Create a resident key on it with the same command; add its public key to `mainSshAuthorizedKeys` and GitHub. Use it with `ssh-add -K` (loads resident keys into the agent) instead of exporting a file | +| SSH | Create a resident key on it with the same command; add its public key to `canokeySshKeys` and GitHub. Use it with `ssh-add -K` (loads resident keys into the agent) instead of exporting a file | | shoukei LUKS | Run the same `systemd-cryptenroll --fido2-device=auto` with only the backup plugged in; it takes the next slot | | OpenPGP | Import the subkeys from the backup into a fresh, temporary `GNUPGHOME`, `keytocard` them onto the backup key, then delete that `GNUPGHOME`. The stubs remember the card serial; after swapping cards run `gpg-connect-agent "scd serialno" "learn --force" /bye` | ## 4. Lost key or reset -| Situation | Action | Lost | -| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | -| Key lost | Remove its `canokeyU2fKeys` entry and SSH public key; `systemd-cryptenroll --wipe-slot=`; remove it on each site | nothing, if a backup key or the passwords remain | -| FIDO2 PIN forgotten or locked | Phone app: reset the WebAuthn application (needs the Admin PIN) | all passkeys, the SSH resident key, LUKS and sudo enrollments; redo section 2 | -| OpenPGP PIN locked | `gpg --card-edit` → `admin` → `passwd`, unblock with the OpenPGP Admin PIN or Reset Code | nothing | -| All OpenPGP PINs and Reset Code lost | Phone app: reset the OpenPGP application | on-card subkeys; re-import from the backup | -| Admin PIN forgotten | Exhaust the Admin PIN, then in the app tap Reset and touch the key while the LED blinks, repeating until it stops | all user data; serial and attestation keys stay | +| Situation | Action | Lost | +| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | +| Key lost | Remove its `canokeyU2fKeys` entry and SSH public keys; `systemd-cryptenroll --wipe-slot=`; remove it on each site | nothing, if a backup key or the passwords remain | +| FIDO2 PIN forgotten or locked | Phone app: reset the WebAuthn application (needs the Admin PIN) | all passkeys, the SSH resident key, LUKS and sudo enrollments; redo section 2 | +| OpenPGP PIN locked | `gpg --card-edit` → `admin` → `passwd`, unblock with the OpenPGP Admin PIN or Reset Code | nothing | +| All OpenPGP PINs and Reset Code lost | Phone app: reset the OpenPGP application | on-card subkeys; re-import from the backup | +| Admin PIN forgotten | Exhaust the Admin PIN, then in the app tap Reset and touch the key while the LED blinks, repeating until it stops | all user data; serial and attestation keys stay | After any reset, rerun sections 1 and 2 for the affected applications. ## Enrollment status -| Key | Firmware | sudo | SSH | shoukei LUKS | OpenPGP | Browsers | -| ------------------------ | -------- | ----------------- | ---------------------------------------------------------- | ----------------------------- | ----------- | -------------------------------- | -| CanoKey Canary (primary) | 3.0.3 | works on idols-ai | key created; public key not yet in `mainSshAuthorizedKeys` | enrolled; unlock not verified | on the card | WebAuthn works in Firefox/Chrome | +| Key | Firmware | sudo | SSH | shoukei LUKS | OpenPGP | Browsers | +| -------------------------------------- | -------- | ----------------- | ------------------------------------------------------- | ----------------------------- | ----------- | -------------------------------- | +| CanoKey Canary (backup, `canokey-moe`) | 3.0.3 | works on idols-ai | public key in `canokeySshKeys`; host login not verified | enrolled; unlock not verified | on the card | WebAuthn works in Firefox/Chrome | shoukei's first boots with the FIDO2 slot asked for the PIN, then fell back to the passphrase: USB only came up after switch-root, because on linux-asahi 7.2 its dwc3/PHY depend on the Thunderbolt diff --git a/home/base/tui/ssh.nix b/home/base/tui/ssh.nix index c3a1dcb2..1a03b1c4 100644 --- a/home/base/tui/ssh.nix +++ b/home/base/tui/ssh.nix @@ -65,7 +65,15 @@ in } // lanHost; } - // lib.genAttrs (builtins.attrNames myvars.networking.hostsAddr) (_: lanHost); + # Aliases forward the agent like their addresses do; servers ask it for sudo + # (modules/nixos/server/ssh-agent-sudo.nix). + // lib.genAttrs (builtins.attrNames myvars.networking.hostsAddr) ( + _: + { + ForwardAgent = true; + } + // lanHost + ); }; # Keep ~/.ssh/config a real, user-owned file rather than a /nix/store symlink. diff --git a/modules/nixos/server/server-aarch64.nix b/modules/nixos/server/server-aarch64.nix index dbc768a9..5792d407 100644 --- a/modules/nixos/server/server-aarch64.nix +++ b/modules/nixos/server/server-aarch64.nix @@ -15,6 +15,7 @@ ../base/packages.nix ../base/ssh.nix ../base/user-group.nix + ./ssh-agent-sudo.nix # This server imports base files individually instead of the whole # modules/nixos/base group, so pull the shared firewall in explicitly. diff --git a/modules/nixos/server/server.nix b/modules/nixos/server/server.nix index 62bb5f15..589d6954 100644 --- a/modules/nixos/server/server.nix +++ b/modules/nixos/server/server.nix @@ -3,6 +3,7 @@ imports = [ ../base ../nushell-login.nix + ./ssh-agent-sudo.nix ../../base ]; diff --git a/modules/nixos/server/ssh-agent-sudo.nix b/modules/nixos/server/ssh-agent-sudo.nix new file mode 100644 index 00000000..bd28c932 --- /dev/null +++ b/modules/nixos/server/ssh-agent-sudo.nix @@ -0,0 +1,20 @@ +{ + lib, + pkgs, + myvars, + ... +}: +{ + # sudo over SSH accepts a CanoKey touch through the forwarded agent; without + # it, sudo asks for the password as before. Only the hardware-backed keys are + # trusted, so a file key loaded in the agent cannot sign for sudo silently. + # Setup and limits: docs/security-keys.md. + security.pam.rssh = { + enable = true; + settings = { + auth_key_file = "${pkgs.writeText "sudo-ssh-agent-keys" (lib.concatLines myvars.canokeySshKeys)}"; + cue = true; + }; + }; + security.pam.services.sudo.rssh = true; +} diff --git a/vars/default.nix b/vars/default.nix index 1d28ef01..0202774f 100644 --- a/vars/default.nix +++ b/vars/default.nix @@ -1,5 +1,15 @@ { lib }: +let + # CanoKey resident SSH keys: they log in like the main keys, and they are the + # only keys pam_rssh accepts for sudo on servers through the forwarded agent + # (modules/nixos/server/ssh-agent-sudo.nix). + canokeySshKeys = [ + # CanoKey Canary (backup) + "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIKgfNy/wJgTw6auO4RmRHR9kh1Mt5TYNkomMDFLJ4Kw6AAAADHNzaDpyeWFuNHlpbg== ryan@canokey-moe" + ]; +in { + inherit canokeySshKeys; username = "ryan"; userfullname = "Ryan Yin"; useremail = "xiaoyin_c@qq.com"; @@ -28,11 +38,12 @@ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIKlN+Q/GxvwxDX/OAjJHaNFEznEN4Tw4E4TwqQu/eD6 ryan@idols-ai" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJwoI5MAogEa726jwwHL5EgM1X/i2A5d2pgV7i7t8fzB ryan@shoukei" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDc1PNTXzzvd93E+e9LXvnEzqgUI5gMTEF/IitvzgmL+ ryan@frieren" - ]; + ] + ++ canokeySshKeys; # CanoKey U2F registrations for sudo (modules/nixos/desktop/canokey.nix). # Key handles and public keys only, so they are safe to commit. canokeyU2fKeys = [ - # CanoKey Canary (primary) + # CanoKey Canary (backup) "DwXDxVU+NDTUZs/d2EjpsPAh4mrg7dqdg/mAozkw/mUAAdEYDZIbm8kTBUGA49wXW0ZvSpF9VNYh2w84Xz9nAIAW////+Q==,6hqEjWBJ+ueBKBJaNijNkUsmlgEHMgv0KdpprqdJjHKoU/FKpeqo12SWlvdgiIVWvqTaGfe4fNu/ckcGY5mlsA==,es256,+presence" ]; secondaryAuthorizedKeys = [