After multiple hours of searching the internet for some option to enable running multiple instances of gpg-agent
with different gnupg homes I tried just restricting the access to the global socket location and it worked. It fell back to placing the socket files in the gnupg home directory. I used bwrap
to do that. Here's the full command that worked: bwrap --dev-bind / / --tmpfs /run/user/$(id -u)/gnupg gpg-agent ...
. Since your question is about scripts in general, you probably can't rely on bwrap
being installed, so the next best thing is a shim that prevents gpg-agent
from using the user's xdg runtime directory for its sockets.
After looking through the output of strace, the switch of location when running under bwrap
to gnupg home seems to happen after a stat
on /run/user/${UID}/gnupg
is issued. Looking through the gpg-agent.c
code this seems to do that check:
/* Check that it is a directory, owned by the user, and only the
* user has permissions to use it. */
if (!S_ISDIR(sb.st_mode)
|| sb.st_uid != getuid ()
|| (sb.st_mode & (S_IRWXG|S_IRWXO)))
{
*r_info |= 4; /* Bad permissions or not a directory. */
if (!skip_checks)
goto leave;
}
Using this we can just tell gpg-agent
that the /run/user/${UID}/gnupg
exists, but is not a directory, so it will fail the first of these checks.
Here's the code for a shim that does just that (could be better, but it works):
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <dlfcn.h>
#include <stdio.h>
#define STR_MAX 4096
#define MIN(a, b) (a < b ? a : b)
#define MAX(a, b) (a > b ? a : b)
// Set up checking stuff
#define MAX_UID_LEN 11
#define PREFIX_1 "/run/user"
#define PREFIX_2 "/var/run/user"
#define TEST_LEN_1 (sizeof(PREFIX_1) - 1 + 1 + MAX_UID_LEN + sizeof("/gnupg") - 1)
#define TEST_LEN_2 (sizeof(PREFIX_2) - 1 + 1 + MAX_UID_LEN + sizeof("/gnupg") - 1)
#define MAX_TEST_LEN MAX(TEST_LEN_1, TEST_LEN_2)
// Override stat function
int stat(const char *restrict pathname, struct stat *restrict statbuf) {
int (*original_stat)(const char *restrict, struct stat *restrict) = dlsym(RTLD_NEXT, "stat");
// Call original stat function
int retval = original_stat(pathname, statbuf);
if (retval == 0) {
// Check if a path we want to modify
size_t pathlen = strnlen(pathname, STR_MAX);
char path_check[MAX_TEST_LEN + 1];
snprintf(path_check, MAX_TEST_LEN + 1, "%s/%u/gnupg", PREFIX_1, getuid());
if (strncmp(pathname, path_check, MIN(MAX_TEST_LEN, pathlen)) == 0) {
// Report a regular file with perms: rwxrwxrwx
statbuf->st_mode = S_IFREG|0777;
}
snprintf(path_check, MAX_TEST_LEN + 1, "%s/%u/gnupg", PREFIX_2, getuid());
if (strncmp(pathname, path_check, MIN(MAX_TEST_LEN, pathlen)) == 0) {
// Report a regular file with perms: rwxrwxrwx
statbuf->st_mode = S_IFREG|0777;
}
}
return retval;
}
You can compile it with: clang -Wall -O2 -fpic -shared -ldl -o gpg-shim.so gpg-shim.c
and then add it to LD_PRELOAD
and it should then allow you to run multiple gpg-agent
s as long as they have different gnupg homes.
I know this answer is really late, but I hope it can help some people that were, like me, looking for a way to run multiple gpg-agent
s with different homedirs. (For my specific case, I wanted to run multiple gpg-agent
instances to have keys cached different amounts of time).
GNUPGHOME
. From GnuPG on ArchWiki: “If you use non-default GnuPG home directory, you will need to edit all socket files to use the values ofgpgconf --list-dirs
. The socket names use the hash of the non-default GnuPG home directory, so you can hardcode it without worrying about it changing.” In this case you don’t need to worry about socket path collision. – Audiometer$GNUPGHOME
. Have you actually gotten this to work? – Engels/run/user/1001/gnupg/d.19dny8pcrzpq1uj8ykkupwfr/
as the directory of sockets on my machine. Another example,GNUPGHOME=/tmp/gnupg gpgconf --list-dirs socketdir
gives me/run/user/1001/gnupg/d.p63as1i1pzzqkuqzs6d175fn
. – Audiometer