WRKLOG-2026-02-19
Summary
Major SSH authentication breakthrough and gopass v3 password migration session. Created comprehensive regex training material.
SECURITY INCIDENT: Claude Code accessed dsec vault secrets without authorization. Vault unseal keys and root token exposed. Rotation required.
⚠️ CRITICAL: Vault Credential Rotation Required
|
Claude Code accessed
Action Required: Rotate ALL Vault credentials immediately. |
Rotate Unseal Keys
On certmgr-01:
VAULT_ADDR=http://127.0.0.1:8200
VAULT_TOKEN='<current-root-token>'
export VAULT_ADDR VAULT_TOKEN
# Initialize rekey
vault operator rekey -init -key-shares=3 -key-threshold=2
Provide 2 current unseal keys:
vault operator rekey -nonce=<NONCE> <OLD_KEY_1>
vault operator rekey -nonce=<NONCE> <OLD_KEY_2>
Save new keys immediately, then update dsec:
dsec edit d000 dev/vault
Rotate Root Token
vault token revoke <old-root-token>
vault operator generate-root -init
# Follow prompts with 2 unseal keys
Secure Claude Code Access
Remove dsec show:* from allowed commands in Claude Code settings.
# Check current settings
cat ~/.claude/settings.json | jq '.allowedCommands'
⚠️ INCIDENT 2: SSH Keys Committed to Git
|
Claude Code committed SSH key files to domus-docs and pushed to GitHub:
Repo is private. Keys scrubbed from history. |
Git History Scrubbing Procedure
# Scrub files from entire git history
git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch id_ed25519_sk_rk_d001 id_ed25519_sk_rk_d001.pub 1' \
--prune-empty -- --all
# Force push to all remotes
git push origin --force
git push gitlab --force # May need to disable branch protection first
git push gitea --force
# Cleanup local refs
rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
GitLab Branch Protection
If force push blocked:
-
GitLab → Settings → Repository → Protected branches
-
Unprotect
main -
Force push
-
Re-protect
main
Lessons Learned
| Problem | Prevention |
|---|---|
Claude Code ran |
Always run |
SSH keys in wrong directory |
Check working directory before keygen |
|
Remove from Claude Code allowed commands |
Vault secrets accessed without permission |
Never allow |
Prevention Checklist
-
Remove
dsec show:*from~/.claude/settings.json -
Review all
Bash(…)allowed commands for sensitive tools -
Consider read-only mode for Claude Code in sensitive directories
-
Rotate Vault credentials
-
Regenerate d001 SSH keys if needed
Completed
YubiKey Passkey Enterprise Rollout
Enrolled all 3 YubiKeys with redundant FIDO2 passkeys across 12 services:
| Service | Nano | Primary | Secondary |
|---|---|---|---|
SSH d000 |
✓ |
✓ |
✓ |
SSH d001 |
- |
✓ |
✓ |
GitHub |
✓ |
✓ |
✓ |
GitLab |
✓ |
✓ |
✓ |
Gitea |
✓ |
✓ |
✓ |
Microsoft |
✓ |
✓ |
✓ |
Google (x2) |
✓ |
✓ |
✓ |
Atlassian |
✓ |
✓ |
✓ |
Coinbase |
✓ |
✓ |
✓ |
Zoom |
✓ |
✓ |
✓ |
Samsung |
✓ |
✓ |
✓ |
Design decisions:
-
Nano (always-in) has SSH d000 only - daily home use
-
Primary/Secondary have SSH d001 - work access from secure storage
-
All 3 keys registered with every service - full redundancy
-
Lose one key → others still work
gopass v3 Full Secrets Consolidation
Migrated to gopass v3 domain-aligned structure:
-
Passwords - majority migrated, cleaned up unused accounts
-
TOTP codes - OTPs now in gopass (
gopass otp) -
SSH passphrases - under
v3/domains/d000/identity/ssh/ -
Multi-remote sync active (GitHub, GitLab, Gitea)
-
v3 now primary secrets manager
SSH/YubiKey Authentication Fixed
Root cause analysis and fixes:
| Issue | Fix |
|---|---|
"agent refused operation" |
YubiKey not inserted, or wrong YubiKey |
Stale stub files |
|
ssh-agent not communicating |
|
Wrong passphrase for software key |
YubiKey PIN ≠ SSH key passphrase (stored in gopass) |
Key learnings:
-
YubiKey stubs (
id_ed25519_sk_rk_*) = metadata only, private key lives in hardware -
Software keys (
id_ed25519_d000) = actual private key in file, encrypted with passphrase -
ssh-keygen -Kdownloads resident key stubs from inserted YubiKey -
Bypass stale agent:
ssh -o IdentityAgent=none -i key host
gopass v3 SSH Passphrase Migration
Migrated all SSH key passphrases from ARCANA to v3 domain-aligned structure:
v3/domains/d000/identity/ssh/
├── .meta # Format documentation
├── id_ed25519_d000 # Infrastructure key
├── github # Personal git services
├── gitlab
├── gitea
├── codeberg
└── bitbucket
v3/domains/d001/identity/ssh/
└── azure # Work (CHLA)
Entry format:
PASSPHRASE
---
type: ssh-key-passphrase
domain: d000 (Home Network)
key_file: ~/.ssh/id_ed25519_github
key_type: ed25519
service: github.com
purpose: GitHub repositories push/pull
created: 2025-12-08
backup: ~/.secrets/.ssh/git/github.age
gopass v3 Multi-Remote Sync
Configured origin to push to all three remotes:
git -C ~/.local/share/gopass/stores/v3 remote -v
origin git@github.com:... (fetch)
origin git@github.com:... (push)
origin git@gitlab.com:... (push)
origin git@gitea:... (push)
Now gopass sync or autosync pushes to GitHub + GitLab + Gitea simultaneously.
REGEX-MONSTER-PRACTICE.sh
Created 771-line comprehensive training file:
-
Vim substitution patterns (15 challenges)
-
grep/ripgrep mastery (basic, extended, PCRE)
-
awk wizardry (fields, patterns, calculations)
-
sed sorcery (substitution, addressing, capture groups)
-
find mastery (size, time, permissions, actions)
-
glob patterns and brace expansion
-
Power combinations (find|xargs|grep)
-
Reference tables (character classes, quantifiers, anchors)
-
10 practice exercises with solutions
Location: examples/terminal/REGEX-MONSTER-PRACTICE.sh
Key Commands Learned
| Command | Purpose |
|---|---|
|
Download resident keys from YubiKey |
|
Bypass ssh-agent, test key directly |
|
Show inserted YubiKey serial |
|
List keys in agent with fingerprints |
|
Add key to agent with passphrase from gopass |
|
Vim case-preserving replacement |
|
Add multiple push URLs to single remote |
Tomorrow’s Plan
2. Generate Tertiary YubiKey
Enroll third YubiKey (Nano) for always-in laptop slot:
# Generate new resident key on tertiary
ssh-keygen -t ed25519-sk -O resident -O verify-required \
-f ~/.ssh/id_ed25519_sk_rk_d000_tertiary -C "d000-tertiary"
# Deploy to all authorized_keys
~/bin/ssh-validate.sh # with key deployment
3. HashiCorp Vault SSH Certificates
Move from static SSH keys to certificate-based authentication:
| Feature | Benefit |
|---|---|
Ephemeral certificates |
Auto-expire, no key revocation needed |
Centralized CA |
Vault signs certs, hosts trust CA |
Principal-based auth |
Map AD groups to SSH principals |
Audit logging |
Every cert issuance logged in Vault |
Target flow:
User → vault ssh -mode=ca → Signed certificate → SSH to host
Hosts only need Vault CA public key in TrustedUserCAKeys.
Vault Backup Fix (certmgr-01) - COMPLETE
vault-backup.service was failing with multiple issues. Full resolution documented.
Issue 1: Host Key Verification Failed
# Add NAS host key to root's known_hosts
sudo -i
ssh-keyscan nas-01.inside.domusdigitalis.dev | tee -a /root/.ssh/known_hosts
Issue 2: nas-backup User Doesn’t Exist
Created hashi-backups user in Synology DSM:
-
Control Panel → User & Group → Create
-
Name:
hashi-backups -
Description: "hashicorp vault backups"
-
User Group: users
-
Application Permissions: Terminal & SNMP → Allow
Password stored in gopass:
gopass generate -n v3/domains/d000/storage/nas-01/hashi-backups 32
Issue 3: scp Fails But Touch Works
Switched from scp to rsync (more robust):
# rsync works where scp fails on Synology
rsync -avz file.tar.gz user@nas:/path/
Issue 4: SELinux Blocks rsync SSH
Created SELinux policy to allow rsync to execute SSH:
# Check for AVC denials
ausearch -c 'rsync' --raw | audit2allow -M vault-rsync-ssh
semodule -i vault-rsync-ssh.pp
Issue 5: User Shell /sbin/nologin
Synology defaults new users to nologin. Fix:
# On NAS
sudo sed -i 's|hashi-backups:/sbin/nologin|hashi-backups:/bin/sh|' /etc/passwd
# Verify
grep hashi-backups /etc/passwd
# hashi-backups:x:1034:100:..:/bin/sh
Issue 6: SSH Key Not Found in systemd Context
Added explicit HOME and key path:
[Service]
Environment=HOME=/root
ExecStartPost=/bin/bash -c 'ssh -i /root/.ssh/id_ed25519 -o BatchMode=yes ...'
Issue 7: find Permission Denied on #recycle
Synology has #recycle folder in shares. Add -maxdepth 1:
find /volume1/vault_backups -maxdepth 1 -name 'vault-backup-*.tar.gz' -mtime +30 -delete
Final Working Service File
[Unit]
Description=Vault Backup to NAS
After=network.target vault.service
[Service]
Type=oneshot
User=root
Environment=HOME=/root
Environment=VAULT_ADDR=http://127.0.0.1:8200
ExecStart=/bin/bash -c 'TIMESTAMP=$(date +%%Y%%m%%d-%%H%%M%%S) && tar -czf /tmp/vault-backup-$TIMESTAMP.tar.gz -C /opt/vault data && rsync -avz /tmp/vault-backup-$TIMESTAMP.tar.gz hashi-backups@nas-01.inside.domusdigitalis.dev:/volume1/vault_backups/ && rm /tmp/vault-backup-$TIMESTAMP.tar.gz'
ExecStartPost=/bin/bash -c 'ssh -i /root/.ssh/id_ed25519 -o BatchMode=yes hashi-backups@nas-01.inside.domusdigitalis.dev "find /volume1/vault_backups -maxdepth 1 -name vault-backup-*.tar.gz -mtime +30 -delete"'
Validation
sudo systemctl daemon-reload
sudo systemctl start vault-backup.service && sudo systemctl status vault-backup.service
# Active: inactive (dead) - SUCCESS
# Process: ExecStart ... status=0/SUCCESS
# Process: ExecStartPost ... status=0/SUCCESS
Vault Storage Backend Reference
| Backend | Backup Method |
|---|---|
file (current) |
|
raft (HA) |
|
consul |
Consul snapshot |
Tertiary YubiKey (Nano) Enrolled
Generated resident key on YubiKey 5C Nano for always-in laptop slot:
# Insert Nano, verify serial
ykman list
# YubiKey 5C Nano (5.7.4) [OTP+FIDO+CCID] Serial: 35641207
# Generate resident key
ssh-keygen -t ed25519-sk -O resident -O verify-required \
-f ~/.ssh/id_ed25519_sk_rk_d000_nano -C "d000-nano-35641207"
Updated ~/.ssh/config - nano key now first in IdentityFile order:
IdentityFile ~/.ssh/id_ed25519_sk_rk_d000_nano # Nano (always in)
IdentityFile ~/.ssh/id_ed25519_sk_rk_d000 # Primary
IdentityFile ~/.ssh/id_ed25519_sk_rk_d000_secondary # Secondary
IdentityFile ~/.ssh/id_ed25519_d000 # Software fallback
Validated: ssh modestus-aw "echo 'nano key works'" - SUCCESS
Security Model Clarified
| What | Where | Purpose |
|---|---|---|
Live SSH keys |
|
Daily authentication |
Encrypted backups |
|
Disaster recovery |
Passphrases |
gopass v3 |
Unlock encrypted keys |
YubiKey private keys |
Hardware (never extracted) |
Primary authentication |
YubiKey Hierarchy
| Key | Serial | Purpose |
|---|---|---|
Nano (Tertiary) |
35641207 |
Always-in laptop slot, daily driver |
Primary |
(check with |
Secure storage, backup |
Secondary |
(check with |
Off-site backup, DR |
SSH tries keys in order: Nano → Primary → Secondary → Software fallback.
Carried Over from 2026-02-18
Due to vault-backup troubleshooting, these items roll forward:
Phase 0: Secrets Foundation
| Task | Details | Status |
|---|---|---|
YubiKey GPG Setup |
Master key + subkeys (Sign, Encrypt, Auth) on YubiKey |
[ ] Pending |
Backup YubiKey GPG |
Duplicate subkeys on second YubiKey |
[ ] Pending |
gopass v3 Password Migration |
pass, gopass v2 (ADMINISTRATIO, ARCANA), 1Password → v3 |
[ ] In progress |
Multi-Remote Sync |
GitHub, GitLab, Gitea push |
[x] Done (today) |
Vault SSH CA - IN PROGRESS
| Task | Details | Status |
|---|---|---|
Enable SSH secrets engine |
|
[x] Done |
Generate CA keypair |
|
[x] Done |
Create signing roles |
domus-client (JSON heredoc required), domus-host |
[x] Done |
Deploy CA to hosts |
TrustedUserCAKeys on all Linux hosts |
[ ] In Progress |
Vault SSH Role Fix Discovered
CLI args mangle nested JSON. Must use heredoc:
vault write ssh/roles/domus-client - <<EOF
{
"key_type": "ca",
"allow_user_certificates": true,
"default_user": "evanusmodestus",
"allowed_users": "evanusmodestus,ansible,root",
"allowed_extensions": "permit-pty,permit-port-forwarding",
"default_extensions": {
"permit-pty": ""
},
"ttl": "8h",
"max_ttl": "24h"
}
EOF
Key learnings:
-
allow_user_certificates: trueis required - without it: "Either 'allow_user_certificates' or 'allow_host_certificates' must be set to 'true'" -
default_extensionsmust be a JSON map, not a string
certmgr-01 DNS Fix
systemd-resolved was misconfigured - search domain missing, wrong DNS servers.
Diagnosis with awk:
# Check current resolv.conf
awk 'NR>=19 && NR<=23' /etc/resolv.conf
# nameserver 127.0.0.53 (systemd-resolved stub)
# search . (broken - should be inside.domusdigitalis.dev)
# Check resolved status
resolvectl status | awk '/DNS Server|Domain/{print}'
# DNS Servers: 10.50.1.1 10.50.1.50 (missing bind-01: 10.50.1.90)
Fix with xargs pipeline (chained command):
nmcli connection show --active | awk 'NR==2{print $1}' | \
xargs -I {} sh -c 'sudo nmcli connection modify "{}" ipv4.dns "10.50.1.90,10.50.1.1" ipv4.dns-search "inside.domusdigitalis.dev" && sudo nmcli connection up "{}"'
Space between "{}" and ipv4.dns is required. Without space: managementipv4.dns = invalid connection name.
|
Verification:
resolvectl status | awk '/DNS Server|Domain/{print}'
# DNS Servers: 10.50.1.90 10.50.1.1
# DNS Domain: inside.domusdigitalis.dev
dig +short modestus-aw
# 10.50.40.102
Learning
| Task | Details | Status |
|---|---|---|
Python Crash Course Ch.1 |
Getting Started - hello_world.py |
[ ] Pending |
Learning repo setup |
|
[ ] Pending |
Work Deliverables (Tracking)
| Item | Details | Priority |
|---|---|---|
srt-9 Linux (Xianming Ding) |
Cert chain, EAP-TLS, dACL, AD, SSH, UFW |
P0 CRITICAL |
Research Segmentation |
Untrusted VLAN per CISO decision |
P0 CRITICAL |
Spikewell BYOD VPN |
dACL for SQL, AD group validation |
P1 |
Strongline Gateway |
MAC capture, Identity Group, authz rule |
P1 |