diff options
| author | Arpit Chakladar <arpitchakladar@gmail.com> | 2026-09-10 19:44:00 +0530 |
|---|---|---|
| committer | Arpit Chakladar <arpitchakladar@gmail.com> | 2026-09-10 19:44:00 +0530 |
| commit | e483a7d255fc27aea1d1d1a7963ea4b93ad0c47e (patch) | |
| tree | d061db5411de504d2d5570a4958a1352516a1b3b | |
| parent | f1787032594f7ca1a6521f5665a48bb70605d222 (diff) | |
| parent | bdee6f183cd38ff7539618336c5448a10f432aae (diff) | |
| download | home-manager-config-e483a7d255fc27aea1d1d1a7963ea4b93ad0c47e.tar.gz home-manager-config-e483a7d255fc27aea1d1d1a7963ea4b93ad0c47e.zip | |
Merge branch 'fixing-ssh-agent'
| -rw-r--r-- | modules/security/gopass/creation-templates/0-website.yml | 27 | ||||
| -rw-r--r-- | modules/security/gopass/creation-templates/1-wifi.yml | 34 | ||||
| -rw-r--r-- | modules/security/gopass/creation-templates/2-access-token.yml | 35 | ||||
| -rw-r--r-- | modules/security/gopass/creation-templates/3-ssh-key.yml | 36 | ||||
| -rw-r--r-- | modules/security/gopass/default.nix | 50 | ||||
| -rw-r--r-- | modules/security/gopass/gopass-ssh-load.sh | 71 | ||||
| -rw-r--r-- | modules/security/ssh/default.nix | 75 | ||||
| -rw-r--r-- | modules/security/ssh/gpg-ssh-key-load.sh | 88 | ||||
| -rw-r--r-- | users/arpit/default.nix | 2 | ||||
| -rw-r--r-- | users/arpit/private.example.nix | 7 |
10 files changed, 288 insertions, 137 deletions
diff --git a/modules/security/gopass/creation-templates/0-website.yml b/modules/security/gopass/creation-templates/0-website.yml new file mode 100644 index 0000000..0b80a12 --- /dev/null +++ b/modules/security/gopass/creation-templates/0-website.yml @@ -0,0 +1,27 @@ +--- +priority: 0 +name: "Website login" +prefix: "websites" +name_from: + - "url" + - "username" +welcome: "🌐 Creating Website login" +attributes: + - name: "url" + type: "hostname" + prompt: "Website URL" + min: 1 + max: 255 + - name: "username" + type: "string" + prompt: "Username" + min: 1 + - name: "password" + type: "password" + prompt: "Password for the Website" + - name: "totp" + type: "otp" + prompt: "TOTP secret / otpauth:// URI (leave blank if no 2FA)" + - name: "recovery_codes" + type: "string" + prompt: "Recovery/backup codes, space-separated (optional)" diff --git a/modules/security/gopass/creation-templates/1-wifi.yml b/modules/security/gopass/creation-templates/1-wifi.yml new file mode 100644 index 0000000..416a621 --- /dev/null +++ b/modules/security/gopass/creation-templates/1-wifi.yml @@ -0,0 +1,34 @@ +--- +priority: 1 +name: "WiFi network" +prefix: "wifi" +name_from: + - "location" + - "ssid" +welcome: "Creating WiFi network entry" +attributes: + - name: "location" + type: "string" + prompt: "Location (e.g. home, work, office, cafe)" + min: 1 + max: 64 + - name: "ssid" + type: "string" + prompt: "SSID" + min: 1 + max: 32 + - name: "username" + type: "string" + prompt: "Username (leave blank if not Enterprise/802.1X)" + - name: "security" + type: "string" + prompt: "Security (WPA2-Personal, WPA2-Enterprise, WPA3-Personal, WEP, Open)" + - name: "eap_method" + type: "string" + prompt: "EAP-Method (PEAP, TLS, TTLS, PWD) - leave blank unless Enterprise" + - name: "phase2_auth" + type: "string" + prompt: "Phase2-Auth (MSCHAPV2, GTC, etc.) - leave blank unless Enterprise" + - name: "password" + type: "password" + prompt: "Password / PSK for the network" diff --git a/modules/security/gopass/creation-templates/2-access-token.yml b/modules/security/gopass/creation-templates/2-access-token.yml new file mode 100644 index 0000000..1671e79 --- /dev/null +++ b/modules/security/gopass/creation-templates/2-access-token.yml @@ -0,0 +1,35 @@ +--- +priority: 2 +name: "Website access token" +prefix: "websites" +name_from: + - "url" + - "username" + - "token_name" +welcome: "Creating Website access token" +attributes: + - name: "url" + type: "hostname" + prompt: "Website URL" + min: 1 + max: 255 + - name: "username" + type: "string" + prompt: "Username / account this token belongs to" + min: 1 + - name: "token_name" + type: "string" + prompt: "Token name (e.g. api-readonly, ci-deploy, personal-access-token)" + min: 1 + - name: "token" + type: "password" + prompt: "Token value" + - name: "scopes" + type: "string" + prompt: "Scopes/permissions granted (optional)" + - name: "expires" + type: "string" + prompt: "Expiry date, e.g. 2027-01-15 (optional, leave blank if none)" + - name: "comment" + type: "string" + prompt: "Notes (optional)" diff --git a/modules/security/gopass/creation-templates/3-ssh-key.yml b/modules/security/gopass/creation-templates/3-ssh-key.yml new file mode 100644 index 0000000..136a22b --- /dev/null +++ b/modules/security/gopass/creation-templates/3-ssh-key.yml @@ -0,0 +1,36 @@ +--- +priority: 3 +name: "SSH login" +prefix: "ssh" +name_from: + - "host" + - "username" +welcome: "🔐 Creating SSH login" +attributes: + - name: "host" + type: "hostname" + prompt: "Host machine name (hostname, IP, or alias)" + min: 1 + max: 255 + - name: "username" + type: "string" + prompt: "Username" + min: 1 + - name: "password" + type: "password" + prompt: "Password (leave blank if using key-only auth)" + - name: "port" + type: "string" + prompt: "SSH port (optional, default 22)" + - name: "private_key" + type: "password" + prompt: "Private key (paste full contents, e.g. -----BEGIN OPENSSH PRIVATE KEY-----...)" + - name: "public_key" + type: "string" + prompt: "Public key (paste full contents, e.g. ssh-ed25519 AAAA...)" + - name: "key_passphrase" + type: "password" + prompt: "Private key passphrase (optional)" + - name: "comment" + type: "string" + prompt: "Notes (optional)" diff --git a/modules/security/gopass/default.nix b/modules/security/gopass/default.nix index 4cf9182..123b33f 100644 --- a/modules/security/gopass/default.nix +++ b/modules/security/gopass/default.nix @@ -5,30 +5,6 @@ pkgs, ... }: -let - gopassKeys = config.security.ssh.gopassKeys; - - gopassSshLoadScript = pkgs.writeShellApplication { - name = "gopass-ssh-load"; - runtimeInputs = [ - config.security.gopass.package - config.security.gpg.package - config.security.ssh.package - config.terminal.bash.package - ]; - text = - builtins.replaceStrings - [ - "@@GOPASS_SSH_KEYS@@" - "@@GNUPGHOME@@" - ] - [ - (lib.concatStringsSep " " gopassKeys) - config.home.sessionVariables.GNUPGHOME - ] - (builtins.readFile ./gopass-ssh-load.sh); - }; -in { options.security.gopass = { enable = lib.mkEnableOption "Enables gopass."; @@ -38,15 +14,6 @@ in default = config.programs.password-store.package; description = "The gopass package to use."; }; - ssh-agent = { - enable = lib.mkEnableOption "gopass-backed SSH keys for git"; - package = lib.mkOption { - type = lib.types.package; - readOnly = true; - default = gopassSshLoadScript; - description = "The gopass-ssh-load script package."; - }; - }; sync = { enable = lib.mkEnableOption "Enables git-backed syncing of the gopass data directory."; remote = lib.mkOption { @@ -67,6 +34,9 @@ in }; }; }; + creation-templates = { + enable = lib.mkEnableOption "Enables gopass entry creation templates. New entry creation templates for gopass new or gopass create commands."; + }; }; config = lib.mkMerge [ @@ -116,6 +86,16 @@ in source = ../../../assets/icons/apps/gopass.svg; }; + home.activation.copyCreationTemplatesForGopass = + lib.mkIf config.security.gopass.creation-templates.enable + ( + lib.hm.dag.entryAfter [ "writeBoundary" ] '' + $DRY_RUN_CMD mkdir -p $VERBOSE_ARG "${config.programs.password-store.settings.PASSWORD_STORE_DIR}/.gopass/create" + $DRY_RUN_CMD rm -rf ${config.programs.password-store.settings.PASSWORD_STORE_DIR}/.gopass/create + $DRY_RUN_CMD cp -r $VERBOSE_ARG --no-preserve=mode ${./creation-templates} "${config.programs.password-store.settings.PASSWORD_STORE_DIR}/.gopass/create" + '' + ); + xdg.desktopEntries."gopass" = { name = "gopass"; exec = "${lib.getExe config.terminal.kitty.package} --class gopass -e ${lib.getExe config.security.gopass.package}"; @@ -127,10 +107,6 @@ in }; }) - (lib.mkIf config.security.gopass.ssh-agent.enable { - home.packages = [ config.security.gopass.ssh-agent.package ]; - }) - (lib.mkIf config.security.gopass.sync.enable { home.activation.gopassSyncInit = let diff --git a/modules/security/gopass/gopass-ssh-load.sh b/modules/security/gopass/gopass-ssh-load.sh deleted file mode 100644 index 8c1b11f..0000000 --- a/modules/security/gopass/gopass-ssh-load.sh +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env bash - -# gopass-ssh-load -# -# Load SSH keys into the SSH agent from the gopass password store. -# -# This script reads private keys and (optionally) their passphrases from gopass -# entries under the `ssh/` directory and adds them to the SSH agent served by -# gpg-agent. It is meant to be run manually whenever a key is imported into or -# rotated within the gopass store, so the SSH agent picks up the change. -# -# Behaviour: -# * It first verifies that a usable SSH agent socket exists and bails out if -# not. -# * It exits early (without doing anything) when the agent already has at -# least one Ed25519/RSA/ECDSA identity loaded, to avoid useless work and -# unnecessary gpg passphrase prompts. -# * For each key listed in GOPASS_SSH_KEYS it writes the corresponding -# `ssh/<key>` entry to a temporary file, strips the passphrase using the -# `ssh/<key>/passphrase` entry, and registers the key with `ssh-add`. -# -# Temporary key files are written with mode 600 and removed afterwards. - -set -o errexit -set -o nounset -set -o pipefail - -export GNUPGHOME="@@GNUPGHOME@@" -export GOPASS_SSH_KEYS="@@GOPASS_SSH_KEYS@@" - -info() { printf '\033[1;34m==>\033[0m %s\n' "$*"; } -warn() { printf '\033[1;33m==> warning:\033[0m %s\n' "$*" >&2; } -error() { printf '\033[1;31m==> error:\033[0m %s\n' "$*" >&2; } -die() { error "$*"; exit 1; } - -SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)" -export SSH_AUTH_SOCK -if [ -z "$SSH_AUTH_SOCK" ] || [ ! -S "$SSH_AUTH_SOCK" ]; then - die "SSH_AUTH_SOCK is not set or valid." -fi - -if ssh-add -l 2>/dev/null | grep -qE "(ED25519|RSA|ECDSA)"; then - exit 0 -fi - -# GOPASS_SSH_KEYS holds a space-separated list of gopass entry names under ssh -if [ -z "${GOPASS_SSH_KEYS:-}" ]; then - die "GOPASS_SSH_KEYS is not set. Example: GOPASS_SSH_KEYS=\"github gitlab\"" -fi - -# shellcheck disable=SC2086 -read -r -a keys <<< "$GOPASS_SSH_KEYS" - -for key in "${keys[@]}"; do - if gopass cat "ssh/$key" > /dev/null 2>&1; then - tmpdir=$(mktemp -d) - keyfile="$tmpdir/key" - gopass cat "ssh/$key" > "$keyfile" 2>/dev/null - chmod 600 "$keyfile" - - passphrase=$(gopass cat "ssh/$key/passphrase" 2>/dev/null || true) - if [ -n "$passphrase" ]; then - ssh-keygen -p -P "$passphrase" -N "" -f "$keyfile" 2>/dev/null - fi - - ssh-add "$keyfile" 2>/dev/null - rm -rf "$tmpdir" - else - warn "no gopass entry ssh/$key" - fi -done diff --git a/modules/security/ssh/default.nix b/modules/security/ssh/default.nix index 8d1f881..e18b933 100644 --- a/modules/security/ssh/default.nix +++ b/modules/security/ssh/default.nix @@ -5,40 +5,69 @@ pkgs, ... }: +let + gpgSshKeyLoad = pkgs.writeShellApplication { + name = "gpg-ssh-key-load"; + runtimeInputs = [ + config.terminal.bash.package + config.security.gopass.package + config.security.gpg.package + config.security.ssh.package + pkgs.coreutils + ]; + text = + builtins.replaceStrings + [ + "@@GOPASS_SSH_KEY@@" + "@@GNUPGHOME@@" + ] + [ + config.security.ssh.sshKeyGopassPath + config.home.sessionVariables.GNUPGHOME + ] + (builtins.readFile ./gpg-ssh-key-load.sh); + }; +in { options.security.ssh = { - enable = lib.mkEnableOption "Enables ssh."; + enable = lib.mkEnableOption "Enables ssh via the gpg-agent."; package = lib.mkOption { type = lib.types.package; readOnly = true; - default = config.programs.ssh.package; + default = pkgs.openssh; description = "The ssh package to use."; }; - gopassKeys = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - description = "SSH keys to load from the gopass store (entries under ssh/)."; + sshKeyGopassPath = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + gopass entry holding the private SSH key. The key is loaded into the + gpg-agent during home-manager switch so ssh works without a ~/.ssh + directory. + ''; }; }; - config = lib.mkIf config.security.ssh.enable { - programs.ssh = { - enable = true; - - package = pkgs.openssh; + config = lib.mkMerge [ + (lib.mkIf config.security.ssh.enable { + home.packages = [ config.security.ssh.package ]; - enableDefaultConfig = false; - extraOptionOverrides = { - AddKeysToAgent = "yes"; - ForwardAgent = "yes"; - ServerAliveInterval = "60"; - ServerAliveCountMax = "3"; - VisualHostKey = "yes"; - HashKnownHosts = "yes"; - }; - }; + assertions = [ + { + assertion = config.security.gpg.enable; + message = '' + Enabling `security.ssh` requires `security.gpg` so that + gpg-agent can be used as the ssh-agent. + ''; + } + ]; + }) - services.ssh-agent.enable = lib.mkIf config.security.gpg.enable false; - }; + (lib.mkIf (config.security.ssh.enable && config.security.ssh.sshKeyGopassPath != null) { + home.activation.gpgSshKeyLoad = lib.hm.dag.entryAfter [ "writeBoundary" ] '' + run ${lib.getExe gpgSshKeyLoad} || true + ''; + }) + ]; } diff --git a/modules/security/ssh/gpg-ssh-key-load.sh b/modules/security/ssh/gpg-ssh-key-load.sh new file mode 100644 index 0000000..d662dbe --- /dev/null +++ b/modules/security/ssh/gpg-ssh-key-load.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash + +# gpg-ssh-key-load +# +# Load the SSH key stored in gopass into the gpg-agent. +# +# SSH keys are served by gpg-agent (enable-ssh-support): the private key at +# @@GOPASS_SSH_KEY@@ in the gopass store is added to the agent, so ssh works +# without ever needing a ~/.ssh directory. It runs automatically during every +# home-manager switch and exits early when the agent already holds an identity, +# so the gpg passphrase prompt only appears when it is actually needed. + +set -o errexit +set -o nounset +set -o pipefail + +export GNUPGHOME="@@GNUPGHOME@@" + +GOPASS_SSH_KEY="@@GOPASS_SSH_KEY@@" + +info() { printf '\033[1;34m==>\033[0m %s\n' "$*"; } +warn() { printf '\033[1;33m==> warning:\033[0m %s\n' "$*" >&2; } +error() { printf '\033[1;31m==> error:\033[0m %s\n' "$*" >&2; } +die() { error "$*"; exit 1; } + +usage() { + cat <<EOF +Usage: gpg-ssh-key-load [OPTS] + +Loads the SSH key stored at "$GOPASS_SSH_KEY" in the gopass store into the +gpg-agent, so ssh uses the gpg-agent without creating a ~/.ssh directory. + +Options: + -h, --help Show this help text +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --help | -h) + usage + exit 0 + ;; + *) + error "Unknown option: $1" + usage + exit 1 + ;; + esac +done + +# Make sure the agent is running and its ssh socket exists. +gpgconf --launch gpg-agent + +SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)" +export SSH_AUTH_SOCK +if [ -z "$SSH_AUTH_SOCK" ] || [ ! -S "$SSH_AUTH_SOCK" ]; then + die "SSH_AUTH_SOCK is not set or valid." +fi + +# Already holding an identity, nothing to do. +if ssh-add -l 2>/dev/null | grep -qE "(ED25519|RSA|ECDSA)"; then + exit 0 +fi + +if ! gopass show -o "$GOPASS_SSH_KEY" > /dev/null 2>&1; then + die "no gopass entry $GOPASS_SSH_KEY" +fi + +tmpdir=$(mktemp -d) +trap 'rm -rf "$tmpdir"' EXIT +keyfile="$tmpdir/key" +gopass show -o "$GOPASS_SSH_KEY" > "$keyfile" 2>/dev/null +chmod 600 "$keyfile" + +passphrase=$(gopass show -o "$GOPASS_SSH_KEY/passphrase" 2>/dev/null || true) +if [ -n "$passphrase" ]; then + ssh-keygen -p -P "$passphrase" -N "" -f "$keyfile" 2>/dev/null +fi + +info "Loading SSH key ($GOPASS_SSH_KEY) into gpg-agent..." +if ! timeout 60 ssh-add "$keyfile"; then + error "Failed to load SSH key into gpg-agent." + error "Run \"ssh-add <(gopass show -o $GOPASS_SSH_KEY)\" manually." + exit 1 +fi + +info "SSH key loaded into gpg-agent."
\ No newline at end of file diff --git a/users/arpit/default.nix b/users/arpit/default.nix index 9348884..a76a893 100644 --- a/users/arpit/default.nix +++ b/users/arpit/default.nix @@ -65,8 +65,8 @@ # Security security.gopass.enable = true; - security.gopass.ssh-agent.enable = true; security.gopass.sync.enable = true; + security.gopass.creation-templates.enable = true; security.gpg.enable = true; security.gpg.backup.enable = true; security.gpg-tui.enable = true; diff --git a/users/arpit/private.example.nix b/users/arpit/private.example.nix index 77e5f54..2f169d9 100644 --- a/users/arpit/private.example.nix +++ b/users/arpit/private.example.nix @@ -38,11 +38,8 @@ }; }; - # SSH keys to load from gopass (entries under ssh/ in the gopass store) - config.security.ssh.gopassKeys = [ - "github" - "gitlab" - ]; + # SSH key to load into the gpg-agent from gopass (entry in the gopass store) + config.security.ssh.sshKeyGopassPath = "ssh/hostname/username"; # Gopass - Template for configuring gopass, specially syncing config.security.gopass.sync = { |
