mirror of
https://github.com/DictXiong/dotfiles.git
synced 2026-10-24 13:48:15 +08:00
[dev] GPG agent forwarding; improve riot, sagt, tmux and zsh (#46)
* feat(riot): support literal ipv6 addresses (with or without ports given) feat(riot): -v, -4, and -6 fix(riot): do not call external binaries if possible * feat(riot): command git * build(riot-config): nasp.fit -> jump.nasp.fit * feat: add bnd and nor and completions * fix(zshrc): bnd exit when build error * fix(tmux): press ^a twice to send ^a * feat(riot): sshl supports specifying local port and unix sock * feat(riot): sshr for ssh -R * feat(riot): -o ServerAliveInterval=60 * feat(riot): conditionally RequestTTY * feat: zsh completion for beam and beaml * fix(frigg): ddns get ip * fix(riot): tmux error with window number and spaces The fix to spaces is just a workaround. Issue may still exist when command contains spaces. * feat(riot): ControlPersist=60s * fix(ci): riot * feat(tmux): prevent tmux from exiting copy mode after selection with mouse see: - https://www.reddit.com/r/tmux/comments/v73005/how_to_prevent_tmux_from_exiting_copy_mode_after/ - https://www.reddit.com/r/tmux/comments/1ltms6a/how_to_select_text_in_tmux_without_having_it_jump/ * feat(tmux): F12 to passthru * fix(riot): ControlPersist 60s -> 5s * feat: sne: search and edit * feat(ssh): add key ip17/sep * feat(tmux): press ESC to exit copy mode * feat(ssh): add key sk1/piv/9a * feat(ssh): add key sk1/fido2 * feat(ssh): add key openpgp:0x8774BF70 and remove key ip14/sep and ltp2 (asu127) * feat(sagent): sagt gpg; improve code * feat(sagent): check pinentry and give notes * feat(riot): add GPG agent forwarding Signed-off-by: Dict Xiong <me@beardic.cn> * feat: gitconf add gpg signature; remove macos from ci * feat(eid): add sk1 9a cert * feat(riot): better help and fix option quite->quiet * feat(riot): separate command options from riot options * feat(sagt): gpg-pin and associated tests * feat(riot): zsh completions * fix: Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix(riot): simplify and secure * fix: syntax --------- Signed-off-by: Dict Xiong <me@beardic.cn> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -6,19 +6,19 @@ if [[ -f ~/.config/dotfiles/env ]]; then set -a; source ~/.config/dotfiles/env;
|
||||
if [[ "$DFS_DEV" == "1" ]]; then set -x; fi
|
||||
DFS_CURL_OPTIONS="--retry 2 --max-time 20"
|
||||
|
||||
# parse args and set env, when it is sourced
|
||||
# todo: make this skipable
|
||||
if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then
|
||||
# Parse args and set env when sourced, unless the caller handles its own
|
||||
# option boundary.
|
||||
if [[ "${BASH_SOURCE[0]}" != "${0}" && "$DFS_SKIP_ARG_PARSE" != "1" ]]; then
|
||||
ORIGIN_ARGS=("$@")
|
||||
ARG=""
|
||||
GOT_OPTS=()
|
||||
while [[ $# > 0 || -n "$ARG" ]]; do
|
||||
while [[ $# -gt 0 || -n "$ARG" ]]; do
|
||||
if [[ -z "$ARG" ]]; then
|
||||
if [[ "$1" == "--" ]]; then GOT_OPTS+=("$@"); break; fi
|
||||
ARG="$1"; shift;
|
||||
fi
|
||||
case $ARG in
|
||||
-q*|--quite ) export DFS_QUIET=1 ;;
|
||||
-q*|--quiet ) export DFS_QUIET=1 ;;
|
||||
-l*|--lite ) export DFS_LITE=1 ;;
|
||||
-d*|--dev ) export DFS_DEV=1; set -x ;;
|
||||
-D*|--dry-run ) export DFS_DRY_RUN=1 ;;
|
||||
@@ -41,15 +41,9 @@ fi
|
||||
|
||||
# Color settings
|
||||
# Source: https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh
|
||||
if [[ -t 1 || "$DFS_COLOR" == "1" ]]; then
|
||||
is_tty() {
|
||||
true
|
||||
}
|
||||
else
|
||||
is_tty() {
|
||||
false
|
||||
}
|
||||
fi
|
||||
is_tty() {
|
||||
[[ -t 1 || "$DFS_COLOR" == "1" ]]
|
||||
}
|
||||
|
||||
supports_truecolor() {
|
||||
case "$COLORTERM" in
|
||||
|
||||
@@ -99,7 +99,7 @@ update_dns()
|
||||
elif [[ "$DFS_DDNS_IP4" == "http"* ]]; then
|
||||
ip4=$(curl $DFS_CURL_OPTIONS -sSL "$DFS_DDNS_IP4")
|
||||
else
|
||||
ip4=$(ip a show $DFS_DDNS_IP4 | grep inet | grep global | awk '/inet / {print $2}' | awk -F'[/]' '{print $1}')
|
||||
ip4=$(ip -4 --brief addr show $DFS_DDNS_IP4 scope global primary | awk '{split($3,a,"/");print a[1]}' | head -n 1)
|
||||
fi
|
||||
if [[ -n "$DFS_DDNS_IP4" && -z "$ip4" ]]; then
|
||||
fmt_fatal "failed getting ip4 address"
|
||||
@@ -115,7 +115,7 @@ update_dns()
|
||||
elif [[ "$DFS_DDNS_IP6" == "http"* ]]; then
|
||||
ip6=$(curl $DFS_CURL_OPTIONS -sSL "$DFS_DDNS_IP6")
|
||||
else
|
||||
ip6=$(ip a show $DFS_DDNS_IP6 | grep inet6 | grep global | awk '/inet6 / {print $2}' | awk -F'[/]' '{print $1}')
|
||||
ip6=$(ip -6 --brief addr show $DFS_DDNS_IP6 scope global primary | awk '{split($3,a,"/");print a[1]}' | head -n 1)
|
||||
fi
|
||||
if [[ -n "$DFS_DDNS_IP6" && -z "$ip6" ]]; then
|
||||
fmt_fatal "failed getting ip6 address"
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
set -e
|
||||
|
||||
op=$(command -v op || command -v op.exe || true)
|
||||
if [[ ! -x $op ]]; then
|
||||
echo "1password cli not found" > /dev/stderr
|
||||
exit -1
|
||||
if [[ -z "$op" || ! -x "$op" ]]; then
|
||||
echo "1Password CLI not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
"$op" read "op://Personal/id25519-passphrase/$(hostname)"
|
||||
exec "$op" read "op://Personal/id25519-passphrase/$(hostname)"
|
||||
|
||||
261
tools/sagent.sh
261
tools/sagent.sh
@@ -20,6 +20,7 @@ find_so_file()
|
||||
return
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
create_agent()
|
||||
@@ -30,27 +31,50 @@ create_agent()
|
||||
|
||||
kill_agent()
|
||||
{
|
||||
if pgrep -x ssh-agent > /dev/null; then
|
||||
fmt_note "killing existing agent"
|
||||
pkill -9 -x ssh-agent
|
||||
local status
|
||||
if pgrep -u "$EUID" -x ssh-agent > /dev/null; then
|
||||
fmt_note "stopping existing ssh-agent"
|
||||
if pkill -TERM -u "$EUID" -x ssh-agent; then
|
||||
:
|
||||
else
|
||||
status=$?
|
||||
[[ $status -eq 1 ]] || return "$status"
|
||||
fi
|
||||
fi
|
||||
if command -v gpgconf > /dev/null 2>&1; then
|
||||
fmt_note "stopping gpg-agent if running"
|
||||
gpgconf --kill gpg-agent
|
||||
fi
|
||||
unset SSH_AUTH_SOCK SSH_AGENT_PID
|
||||
echo unset SSH_AUTH_SOCK SSH_AGENT_PID
|
||||
}
|
||||
|
||||
add_piv()
|
||||
{
|
||||
local SO_FILE=$(find_so_file)
|
||||
if [[ -n "$SO_FILE" ]]; then
|
||||
echo ssh-add -s \"$SO_FILE\"
|
||||
else
|
||||
local SO_FILE
|
||||
if ! SO_FILE=$(find_so_file); then
|
||||
fmt_error "opensc-pkcs11.so not found"
|
||||
return 1
|
||||
fi
|
||||
printf 'ssh-add -s %q\n' "$SO_FILE"
|
||||
list
|
||||
}
|
||||
|
||||
add_id25519_with_op()
|
||||
{
|
||||
SSH_ASKPASS_REQUIRE=force SSH_ASKPASS="$THIS_DIR/sagent-op.sh" timeout 60s ssh-add ~/.ssh/id_ed25519 || fmt_fatal "timed out when adding the key. probably the passphrase is wrong or 1password-cli is not working"
|
||||
list
|
||||
local status
|
||||
if SSH_ASKPASS_REQUIRE=force SSH_ASKPASS="$THIS_DIR/sagent-op.sh" timeout 60s ssh-add "$HOME/.ssh/id_ed25519"; then
|
||||
list
|
||||
return
|
||||
else
|
||||
status=$?
|
||||
fi
|
||||
|
||||
if [[ $status -eq 124 ]]; then
|
||||
fmt_fatal "timed out when adding the key"
|
||||
else
|
||||
fmt_fatal "failed to add the key (ssh-add exit $status); check the key, agent, and 1Password CLI"
|
||||
fi
|
||||
}
|
||||
|
||||
list()
|
||||
@@ -59,30 +83,220 @@ list()
|
||||
echo ssh-add -l
|
||||
}
|
||||
|
||||
configured_pinentry()
|
||||
{
|
||||
local agent_conf
|
||||
local agent_confs=()
|
||||
for agent_conf in "$@"; do
|
||||
[[ -f "$agent_conf" ]] && agent_confs+=("$agent_conf")
|
||||
done
|
||||
[[ ${#agent_confs[@]} -gt 0 ]] || return 1
|
||||
|
||||
awk '
|
||||
/^[[:space:]]*#/ { next }
|
||||
{
|
||||
line = $0
|
||||
sub(/^[[:space:]]*/, "", line)
|
||||
if (line ~ /^pinentry-program([[:space:]]|=)/) {
|
||||
sub(/^pinentry-program[[:space:]=]*/, "", line)
|
||||
sub(/[[:space:]]*$/, "", line)
|
||||
pinentry = line
|
||||
}
|
||||
}
|
||||
END {
|
||||
if (pinentry == "") exit 1
|
||||
print pinentry
|
||||
}
|
||||
' "${agent_confs[@]}"
|
||||
}
|
||||
|
||||
check_pinentry()
|
||||
{
|
||||
local gnupg_home
|
||||
local gpg_sysconfdir
|
||||
local agent_conf
|
||||
local system_agent_conf
|
||||
local pinentry
|
||||
local gpg_bindir
|
||||
local candidate
|
||||
|
||||
gnupg_home=$(gpgconf --list-dirs homedir)
|
||||
gpg_sysconfdir=$(gpgconf --list-dirs sysconfdir)
|
||||
agent_conf="$gnupg_home/gpg-agent.conf"
|
||||
system_agent_conf="$gpg_sysconfdir/gpg-agent.conf"
|
||||
if pinentry=$(configured_pinentry "$system_agent_conf" "$agent_conf"); then
|
||||
if [[ "$pinentry" == "~/"* ]]; then
|
||||
pinentry="$HOME/${pinentry#\~/}"
|
||||
fi
|
||||
if [[ -x "$pinentry" ]]; then
|
||||
return
|
||||
fi
|
||||
fmt_warning "configured pinentry is not executable: $pinentry"
|
||||
else
|
||||
gpg_bindir=$(gpgconf --list-dirs bindir)
|
||||
if [[ -x "$gpg_bindir/pinentry" || -x "$gpg_bindir/pinentry-basic" ]]; then
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
for candidate in \
|
||||
"$(command -v pinentry-curses 2>/dev/null || true)" \
|
||||
"$(command -v pinentry 2>/dev/null || true)" \
|
||||
"$(command -v pinentry-tty 2>/dev/null || true)"; do
|
||||
[[ -n "$candidate" && -x "$candidate" ]] && break
|
||||
candidate=""
|
||||
done
|
||||
|
||||
if [[ -n "$candidate" ]]; then
|
||||
fmt_warning "gpg-agent has no usable pinentry; add 'pinentry-program $candidate' to $agent_conf"
|
||||
else
|
||||
fmt_warning "gpg-agent has no usable pinentry; install one and configure pinentry-program in $agent_conf"
|
||||
fi
|
||||
}
|
||||
|
||||
prepare_gpg_agent()
|
||||
{
|
||||
command -v gpgconf > /dev/null 2>&1 || fmt_fatal "gpgconf not found"
|
||||
command -v gpg-connect-agent > /dev/null 2>&1 || fmt_fatal "gpg-connect-agent not found"
|
||||
|
||||
check_pinentry
|
||||
|
||||
local current_tty
|
||||
current_tty=$(tty) || fmt_fatal "unable to determine the current TTY"
|
||||
export GPG_TTY="$current_tty"
|
||||
|
||||
gpgconf --launch gpg-agent
|
||||
gpg-connect-agent updatestartuptty /bye > /dev/null
|
||||
}
|
||||
|
||||
use_gpg_agent()
|
||||
{
|
||||
prepare_gpg_agent
|
||||
|
||||
local agent_socket
|
||||
agent_socket=$(gpgconf --list-dirs agent-ssh-socket)
|
||||
if [[ -z "$agent_socket" || ! -S "$agent_socket" ]]; then
|
||||
fmt_fatal "gpg-agent SSH socket not found; add 'enable-ssh-support' to ~/.gnupg/gpg-agent.conf and restart gpg-agent"
|
||||
fi
|
||||
|
||||
fmt_note "using gpg-agent: $agent_socket"
|
||||
echo unset SSH_AGENT_PID
|
||||
printf 'export GPG_TTY=%q\n' "$GPG_TTY"
|
||||
printf 'export SSH_AUTH_SOCK=%q\n' "$agent_socket"
|
||||
}
|
||||
|
||||
cache_gpg_pin()
|
||||
{
|
||||
[[ $# -le 1 ]] || fmt_fatal "usage: sagt gpg-pin [KEY]"
|
||||
command -v gpg > /dev/null 2>&1 || fmt_fatal "gpg not found"
|
||||
prepare_gpg_agent
|
||||
|
||||
local signing_key="${1:-}"
|
||||
local temp_dir
|
||||
local signature_file
|
||||
local status=0
|
||||
local gpg_args=(--detach-sign)
|
||||
|
||||
temp_dir=$(mktemp -d "${TMPDIR:-/tmp}/sagent-gpg-pin.XXXXXXXXXX") || \
|
||||
fmt_fatal "failed to create a temporary directory"
|
||||
signature_file="$temp_dir/signature.gpg"
|
||||
gpg_args+=(--output "$signature_file")
|
||||
[[ -z "$signing_key" ]] || gpg_args+=(--local-user "$signing_key")
|
||||
|
||||
fmt_note "performing a test signature; enter the GPG PIN and touch the token if prompted"
|
||||
printf 'sagt gpg-pin\n' | gpg "${gpg_args[@]}" || status=$?
|
||||
|
||||
rm -f -- "$signature_file"
|
||||
rmdir -- "$temp_dir"
|
||||
[[ $status -eq 0 ]] || fmt_fatal "test signature failed (gpg exit $status)"
|
||||
fmt_note "test signature completed; the GPG PIN should remain cached until the card or agent session is reset"
|
||||
}
|
||||
|
||||
read_agent_file()
|
||||
{
|
||||
local agent_file="$1"
|
||||
local line
|
||||
local agent_socket=""
|
||||
local agent_pid=""
|
||||
|
||||
while IFS= read -r line; do
|
||||
case "$line" in
|
||||
SSH_AUTH_SOCK=*)
|
||||
agent_socket=${line#SSH_AUTH_SOCK=}
|
||||
agent_socket=${agent_socket%%;*}
|
||||
;;
|
||||
SSH_AGENT_PID=*)
|
||||
agent_pid=${line#SSH_AGENT_PID=}
|
||||
agent_pid=${agent_pid%%;*}
|
||||
;;
|
||||
esac
|
||||
done < "$agent_file"
|
||||
|
||||
[[ -n "$agent_socket" && "$agent_pid" =~ ^[1-9][0-9]*$ ]] || return 1
|
||||
export SSH_AUTH_SOCK="$agent_socket"
|
||||
export SSH_AGENT_PID="$agent_pid"
|
||||
}
|
||||
|
||||
agent_is_usable()
|
||||
{
|
||||
[[ -S "$SSH_AUTH_SOCK" ]] || return 1
|
||||
ps -p "$SSH_AGENT_PID" -o uid= -o comm= 2>/dev/null |
|
||||
awk -v uid="$EUID" '$1 == uid && $2 ~ /(^|\/)ssh-agent$/ { found=1 } END { exit !found }' || return 1
|
||||
|
||||
local status
|
||||
if ssh-add -l > /dev/null 2>&1; then
|
||||
status=0
|
||||
else
|
||||
status=$?
|
||||
fi
|
||||
[[ $status -eq 0 || $status -eq 1 ]]
|
||||
}
|
||||
|
||||
print_agent_env()
|
||||
{
|
||||
printf 'export SSH_AUTH_SOCK=%q\n' "$SSH_AUTH_SOCK"
|
||||
printf 'export SSH_AGENT_PID=%q\n' "$SSH_AGENT_PID"
|
||||
}
|
||||
|
||||
reset()
|
||||
{
|
||||
kill_agent
|
||||
all
|
||||
all already-killed
|
||||
}
|
||||
|
||||
all()
|
||||
{
|
||||
test -d ~/.ssh || mkdir ~/.ssh
|
||||
local agent_file=~/.ssh/agent-$(whoami)
|
||||
if [[ -f $agent_file ]]; then
|
||||
source $agent_file > /dev/null
|
||||
local mode="${1:-}"
|
||||
mkdir -p "$HOME/.ssh"
|
||||
local agent_file="$HOME/.ssh/agent-$(whoami)"
|
||||
[[ ! -L "$agent_file" ]] || fmt_fatal "refusing to use symlink as agent file: $agent_file"
|
||||
unset SSH_AUTH_SOCK SSH_AGENT_PID
|
||||
|
||||
if [[ "$mode" != "already-killed" && -f "$agent_file" ]]; then
|
||||
chmod 600 "$agent_file"
|
||||
read_agent_file "$agent_file" || true
|
||||
else
|
||||
touch $agent_file
|
||||
chmod 600 $agent_file
|
||||
touch "$agent_file"
|
||||
chmod 600 "$agent_file"
|
||||
fi
|
||||
if ! ps -p "$SSH_AGENT_PID" 1>/dev/null 2>&1; then
|
||||
kill_agent
|
||||
|
||||
if ! agent_is_usable; then
|
||||
if [[ "$mode" != "already-killed" ]]; then
|
||||
kill_agent
|
||||
fi
|
||||
fmt_note "launching a new agent"
|
||||
create_agent | tee $agent_file
|
||||
local agent_output
|
||||
if ! agent_output=$(create_agent); then
|
||||
fmt_fatal "failed to launch ssh-agent"
|
||||
fi
|
||||
printf '%s\n' "$agent_output" > "$agent_file"
|
||||
chmod 600 "$agent_file"
|
||||
read_agent_file "$agent_file" || fmt_fatal "ssh-agent returned invalid environment data"
|
||||
agent_is_usable || fmt_fatal "new ssh-agent is not usable"
|
||||
else
|
||||
fmt_note "using existing agent: $SSH_AGENT_PID"
|
||||
cat $agent_file
|
||||
fi
|
||||
print_agent_env
|
||||
}
|
||||
|
||||
route()
|
||||
@@ -105,6 +319,12 @@ route()
|
||||
op)
|
||||
add_id25519_with_op
|
||||
;;
|
||||
gpg)
|
||||
use_gpg_agent
|
||||
;;
|
||||
gpg-pin)
|
||||
cache_gpg_pin "${@:2}"
|
||||
;;
|
||||
reset)
|
||||
reset
|
||||
;;
|
||||
@@ -113,6 +333,7 @@ route()
|
||||
;;
|
||||
*)
|
||||
fmt_error "unknown command: $1"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
OPTS='-a -bcl --color --arg1=1 --arg2 2 " 1 2" yes'
|
||||
OPTS='-a -bcl --color --quiet --arg1=1 --arg2 2 " 1 2" yes'
|
||||
TARGET_OPTS='-a -b -c --arg1 1 --arg2 2 1 2 yes'
|
||||
eval set -- $OPTS
|
||||
|
||||
@@ -8,14 +8,14 @@ THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]:-${(%):-%x}}" )" && pwd )
|
||||
source "$THIS_DIR/common.sh"
|
||||
|
||||
test "${GOT_OPTS[*]}" = "$TARGET_OPTS"
|
||||
test $# -eq 8
|
||||
test $# -eq 9
|
||||
test "$*" = "${OPTS//\"/}"
|
||||
test "$DFS_LITE" = "1"
|
||||
is_tty
|
||||
test -z "$DFS_QUIET"
|
||||
test "$DFS_QUIET" = "1"
|
||||
|
||||
set +x
|
||||
echo "test passed, args:"
|
||||
for i in "${GOT_OPTS[@]}"; do
|
||||
echo "$i"
|
||||
done
|
||||
done
|
||||
|
||||
132
tools/test-riot-gpg.sh
Executable file
132
tools/test-riot-gpg.sh
Executable file
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
THIS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
RIOT="$THIS_DIR/../scripts/riot"
|
||||
TEST_DIR=$(mktemp -d /tmp/riot-gpg.XXXXXX)
|
||||
trap 'rm -rf "$TEST_DIR"' EXIT
|
||||
|
||||
MOCK_BIN="$TEST_DIR/bin"
|
||||
MOCK_HOME="$TEST_DIR/home"
|
||||
MOCK_LOCAL_SOCKET="$TEST_DIR/local/S.gpg-agent.extra"
|
||||
MOCK_REMOTE_SOCKET="$TEST_DIR/remote/S.gpg-agent"
|
||||
MOCK_GPG_LOG="$TEST_DIR/gpg.log"
|
||||
MOCK_SSH_LOG="$TEST_DIR/ssh.log"
|
||||
mkdir -p "$MOCK_BIN" "$MOCK_HOME" "${MOCK_LOCAL_SOCKET%/*}" "${MOCK_REMOTE_SOCKET%/*}"
|
||||
export MOCK_LOCAL_SOCKET MOCK_REMOTE_SOCKET MOCK_GPG_LOG MOCK_SSH_LOG
|
||||
|
||||
make_stale_socket() {
|
||||
rm -f "$1"
|
||||
python3 - "$1" <<'PY'
|
||||
import socket
|
||||
import sys
|
||||
|
||||
sock = socket.socket(socket.AF_UNIX)
|
||||
sock.bind(sys.argv[1])
|
||||
sock.close()
|
||||
PY
|
||||
}
|
||||
|
||||
cat > "$MOCK_BIN/gpgconf" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
printf 'gpgconf %s\n' "$*" >> "$MOCK_GPG_LOG"
|
||||
case "$*" in
|
||||
'--list-dirs agent-extra-socket') printf '%s\n' "$MOCK_LOCAL_SOCKET" ;;
|
||||
'--list-dirs agent-socket') printf '%s\n' "$MOCK_REMOTE_SOCKET" ;;
|
||||
'--kill gpg-agent') exit "${MOCK_KILL_STATUS:-0}" ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
EOF
|
||||
|
||||
cat > "$MOCK_BIN/gpg-connect-agent" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
printf 'gpg-connect-agent %s\n' "$*" >> "$MOCK_GPG_LOG"
|
||||
case "$*" in
|
||||
*'GETINFO pid'*) printf 'D 123\nOK\n' ;;
|
||||
*'GETINFO version'*)
|
||||
[[ "${MOCK_LOCAL_LIVE:-1}" == 1 ]] || exit 1
|
||||
printf 'D 2.4.0\nOK\n'
|
||||
;;
|
||||
*'GETINFO restricted'*) printf 'D %s\nOK\n' "${MOCK_REMOTE_RESTRICTED:-0}" ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
EOF
|
||||
|
||||
cat > "$MOCK_BIN/systemctl" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
[[ "${MOCK_SYSTEMD_ACTIVE:-0}" == 1 ]]
|
||||
EOF
|
||||
|
||||
cat > "$MOCK_BIN/ssh" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
{
|
||||
printf 'CALL\n'
|
||||
printf 'ARG=%s\n' "$@"
|
||||
} >> "$MOCK_SSH_LOG"
|
||||
|
||||
is_probe=0
|
||||
last_arg=''
|
||||
for arg in "$@"; do
|
||||
[[ "$arg" == '-T' ]] && is_probe=1
|
||||
last_arg=$arg
|
||||
done
|
||||
if [[ "$is_probe" == 1 ]]; then
|
||||
sh -c "$last_arg"
|
||||
fi
|
||||
EOF
|
||||
|
||||
chmod +x "$MOCK_BIN/gpgconf" "$MOCK_BIN/gpg-connect-agent" "$MOCK_BIN/systemctl" "$MOCK_BIN/ssh"
|
||||
|
||||
run_riot() {
|
||||
HOME="$MOCK_HOME" RIOT_TRUST_CLIENT=0 PATH="$MOCK_BIN:$PATH" "$RIOT" "$@"
|
||||
}
|
||||
|
||||
expect_failure() {
|
||||
local expected=$1
|
||||
shift
|
||||
if run_riot "$@" > "$TEST_DIR/out" 2> "$TEST_DIR/err"; then
|
||||
echo "expected riot to fail: $*" >&2
|
||||
exit 1
|
||||
fi
|
||||
grep -Fq "$expected" "$TEST_DIR/err"
|
||||
}
|
||||
|
||||
# Dry-run must not inspect or mutate either host.
|
||||
: > "$MOCK_GPG_LOG"
|
||||
: > "$MOCK_SSH_LOG"
|
||||
DFS_DRY_RUN=1 run_riot -g example.test > "$TEST_DIR/out" 2> "$TEST_DIR/err"
|
||||
grep -Fq '<remote-gpg-agent-socket>:<local-gpg-agent-extra-socket>' "$TEST_DIR/out"
|
||||
[[ ! -s "$MOCK_GPG_LOG" && ! -s "$MOCK_SSH_LOG" ]]
|
||||
|
||||
# A socket inode without a listener must be rejected locally.
|
||||
make_stale_socket "$MOCK_LOCAL_SOCKET"
|
||||
MOCK_LOCAL_LIVE=0 expect_failure 'extra socket exists but is not accepting connections' -g example.test
|
||||
|
||||
# -g is valid only for an interactive SSH login.
|
||||
expect_failure 'only supported for interactive SSH login' -g example.test scp ./a ./b
|
||||
expect_failure 'only supported for interactive SSH login' -g example.test ssh -- true
|
||||
|
||||
# Never remove a regular file merely because it has the expected basename.
|
||||
MOCK_LOCAL_LIVE=1
|
||||
printf 'keep me\n' > "$MOCK_REMOTE_SOCKET"
|
||||
expect_failure 'refusing to remove the non-socket remote gpg-agent path' -g example.test
|
||||
grep -Fqx 'keep me' "$MOCK_REMOTE_SOCKET"
|
||||
|
||||
# A restricted agent at the remote socket represents another forwarding session.
|
||||
make_stale_socket "$MOCK_REMOTE_SOCKET"
|
||||
MOCK_REMOTE_RESTRICTED=1 expect_failure 'another forwarded gpg-agent is already using the remote socket' -g example.test
|
||||
|
||||
# A normal remote agent can be cleaned up; systemd activation is reported.
|
||||
make_stale_socket "$MOCK_REMOTE_SOCKET"
|
||||
: > "$MOCK_SSH_LOG"
|
||||
MOCK_REMOTE_RESTRICTED=0 MOCK_SYSTEMD_ACTIVE=1 run_riot -g example.test > "$TEST_DIR/out" 2> "$TEST_DIR/err"
|
||||
grep -Fq 'remote gpg-agent.socket is active' "$TEST_DIR/err"
|
||||
grep -Fq 'ARG=none' "$MOCK_SSH_LOG"
|
||||
grep -Fq 'ARG=ClearAllForwardings=yes' "$MOCK_SSH_LOG"
|
||||
grep -Fq 'ARG=-T' "$MOCK_SSH_LOG"
|
||||
! grep -Fq 'ARG=RequestTTY=' "$MOCK_SSH_LOG"
|
||||
grep -Fq 'ARG=StreamLocalBindUnlink=no' "$MOCK_SSH_LOG"
|
||||
grep -Fq "ARG=$MOCK_REMOTE_SOCKET:$MOCK_LOCAL_SOCKET" "$MOCK_SSH_LOG"
|
||||
[[ ! -e "$MOCK_REMOTE_SOCKET" ]]
|
||||
|
||||
echo 'riot gpg forwarding tests passed'
|
||||
75
tools/test-sagent-gpg-pin.sh
Executable file
75
tools/test-sagent-gpg-pin.sh
Executable file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
THIS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
SAGENT="$THIS_DIR/sagent.sh"
|
||||
TEST_DIR=$(mktemp -d /tmp/sagent-gpg-pin.XXXXXX)
|
||||
trap 'rm -rf "$TEST_DIR"' EXIT
|
||||
|
||||
MOCK_BIN="$TEST_DIR/bin"
|
||||
MOCK_HOME="$TEST_DIR/home"
|
||||
MOCK_TMP="$TEST_DIR/tmp"
|
||||
MOCK_GPG_ARGS="$TEST_DIR/gpg-args"
|
||||
MOCK_GPG_INPUT="$TEST_DIR/gpg-input"
|
||||
MOCK_AGENT_LOG="$TEST_DIR/agent-log"
|
||||
mkdir -p "$MOCK_BIN" "$MOCK_HOME/gnupg" "$MOCK_HOME/sysconf" "$MOCK_TMP"
|
||||
export MOCK_BIN MOCK_HOME MOCK_GPG_ARGS MOCK_GPG_INPUT MOCK_AGENT_LOG
|
||||
|
||||
cat > "$MOCK_BIN/gpgconf" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
printf 'gpgconf %s\n' "$*" >> "$MOCK_AGENT_LOG"
|
||||
case "$*" in
|
||||
'--list-dirs homedir') printf '%s\n' "$MOCK_HOME/gnupg" ;;
|
||||
'--list-dirs sysconfdir') printf '%s\n' "$MOCK_HOME/sysconf" ;;
|
||||
'--list-dirs bindir') printf '%s\n' "$MOCK_BIN" ;;
|
||||
'--launch gpg-agent') ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
EOF
|
||||
|
||||
cat > "$MOCK_BIN/gpg-connect-agent" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
printf 'gpg-connect-agent %s\n' "$*" >> "$MOCK_AGENT_LOG"
|
||||
EOF
|
||||
|
||||
cat > "$MOCK_BIN/tty" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
printf '/dev/pts/mock\n'
|
||||
EOF
|
||||
|
||||
cat > "$MOCK_BIN/gpg" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
printf '%s\n' "$@" > "$MOCK_GPG_ARGS"
|
||||
output=''
|
||||
while [[ $# -gt 0 ]]; do
|
||||
if [[ "$1" == '--output' ]]; then
|
||||
output=$2
|
||||
shift 2
|
||||
else
|
||||
shift
|
||||
fi
|
||||
done
|
||||
cat > "$MOCK_GPG_INPUT"
|
||||
printf 'mock signature\n' > "$output"
|
||||
EOF
|
||||
|
||||
printf '#!/usr/bin/env bash\n' > "$MOCK_BIN/pinentry"
|
||||
chmod +x "$MOCK_BIN/gpgconf" "$MOCK_BIN/gpg-connect-agent" "$MOCK_BIN/tty" \
|
||||
"$MOCK_BIN/gpg" "$MOCK_BIN/pinentry"
|
||||
|
||||
output=$(HOME="$MOCK_HOME" TMPDIR="$MOCK_TMP" PATH="$MOCK_BIN:$PATH" "$SAGENT" gpg-pin TEST-KEY 2> "$TEST_DIR/stderr")
|
||||
[[ -z "$output" ]]
|
||||
grep -Fxq -- '--detach-sign' "$MOCK_GPG_ARGS"
|
||||
grep -Fxq -- '--local-user' "$MOCK_GPG_ARGS"
|
||||
grep -Fxq -- 'TEST-KEY' "$MOCK_GPG_ARGS"
|
||||
grep -Fxq -- 'sagt gpg-pin' "$MOCK_GPG_INPUT"
|
||||
grep -Fq -- 'gpgconf --launch gpg-agent' "$MOCK_AGENT_LOG"
|
||||
grep -Fq -- 'gpg-connect-agent updatestartuptty /bye' "$MOCK_AGENT_LOG"
|
||||
grep -Fq -- 'test signature completed' "$TEST_DIR/stderr"
|
||||
|
||||
output=$(HOME="$MOCK_HOME" TMPDIR="$MOCK_TMP" PATH="$MOCK_BIN:$PATH" "$SAGENT" gpg-pin 2> "$TEST_DIR/stderr")
|
||||
[[ -z "$output" ]]
|
||||
! grep -Fxq -- '--local-user' "$MOCK_GPG_ARGS"
|
||||
[[ -z $(find "$MOCK_TMP" -mindepth 1 -print -quit) ]]
|
||||
|
||||
echo "sagent gpg-pin tests passed"
|
||||
@@ -34,15 +34,17 @@ dogo
|
||||
doll
|
||||
dfs cd
|
||||
tools/test-getopts.sh
|
||||
tools/test-riot-gpg.sh
|
||||
tools/test-sagent-gpg-pin.sh
|
||||
tools/common.sh get_os_name
|
||||
test $(echo y | tools/common.sh ask_for_yN "test") = "1"
|
||||
test $(echo n | tools/common.sh ask_for_yN "test") = "0"
|
||||
test $(echo | tools/common.sh ask_for_yN "test") = "0"
|
||||
test $(echo | tools/common.sh ask_for_Yn "test") = "1"
|
||||
test $(DFS_QUIET=1 tools/common.sh ask_for_Yn "test") = "1"
|
||||
test "$(DFS_TRUST=1 riot time@is.impt:2222/yes@you-r.right/you@are.really.recht./ibd./try@it,another@host scp /tmp/ ./tmp -D 2>/dev/null)" = 'scp -P 12022 -o RequestTTY=yes -o PermitLocalCommand=yes -o ControlMaster=auto -o ControlPath=~/.ssh/master-socket/%C -o ProxyJump=time@is.impt:2222,yes@you-r.right:12022,you@are.really.recht:12022,root@ibd:12022 -r try@it.dxng.net:/tmp/ ./tmp
|
||||
scp -P 12022 -o RequestTTY=yes -o PermitLocalCommand=yes -o ControlMaster=auto -o ControlPath=~/.ssh/master-socket/%C -o ForwardX11=yes -o ForwardAgent=yes -r another@host.dxng.net:/tmp/ ./tmp'
|
||||
test "$(riot you@example.com:55 -tD ssh --password -- ping -c 1 2>/dev/null)" = 'ssh -p 55 -o RequestTTY=yes -o ForwardX11=yes -o ForwardAgent=yes -o PasswordAuthentication=yes -o PubkeyAuthentication=no you@example.com ping -c 1'
|
||||
test "$(DFS_TRUST=1 riot time@is.impt:2222/yes@you-r.right/you@are.really.recht./ibd./try@it,another@host -D scp /tmp/ ./tmp 2>/dev/null)" = 'scp -P 12022 -o ServerAliveInterval=60 -o PermitLocalCommand=yes -o ControlMaster=auto -o ControlPersist=5s -o ControlPath=~/.ssh/master-socket/%C -o ProxyJump=time@is.impt:2222,yes@you-r.right:12022,you@are.really.recht:12022,root@ibd:12022 -r try@it.dxng.net:/tmp/ ./tmp
|
||||
scp -P 12022 -o ServerAliveInterval=60 -o PermitLocalCommand=yes -o ControlMaster=auto -o ControlPersist=5s -o ControlPath=~/.ssh/master-socket/%C -o ForwardX11=yes -o ForwardAgent=yes -r another@host.dxng.net:/tmp/ ./tmp'
|
||||
test "$(riot you@example.com:55 -tD --password ssh ping -c 1 2>/dev/null)" = 'ssh -p 55 -o ServerAliveInterval=60 -o ForwardX11=yes -o ForwardAgent=yes -o PasswordAuthentication=yes -o PubkeyAuthentication=no you@example.com ping -c 1'
|
||||
|
||||
# check alias
|
||||
alias p114
|
||||
|
||||
Reference in New Issue
Block a user