Skip to content

Commit

Permalink
ssh-agent: remove all keys upon SIGUSR1..
Browse files Browse the repository at this point in the history
With the advent of per-user temporary directories it became
hard for an administrator to remove all keys from all running
ssh-agent instances; what formerly could be done like so

   if command -v ssh-add >/dev/null 2>&1; then
      for a in /tmp/ssh-*/agent.*; do
         [ -e "$a" ] || continue
         act "SSH_AUTH_SOCK=\"$a\" ssh-add -D </dev/null >/dev/null 2>&1 &"
         inc
      done
   fi

has become a major undertaking, especially with even more
containerization.  Being able to remove all keys from all agents
with a single command seems so desirable that it is available in
other agents in the software world.
  • Loading branch information
sdaoden committed Mar 6, 2024
1 parent c47e1c9 commit 9741593
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 27 deletions.
45 changes: 32 additions & 13 deletions regress/agent.sh
Original file line number Diff line number Diff line change
Expand Up @@ -157,30 +157,49 @@ done

## Deletion tests.

delete_cycle() {
# make sure they're gone
${SSHADD} -l > /dev/null 2>&1
r=$?
if [ $r -ne 1 ]; then
fail "ssh-add -l returned unexpected exit code: $r"
fi
trace "readd keys"
# re-add keys/certs to agent
for t in ${SSH_KEYTYPES}; do
${SSHADD} $OBJ/$t-agent-private >/dev/null 2>&1 || \
fail "ssh-add failed exit code $?"
done
# make sure they are there
${SSHADD} -l > /dev/null 2>&1
r=$?
if [ $r -ne 0 ]; then
fail "ssh-add -l failed: exit code $r"
fi
}

trace "delete all agent keys"
${SSHADD} -D > /dev/null 2>&1
r=$?
if [ $r -ne 0 ]; then
fail "ssh-add -D failed: exit code $r"
fi
# make sure they're gone
${SSHADD} -l > /dev/null 2>&1
delete_cycle

trace "delete all agent keys via SIGUSR1"
kill -USR1 $SSH_AGENT_PID >/dev/null 2>&1
r=$?
if [ $r -ne 1 ]; then
fail "ssh-add -l returned unexpected exit code: $r"
if [ $r -ne 0 ]; then
fail "kill -USR1: exit code $r"
fi
trace "readd keys"
# re-add keys/certs to agent
for t in ${SSH_KEYTYPES}; do
${SSHADD} $OBJ/$t-agent-private >/dev/null 2>&1 || \
fail "ssh-add failed exit code $?"
done
# make sure they are there
${SSHADD} -l > /dev/null 2>&1
delete_cycle
trace ".. and again"
kill -USR1 $SSH_AGENT_PID >/dev/null 2>&1
r=$?
if [ $r -ne 0 ]; then
fail "ssh-add -l failed: exit code $r"
fail "kill -USR1: exit code $r"
fi
delete_cycle

check_key_absent() {
${SSHADD} -L | grep "^$1 " >/dev/null
Expand Down
3 changes: 3 additions & 0 deletions ssh-agent.1
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@ will automatically use them if present.
is also used to remove keys from
.Nm
and to query the keys that are held in one.
.Nm
will remove all keys when it receives the user signal
.Dv SIGUSR1 .
.Pp
Connections to
.Nm
Expand Down
66 changes: 52 additions & 14 deletions ssh-agent.c
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ static int fingerprint_hash = SSH_FP_HASH_DEFAULT;
/* Refuse signing of non-SSH messages for web-origin FIDO keys */
static int restrict_websafe = 1;

/* Request to remove_all_identities() pending */
static volatile sig_atomic_t received_sigusr1 = 0;

static void
close_socket(SocketEntry *e)
{
Expand Down Expand Up @@ -1019,11 +1022,10 @@ process_remove_identity(SocketEntry *e)
}

static void
process_remove_all_identities(SocketEntry *e)
remove_all_identities(void)
{
Identity *id;

debug2_f("entering");
/* Loop over all identities and clear the keys. */
for (id = TAILQ_FIRST(&idtab->idlist); id;
id = TAILQ_FIRST(&idtab->idlist)) {
Expand All @@ -1033,6 +1035,13 @@ process_remove_all_identities(SocketEntry *e)

/* Mark that there are no identities. */
idtab->nentries = 0;
}

static void
process_remove_all_identities(SocketEntry *e)
{
debug2_f("entering");
remove_all_identities();

/* Send success. */
send_status(e, 1);
Expand Down Expand Up @@ -2062,7 +2071,8 @@ after_poll(struct pollfd *pfd, size_t npfd, u_int maxfds)
}

static int
prepare_poll(struct pollfd **pfdp, size_t *npfdp, int *timeoutp, u_int maxfds)
prepare_poll(struct pollfd **pfdp, size_t *npfdp, struct timespec **ptimeoutpp,
u_int maxfds)
{
struct pollfd *pfd = *pfdp;
size_t i, j, npfd = 0;
Expand Down Expand Up @@ -2124,17 +2134,17 @@ prepare_poll(struct pollfd **pfdp, size_t *npfdp, int *timeoutp, u_int maxfds)
break;
}
}

/* This also removes expired keys */
deadline = reaper();
if (parent_alive_interval != 0)
deadline = (deadline == 0) ? parent_alive_interval :
MINIMUM(deadline, parent_alive_interval);
if (deadline == 0) {
*timeoutp = -1; /* INFTIM */
} else {
if (deadline > INT_MAX / 1000)
*timeoutp = INT_MAX / 1000;
else
*timeoutp = deadline * 1000;
if (deadline == 0)
*ptimeoutpp = NULL; /* INFTIM */
else {
(*ptimeoutpp)->tv_nsec = 0;
(*ptimeoutpp)->tv_sec = deadline;
}
return (1);
}
Expand Down Expand Up @@ -2168,6 +2178,13 @@ cleanup_handler(int sig)
_exit(2);
}

/*ARGSUSED*/
static void
onsigusr1(int sig)
{
received_sigusr1 = 1;
}

static void
check_parent_exists(void)
{
Expand Down Expand Up @@ -2209,7 +2226,8 @@ main(int ac, char **av)
char pidstrbuf[1 + 3 * sizeof pid];
size_t len;
mode_t prev_mask;
int timeout = -1; /* INFTIM */
sigset_t psigset, psigseto;
struct timespec ptimeout, *ptimeoutp;
struct pollfd *pfd = NULL;
size_t npfd = 0;
u_int maxfds;
Expand Down Expand Up @@ -2447,18 +2465,38 @@ main(int ac, char **av)
ssh_signal(SIGINT, (d_flag | D_flag) ? cleanup_handler : SIG_IGN);
ssh_signal(SIGHUP, cleanup_handler);
ssh_signal(SIGTERM, cleanup_handler);
ssh_signal(SIGUSR1, onsigusr1);

if (pledge("stdio rpath cpath unix id proc exec", NULL) == -1)
fatal("%s: pledge: %s", __progname, strerror(errno));
platform_pledge_agent();

/*
* Prepare signal mask that we use to block signals that might set
* received_sigusr1, so that we are guaranteed to immediately wake up
* the ppoll if a signal is received after the flag is checked.
*/
sigemptyset(&psigset);
if (d_flag | D_flag)
sigaddset(&psigset, SIGINT);
sigaddset(&psigset, SIGHUP);
sigaddset(&psigset, SIGTERM);
sigaddset(&psigset, SIGUSR1);

while (1) {
prepare_poll(&pfd, &npfd, &timeout, maxfds);
result = poll(pfd, npfd, timeout);
sigprocmask(SIG_BLOCK, &psigset, &psigseto);
if (received_sigusr1) {
received_sigusr1 = 0;
remove_all_identities();
}
/* This also removes expired keys */
ptimeoutp = &ptimeout;
prepare_poll(&pfd, &npfd, &ptimeoutp, maxfds);
result = ppoll(pfd, npfd, ptimeoutp, &psigseto);
saved_errno = errno;
sigprocmask(SIG_SETMASK, &psigseto, NULL);
if (parent_alive_interval != 0)
check_parent_exists();
(void) reaper(); /* remove expired keys */
if (result == -1) {
if (saved_errno == EINTR)
continue;
Expand Down

0 comments on commit 9741593

Please sign in to comment.