diff options
-rw-r--r-- | INSTALL | 41 | ||||
-rw-r--r-- | scripts/git-integration/0001-Patch-sshd-for-the-AUR.patch | 1094 | ||||
-rwxr-xr-x | scripts/git-integration/git-auth.sh | 3 |
3 files changed, 18 insertions, 1120 deletions
@@ -17,39 +17,28 @@ Setup on Arch Linux 4) Create a new MySQL database and a user and import the AUR SQL schema: - $ mysql -uaur -p AUR </srv/http/aurweb/schema/aur-schema.sql + $ mysql -uaur -p AUR </srv/http/aurweb/schema/aur-schema.sql 5) Generate templates for new Git repositories: - $ /srv/http/aurweb/scripts/git-integration/gen-templates.py + $ /srv/http/aurweb/scripts/git-integration/gen-templates.py -6) Clone the OpenSSH project, apply the aurweb sshd patch and run `make`: +6) Create a new user: - $ cd /srv/http/aurweb/ - $ git clone git://anongit.mindrot.org/openssh.git - $ cd openssh - $ git checkout V_6_8_P1 - $ git am ../scripts/git-integration/0001-Patch-sshd-for-the-AUR.patch - $ autoreconf - $ ./configure - $ make + # useradd -U -d /srv/http/aurweb -c 'AUR user' aur -7) Create and edit the sshd configuration: +7) Install the git-auth wrapper script: - $ cd /srv/http/aurweb/ - $ umask 077 - $ mkdir .ssh/ - $ ssh-keygen -f .ssh/ssh_host_rsa_key -N '' -t rsa - $ cp scripts/git-integration/sshd_config .ssh/ + # cd /srv/http/aurweb/scripts/git-integration/ + # cp git-auth.sh /usr/local/bin/aur-git-auth + # chmod 755 /usr/local/bin/aur-git-auth -8) Create a new user and change ownership of the .ssh directory: +8) Configure sshd(8) for the AUR. Add the following lines at the end of your + sshd_config(5) and restart the sshd. Note that OpenSSH 6.9 or newer is + needed! - # useradd -U -d /srv/http/aurweb -c 'AUR user' aur - # chown aur:aur /srv/http/aurweb/.ssh/ + Match User aur + PasswordAuthentication no + AuthorizedKeysCommand /usr/local/bin/aur-git-auth "%t" "%k" + AuthorizedKeysCommandUser aur -9) Add, enable and start systemd unit files for the new sshd: - - # cp /srv/http/aurweb/conf/aur-sshd.socket /etc/systemd/system/ - # cp /srv/http/aurweb/conf/aur-sshd@.service /etc/systemd/system/ - # systemctl enable aur-sshd.socket - # systemctl start aur-sshd.socket diff --git a/scripts/git-integration/0001-Patch-sshd-for-the-AUR.patch b/scripts/git-integration/0001-Patch-sshd-for-the-AUR.patch deleted file mode 100644 index 688b115b..00000000 --- a/scripts/git-integration/0001-Patch-sshd-for-the-AUR.patch +++ /dev/null @@ -1,1094 +0,0 @@ -From 6423ae83d38535687d52097b7854b3c81151fe34 Mon Sep 17 00:00:00 2001 -From: Lukas Fleischer <lfleischer@archlinux.org> -Date: Sat, 11 Apr 2015 12:57:46 +0200 -Subject: [PATCH] Patch sshd for the AUR - -* Apply the latest version of Damien Miller's patch to extend the - parameters to the AuthorizedKeysCommand. - -* Remove the secure path check for the AuthorizedKeysCommand. We are - running the sshd under a non-privileged user who has as little - permissions as possible. In particular, he does not own the directory - that contains the scripts for the Git backend. - -* Prevent from running the sshd as root. - -Signed-off-by: Lukas Fleischer <lfleischer@archlinux.org> ---- - auth2-pubkey.c | 530 +++++++++++++++++++++++++++++++++++++++++++-------------- - servconf.c | 35 ++++ - servconf.h | 8 +- - ssh.c | 5 + - sshd.c | 5 + - sshd_config.5 | 54 +++++- - sshkey.c | 172 +++++++++++-------- - sshkey.h | 1 + - 8 files changed, 606 insertions(+), 204 deletions(-) - -diff --git a/auth2-pubkey.c b/auth2-pubkey.c -index d943efa..2ce0a4b 100644 ---- a/auth2-pubkey.c -+++ b/auth2-pubkey.c -@@ -65,6 +65,9 @@ - #include "monitor_wrap.h" - #include "authfile.h" - #include "match.h" -+#include "ssherr.h" -+#include "channels.h" /* XXX for session.h */ -+#include "session.h" /* XXX for child_set_env(); refactor? */ - - /* import */ - extern ServerOptions options; -@@ -248,6 +251,227 @@ pubkey_auth_info(Authctxt *authctxt, const Key *key, const char *fmt, ...) - free(extra); - } - -+/* -+ * Splits 's' into an argument vector. Handles quoted string and basic -+ * escape characters (\\, \", \'). Caller must free the argument vector -+ * and its members. -+ */ -+static int -+split_argv(const char *s, int *argcp, char ***argvp) -+{ -+ int r = SSH_ERR_INTERNAL_ERROR; -+ int argc = 0, quote, i, j; -+ char *arg, **argv = xcalloc(1, sizeof(*argv)); -+ -+ *argvp = NULL; -+ *argcp = 0; -+ -+ for (i = 0; s[i] != '\0'; i++) { -+ /* Skip leading whitespace */ -+ if (s[i] == ' ' || s[i] == '\t') -+ continue; -+ -+ /* Start of a token */ -+ quote = 0; -+ if (s[i] == '\\' && -+ (s[i + 1] == '\'' || s[i + 1] == '\"' || s[i + 1] == '\\')) -+ i++; -+ else if (s[i] == '\'' || s[i] == '"') -+ quote = s[i++]; -+ -+ argv = xrealloc(argv, (argc + 2), sizeof(*argv)); -+ arg = argv[argc++] = xcalloc(1, strlen(s + i) + 1); -+ argv[argc] = NULL; -+ -+ /* Copy the token in, removing escapes */ -+ for (j = 0; s[i] != '\0'; i++) { -+ if (s[i] == '\\') { -+ if (s[i + 1] == '\'' || -+ s[i + 1] == '\"' || -+ s[i + 1] == '\\') { -+ i++; /* Skip '\' */ -+ arg[j++] = s[i]; -+ } else { -+ /* Unrecognised escape */ -+ arg[j++] = s[i]; -+ } -+ } else if (quote == 0 && (s[i] == ' ' || s[i] == '\t')) -+ break; /* done */ -+ else if (quote != 0 && s[i] == quote) -+ break; /* done */ -+ else -+ arg[j++] = s[i]; -+ } -+ if (s[i] == '\0') { -+ if (quote != 0) { -+ /* Ran out of string looking for close quote */ -+ r = SSH_ERR_INVALID_FORMAT; -+ goto out; -+ } -+ break; -+ } -+ } -+ /* Success */ -+ *argcp = argc; -+ *argvp = argv; -+ argc = 0; -+ argv = NULL; -+ r = 0; -+ out: -+ if (argc != 0 && argv != NULL) { -+ for (i = 0; i < argc; i++) -+ free(argv[i]); -+ free(argv); -+ } -+ return r; -+} -+ -+/* -+ * Runs command in a subprocess. Returns pid on success and a FILE* to the -+ * subprocess' stdout or 0 on failure. -+ * NB. "command" is only used for logging. -+ */ -+static pid_t -+subprocess(const char *tag, struct passwd *pw, const char *command, -+ int ac, char **av, FILE **child) -+{ -+ FILE *f; -+ struct stat st; -+ int devnull, p[2], i; -+ pid_t pid; -+ char *cp, errmsg[512]; -+ u_int envsize; -+ char **child_env; -+ -+ *child = NULL; -+ -+ debug3("%s: %s command \"%s\" running as %s", __func__, -+ tag, command, pw->pw_name); -+ -+ /* Verify the path exists and is safe-ish to execute */ -+ if (*av[0] != '/') { -+ error("%s path is not absolute", tag); -+ return 0; -+ } -+ temporarily_use_uid(pw); -+ if (stat(av[0], &st) < 0) { -+ error("Could not stat %s \"%s\": %s", tag, -+ av[0], strerror(errno)); -+ restore_uid(); -+ return 0; -+ } -+ -+ /* -+ * Run the command; stderr is left in place, stdout is the -+ * authorized_keys output. -+ */ -+ if (pipe(p) != 0) { -+ error("%s: pipe: %s", tag, strerror(errno)); -+ restore_uid(); -+ return 0; -+ } -+ -+ /* -+ * Don't want to call this in the child, where it can fatal() and -+ * run cleanup_exit() code. -+ */ -+ restore_uid(); -+ -+ switch ((pid = fork())) { -+ case -1: /* error */ -+ error("%s: fork: %s", tag, strerror(errno)); -+ close(p[0]); -+ close(p[1]); -+ return 0; -+ case 0: /* child */ -+ /* Prepare a minimal environment for the child. */ -+ envsize = 5; -+ child_env = xcalloc(sizeof(*child_env), envsize); -+ child_set_env(&child_env, &envsize, "PATH", _PATH_STDPATH); -+ child_set_env(&child_env, &envsize, "USER", pw->pw_name); -+ child_set_env(&child_env, &envsize, "LOGNAME", pw->pw_name); -+ child_set_env(&child_env, &envsize, "HOME", pw->pw_dir); -+ if ((cp = getenv("LANG")) != NULL) -+ child_set_env(&child_env, &envsize, "LANG", cp); -+ -+ for (i = 0; i < NSIG; i++) -+ signal(i, SIG_DFL); -+ -+ if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) { -+ error("%s: open %s: %s", tag, _PATH_DEVNULL, -+ strerror(errno)); -+ _exit(1); -+ } -+ /* Keep stderr around a while longer to catch errors */ -+ if (dup2(devnull, STDIN_FILENO) == -1 || -+ dup2(p[1], STDOUT_FILENO) == -1) { -+ error("%s: dup2: %s", tag, strerror(errno)); -+ _exit(1); -+ } -+ closefrom(STDERR_FILENO + 1); -+ -+ /* Don't use permanently_set_uid() here to avoid fatal() */ -+ if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) { -+ error("%s: setresgid %u: %s", tag, (u_int)pw->pw_gid, -+ strerror(errno)); -+ _exit(1); -+ } -+ if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) { -+ error("%s: setresuid %u: %s", tag, (u_int)pw->pw_uid, -+ strerror(errno)); -+ _exit(1); -+ } -+ /* stdin is pointed to /dev/null at this point */ -+ if (dup2(STDIN_FILENO, STDERR_FILENO) == -1) { -+ error("%s: dup2: %s", tag, strerror(errno)); -+ _exit(1); -+ } -+ -+ execve(av[0], av, child_env); -+ error("%s exec \"%s\": %s", tag, command, strerror(errno)); -+ _exit(127); -+ default: /* parent */ -+ break; -+ } -+ -+ close(p[1]); -+ if ((f = fdopen(p[0], "r")) == NULL) { -+ error("%s: fdopen: %s", tag, strerror(errno)); -+ close(p[0]); -+ /* Don't leave zombie child */ -+ kill(pid, SIGTERM); -+ while (waitpid(pid, NULL, 0) == -1 && errno == EINTR) -+ ; -+ return 0; -+ } -+ /* Success */ -+ debug3("%s: %s pid %ld", __func__, tag, (long)pid); -+ *child = f; -+ return pid; -+} -+ -+/* Returns 0 if pid exited cleanly, non-zero otherwise */ -+static int -+exited_cleanly(pid_t pid, const char *tag, const char *cmd) -+{ -+ int status; -+ -+ while (waitpid(pid, &status, 0) == -1) { -+ if (errno != EINTR) { -+ error("%s: waitpid: %s", tag, strerror(errno)); -+ return -1; -+ } -+ } -+ if (WIFSIGNALED(status)) { -+ error("%s %s exited on signal %d", tag, cmd, WTERMSIG(status)); -+ return -1; -+ } else if (WEXITSTATUS(status) != 0) { -+ error("%s %s failed, status %d", tag, cmd, WEXITSTATUS(status)); -+ return -1; -+ } -+ return 0; -+} -+ - static int - match_principals_option(const char *principal_list, struct sshkey_cert *cert) - { -@@ -269,19 +493,13 @@ match_principals_option(const char *principal_list, struct sshkey_cert *cert) - } - - static int --match_principals_file(char *file, struct passwd *pw, struct sshkey_cert *cert) -+process_principals(FILE *f, char *file, struct passwd *pw, -+ struct sshkey_cert *cert) - { -- FILE *f; - char line[SSH_MAX_PUBKEY_BYTES], *cp, *ep, *line_opts; - u_long linenum = 0; - u_int i; - -- temporarily_use_uid(pw); -- debug("trying authorized principals file %s", file); -- if ((f = auth_openprincipals(file, pw, options.strict_modes)) == NULL) { -- restore_uid(); -- return 0; -- } - while (read_keyfile_line(f, file, line, sizeof(line), &linenum) != -1) { - /* Skip leading whitespace. */ - for (cp = line; *cp == ' ' || *cp == '\t'; cp++) -@@ -309,24 +527,119 @@ match_principals_file(char *file, struct passwd *pw, struct sshkey_cert *cert) - } - for (i = 0; i < cert->nprincipals; i++) { - if (strcmp(cp, cert->principals[i]) == 0) { -- debug3("matched principal \"%.100s\" " -- "from file \"%s\" on line %lu", -- cert->principals[i], file, linenum); -+ debug3("%s:%lu: matched principal \"%.100s\"", -+ file == NULL ? "(command)" : file, -+ linenum, cert->principals[i]); - if (auth_parse_options(pw, line_opts, - file, linenum) != 1) - continue; -- fclose(f); -- restore_uid(); - return 1; - } - } - } -+ return 0; -+} -+ -+static int -+match_principals_file(char *file, struct passwd *pw, struct sshkey_cert *cert) -+{ -+ FILE *f; -+ int success; -+ -+ temporarily_use_uid(pw); -+ debug("trying authorized principals file %s", file); -+ if ((f = auth_openprincipals(file, pw, options.strict_modes)) == NULL) { -+ restore_uid(); -+ return 0; -+ } -+ success = process_principals(f, file, pw, cert); - fclose(f); - restore_uid(); -- return 0; -+ return success; - } - - /* -+ * Checks whether principal is allowed in output of command. -+ * returns 1 if the principal is allowed or 0 otherwise. -+ */ -+static int -+match_principals_command(struct passwd *user_pw, struct sshkey *key) -+{ -+ FILE *f = NULL; -+ int ok, found_principal = 0; -+ struct passwd *pw; -+ int i, ac = 0, uid_swapped = 0; -+ pid_t pid; -+ char *username = NULL, *command = NULL, **av = NULL; -+ void (*osigchld)(int); -+ -+ if (options.authorized_principals_command == NULL) -+ return 0; -+ if (options.authorized_principals_command_user == NULL) { -+ error("No user for AuthorizedPrincipalsCommand specified, " -+ "skipping"); -+ return 0; -+ } -+ -+ /* -+ * NB. all returns later this function should go via "out" to -+ * ensure the original SIGCHLD handler is restored properly. -+ */ -+ osigchld = signal(SIGCHLD, SIG_DFL); -+ -+ /* Prepare and verify the user for the command */ -+ username = percent_expand(options.authorized_principals_command_user, -+ "u", user_pw->pw_name, (char *)NULL); -+ pw = getpwnam(username); -+ if (pw == NULL) { -+ error("AuthorizedPrincipalsCommandUser \"%s\" not found: %s", -+ username, strerror(errno)); -+ goto out; -+ } -+ -+ command = percent_expand(options.authorized_principals_command, -+ "u", user_pw->pw_name, "h", user_pw->pw_dir, (char *)NULL); -+ -+ /* Turn the command into an argument vector */ -+ if (split_argv(command, &ac, &av) != 0) { -+ error("AuthorizedPrincipalsCommand \"%s\" contains " -+ "invalid quotes", command); -+ goto out; -+ } -+ if (ac == 0) { -+ error("AuthorizedPrincipalsCommand \"%s\" yielded no arguments", -+ command); -+ goto out; -+ } -+ -+ if ((pid = subprocess("AuthorizedPrincipalsCommand", pw, command, -+ ac, av, &f)) == 0) -+ goto out; -+ -+ uid_swapped = 1; -+ temporarily_use_uid(pw); -+ -+ ok = process_principals(f, NULL, pw, key->cert); -+ -+ if (exited_cleanly(pid, "AuthorizedPrincipalsCommand", command)) -+ goto out; -+ -+ /* Read completed successfully */ -+ found_principal = ok; -+ out: -+ if (f != NULL) -+ fclose(f); -+ signal(SIGCHLD, osigchld); -+ for (i = 0; i < ac; i++) -+ free(av[i]); -+ free(av); -+ if (uid_swapped) -+ restore_uid(); -+ free(command); -+ free(username); -+ return found_principal; -+} -+/* - * Checks whether key is allowed in authorized_keys-format file, - * returns 1 if the key is allowed or 0 otherwise. - */ -@@ -448,7 +761,7 @@ user_cert_trusted_ca(struct passwd *pw, Key *key) - { - char *ca_fp, *principals_file = NULL; - const char *reason; -- int ret = 0; -+ int ret = 0, found_principal = 0; - - if (!key_is_cert(key) || options.trusted_user_ca_keys == NULL) - return 0; -@@ -470,14 +783,20 @@ user_cert_trusted_ca(struct passwd *pw, Key *key) - * against the username. - */ - if ((principals_file = authorized_principals_file(pw)) != NULL) { -- if (!match_principals_file(principals_file, pw, key->cert)) { -- reason = "Certificate does not contain an " -- "authorized principal"; -+ if (match_principals_file(principals_file, pw, key->cert)) -+ found_principal = 1; -+ } -+ /* Try querying command if specified */ -+ if (!found_principal && match_principals_command(pw, key)) -+ found_principal = 1; -+ /* If principals file or command specify, then require a match here */ -+ if (!found_principal && (principals_file != NULL || -+ options.authorized_principals_command != NULL)) { -+ reason = "Certificate does not contain an authorized principal"; - fail_reason: -- error("%s", reason); -- auth_debug_add("%s", reason); -- goto out; -- } -+ error("%s", reason); -+ auth_debug_add("%s", reason); -+ goto out; - } - if (key_cert_check_authority(key, 0, 1, - principals_file == NULL ? pw->pw_name : NULL, &reason) != 0) -@@ -526,144 +845,105 @@ user_key_allowed2(struct passwd *pw, Key *key, char *file) - static int - user_key_command_allowed2(struct passwd *user_pw, Key *key) - { -- FILE *f; -- int ok, found_key = 0; -+ FILE *f = NULL; -+ int r, ok, found_key = 0; - struct passwd *pw; -- struct stat st; -- int status, devnull, p[2], i; -+ int i, uid_swapped = 0, ac = 0; - pid_t pid; -- char *username, errmsg[512]; -+ char *username = NULL, *key_fp = NULL, *keytext = NULL; -+ char *command = NULL, **av = NULL; -+ void (*osigchld)(int); - -- if (options.authorized_keys_command == NULL || -- options.authorized_keys_command[0] != '/') -+ if (options.authorized_keys_command == NULL) - return 0; -- - if (options.authorized_keys_command_user == NULL) { - error("No user for AuthorizedKeysCommand specified, skipping"); - return 0; - } - -+ /* -+ * NB. all returns later this function should go via "out" to -+ * ensure the original SIGCHLD handler is restored properly. -+ */ -+ osigchld = signal(SIGCHLD, SIG_DFL); -+ -+ /* Prepare and verify the user for the command */ - username = percent_expand(options.authorized_keys_command_user, - "u", user_pw->pw_name, (char *)NULL); - pw = getpwnam(username); - if (pw == NULL) { - error("AuthorizedKeysCommandUser \"%s\" not found: %s", - username, strerror(errno)); -- free(username); -- return 0; -+ goto out; - } -- free(username); -- -- temporarily_use_uid(pw); - -- if (stat(options.authorized_keys_command, &st) < 0) { -- error("Could not stat AuthorizedKeysCommand \"%s\": %s", -- options.authorized_keys_command, strerror(errno)); -+ /* Prepare AuthorizedKeysCommand */ -+ if ((key_fp = sshkey_fingerprint(key, options.fingerprint_hash, -+ SSH_FP_DEFAULT)) == NULL) { -+ error("%s: sshkey_fingerprint failed", __func__); - goto out; - } -- if (auth_secure_path(options.authorized_keys_command, &st, NULL, 0, -- errmsg, sizeof(errmsg)) != 0) { -- error("Unsafe AuthorizedKeysCommand: %s", errmsg); -+ if ((r = sshkey_to_base64(key, &keytext)) != 0) { -+ error("%s: sshkey_to_base64 failed: %s", __func__, ssh_err(r)); - goto out; - } -- -- if (pipe(p) != 0) { -- error("%s: pipe: %s", __func__, strerror(errno)); -+ command = percent_expand(options.authorized_keys_command, -+ "u", user_pw->pw_name, "h", user_pw->pw_dir, -+ "t", sshkey_ssh_name(key), "f", key_fp, "k", keytext, (char *)NULL); -+ -+ /* Turn the command into an argument vector */ -+ if (split_argv(command, &ac, &av) != 0) { -+ error("AuthorizedKeysCommand \"%s\" contains invalid quotes", -+ command); -+ goto out; -+ } -+ if (ac == 0) { -+ error("AuthorizedKeysCommand \"%s\" yielded no arguments", -+ command); - goto out; - } -- -- debug3("Running AuthorizedKeysCommand: \"%s %s\" as \"%s\"", -- options.authorized_keys_command, user_pw->pw_name, pw->pw_name); - - /* -- * Don't want to call this in the child, where it can fatal() and -- * run cleanup_exit() code. -+ * If AuthorizedKeysCommand was run without arguments -+ * then fall back to the old behaviour of passing the -+ * target username as a single argument. - */ -- restore_uid(); -- -- switch ((pid = fork())) { -- case -1: /* error */ -- error("%s: fork: %s", __func__, strerror(errno)); -- close(p[0]); -- close(p[1]); -- return 0; -- case 0: /* child */ -- for (i = 0; i < NSIG; i++) -- signal(i, SIG_DFL); -- -- if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) { -- error("%s: open %s: %s", __func__, _PATH_DEVNULL, -- strerror(errno)); -- _exit(1); -- } -- /* Keep stderr around a while longer to catch errors */ -- if (dup2(devnull, STDIN_FILENO) == -1 || -- dup2(p[1], STDOUT_FILENO) == -1) { -- error("%s: dup2: %s", __func__, strerror(errno)); -- _exit(1); -- } -- closefrom(STDERR_FILENO + 1); -- -- /* Don't use permanently_set_uid() here to avoid fatal() */ -- if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) { -- error("setresgid %u: %s", (u_int)pw->pw_gid, -- strerror(errno)); -- _exit(1); -- } -- if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) { -- error("setresuid %u: %s", (u_int)pw->pw_uid, -- strerror(errno)); -- _exit(1); -- } -- /* stdin is pointed to /dev/null at this point */ -- if (dup2(STDIN_FILENO, STDERR_FILENO) == -1) { -- error("%s: dup2: %s", __func__, strerror(errno)); -- _exit(1); -- } -- -- execl(options.authorized_keys_command, -- options.authorized_keys_command, user_pw->pw_name, NULL); -- -- error("AuthorizedKeysCommand %s exec failed: %s", -- options.authorized_keys_command, strerror(errno)); -- _exit(127); -- default: /* parent */ -- break; -+ if (ac == 1) { -+ av = xrealloc(av, ac + 2, sizeof(*av)); -+ av[1] = xstrdup(user_pw->pw_name); -+ av[2] = NULL; -+ /* Fix up command too, since it is used in log messages */ -+ free(command); -+ xasprintf(&command, "%s %s", av[0], av[1]); - } - -+ if ((pid = subprocess("AuthorizedKeysCommand", pw, command, -+ ac, av, &f)) == 0) -+ goto out; -+ -+ uid_swapped = 1; - temporarily_use_uid(pw); - -- close(p[1]); -- if ((f = fdopen(p[0], "r")) == NULL) { -- error("%s: fdopen: %s", __func__, strerror(errno)); -- close(p[0]); -- /* Don't leave zombie child */ -- kill(pid, SIGTERM); -- while (waitpid(pid, NULL, 0) == -1 && errno == EINTR) -- ; -- goto out; -- } - ok = check_authkeys_file(f, options.authorized_keys_command, key, pw); -- fclose(f); - -- while (waitpid(pid, &status, 0) == -1) { -- if (errno != EINTR) { -- error("%s: waitpid: %s", __func__, strerror(errno)); -- goto out; -- } -- } -- if (WIFSIGNALED(status)) { -- error("AuthorizedKeysCommand %s exited on signal %d", -- options.authorized_keys_command, WTERMSIG(status)); -+ if (exited_cleanly(pid, "AuthorizedKeysCommand", command)) - goto out; -- } else if (WEXITSTATUS(status) != 0) { -- error("AuthorizedKeysCommand %s returned status %d", -- options.authorized_keys_command, WEXITSTATUS(status)); -- goto out; -- } -+ -+ /* Read completed successfully */ - found_key = ok; - out: -- restore_uid(); -+ if (f != NULL) -+ fclose(f); -+ signal(SIGCHLD, osigchld); -+ for (i = 0; i < ac; i++) -+ free(av[i]); -+ free(av); -+ if (uid_swapped) -+ restore_uid(); -+ free(command); -+ free(username); -+ free(key_fp); -+ free(keytext); - return found_key; - } - -diff --git a/servconf.c b/servconf.c -index 3185462..510cdde 100644 ---- a/servconf.c -+++ b/servconf.c -@@ -159,6 +159,8 @@ initialize_server_options(ServerOptions *options) - options->revoked_keys_file = NULL; - options->trusted_user_ca_keys = NULL; - options->authorized_principals_file = NULL; -+ options->authorized_principals_command = NULL; -+ options->authorized_principals_command_user = NULL; - options->ip_qos_interactive = -1; - options->ip_qos_bulk = -1; - options->version_addendum = NULL; -@@ -396,6 +398,7 @@ typedef enum { - sUsePrivilegeSeparation, sAllowAgentForwarding, - sHostCertificate, - sRevokedKeys, sTrustedUserCAKeys, sAuthorizedPrincipalsFile, -+ sAuthorizedPrincipalsCommand, sAuthorizedPrincipalsCommandUser, - sKexAlgorithms, sIPQoS, sVersionAddendum, - sAuthorizedKeysCommand, sAuthorizedKeysCommandUser, - sAuthenticationMethods, sHostKeyAgent, sPermitUserRC, -@@ -528,6 +531,8 @@ static struct { - { "ipqos", sIPQoS, SSHCFG_ALL }, - { "authorizedkeyscommand", sAuthorizedKeysCommand, SSHCFG_ALL }, - { "authorizedkeyscommanduser", sAuthorizedKeysCommandUser, SSHCFG_ALL }, -+ { "authorizedprincipalscommand", sAuthorizedPrincipalsCommand, SSHCFG_ALL }, -+ { "authorizedprincipalscommanduser", sAuthorizedPrincipalsCommandUser, SSHCFG_ALL }, - { "versionaddendum", sVersionAddendum, SSHCFG_GLOBAL }, - { "authenticationmethods", sAuthenticationMethods, SSHCFG_ALL }, - { "streamlocalbindmask", sStreamLocalBindMask, SSHCFG_ALL }, -@@ -1697,6 +1702,34 @@ process_server_config_line(ServerOptions *options, char *line, - *charptr = xstrdup(arg); - break; - -+ case sAuthorizedPrincipalsCommand: -+ if (cp == NULL) -+ fatal("%.200s line %d: Missing argument.", filename, -+ linenum); -+ len = strspn(cp, WHITESPACE); -+ if (*activep && -+ options->authorized_principals_command == NULL) { -+ if (cp[len] != '/' && strcasecmp(cp + len, "none") != 0) -+ fatal("%.200s line %d: " -+ "AuthorizedPrincipalsCommand must be " -+ "an absolute path", filename, linenum); -+ options->authorized_principals_command = -+ xstrdup(cp + len); -+ } -+ return 0; -+ -+ case sAuthorizedPrincipalsCommandUser: -+ charptr = &options->authorized_principals_command_user; -+ -+ arg = strdelim(&cp); -+ if (!arg || *arg == '\0') -+ fatal("%s line %d: missing " -+ "AuthorizedPrincipalsCommandUser argument.", -+ filename, linenum); -+ if (*activep && *charptr == NULL) -+ *charptr = xstrdup(arg); -+ break; -+ - case sAuthenticationMethods: - if (*activep && options->num_auth_methods == 0) { - while ((arg = strdelim(&cp)) && *arg != '\0') { -@@ -2166,6 +2199,8 @@ dump_config(ServerOptions *o) - dump_cfg_string(sVersionAddendum, o->version_addendum); - dump_cfg_string(sAuthorizedKeysCommand, o->authorized_keys_command); - dump_cfg_string(sAuthorizedKeysCommandUser, o->authorized_keys_command_user); -+ dump_cfg_string(sAuthorizedPrincipalsCommand, o->authorized_principals_command); -+ dump_cfg_string(sAuthorizedPrincipalsCommandUser, o->authorized_principals_command_user); - dump_cfg_string(sHostKeyAgent, o->host_key_agent); - dump_cfg_string(sKexAlgorithms, - o->kex_algorithms ? o->kex_algorithms : KEX_SERVER_KEX); -diff --git a/servconf.h b/servconf.h -index 9922f0c..35d6673 100644 ---- a/servconf.h -+++ b/servconf.h -@@ -176,9 +176,11 @@ typedef struct { - char *chroot_directory; - char *revoked_keys_file; - char *trusted_user_ca_keys; -- char *authorized_principals_file; - char *authorized_keys_command; - char *authorized_keys_command_user; -+ char *authorized_principals_file; -+ char *authorized_principals_command; -+ char *authorized_principals_command_user; - - int64_t rekey_limit; - int rekey_interval; -@@ -214,9 +216,11 @@ struct connection_info { - M_CP_STROPT(banner); \ - M_CP_STROPT(trusted_user_ca_keys); \ - M_CP_STROPT(revoked_keys_file); \ -- M_CP_STROPT(authorized_principals_file); \ - M_CP_STROPT(authorized_keys_command); \ - M_CP_STROPT(authorized_keys_command_user); \ -+ M_CP_STROPT(authorized_principals_file); \ -+ M_CP_STROPT(authorized_principals_command); \ -+ M_CP_STROPT(authorized_principals_command_user); \ - M_CP_STROPT(hostbased_key_types); \ - M_CP_STROPT(pubkey_key_types); \ - M_CP_STRARRAYOPT(authorized_keys_files, num_authkeys_files); \ -diff --git a/ssh.c b/ssh.c -index 0ad82f0..abf4e54 100644 ---- a/ssh.c -+++ b/ssh.c -@@ -548,6 +548,11 @@ main(int ac, char **av) - original_real_uid = getuid(); - original_effective_uid = geteuid(); - -+ if (original_effective_uid == 0) { -+ fprintf(stderr, "this is a patched version of the sshd that must not be run as root.\n"); -+ exit(1); -+ } -+ - /* - * Use uid-swapping to give up root privileges for the duration of - * option processing. We will re-instantiate the rights when we are -diff --git a/sshd.c b/sshd.c -index 6aa17fa..672c486 100644 ---- a/sshd.c -+++ b/sshd.c -@@ -1694,6 +1694,11 @@ main(int ac, char **av) - strcasecmp(options.authorized_keys_command, "none") != 0)) - fatal("AuthorizedKeysCommand set without " - "AuthorizedKeysCommandUser"); -+ if (options.authorized_principals_command_user == NULL && -+ (options.authorized_principals_command != NULL && -+ strcasecmp(options.authorized_principals_command, "none") != 0)) -+ fatal("AuthorizedPrincipalsCommand set without " -+ "AuthorizedPrincipalsCommandUser"); - - /* - * Check whether there is any path through configured auth methods. -diff --git a/sshd_config.5 b/sshd_config.5 -index 6dce0c7..a267af9 100644 ---- a/sshd_config.5 -+++ b/sshd_config.5 -@@ -230,9 +230,21 @@ The default is not to require multiple authentication; successful completion - of a single authentication method is sufficient. - .It Cm AuthorizedKeysCommand - Specifies a program to be used to look up the user's public keys. --The program must be owned by root and not writable by group or others. --It will be invoked with a single argument of the username --being authenticated, and should produce on standard output zero or -+The program must be owned by root, not writable by group or others and -+specified by an absolute path. -+.Pp -+Arguments to -+.Cm AuthorizedKeysCommand -+may be provided using the following tokens, which will be expanded -+at runtime: %% is replaced by a literal '%', %u is replaced by the -+username being authenticated, %h is replaced by the home directory -+of the user being authenticated, %t is replaced with the key type -+offered for authentication, %f is replaced with the fingerprint of -+the key, and %k is replaced with the key being offered for authentication. -+If no arguments are specified then the username of the target user -+will be supplied. -+.Pp -+The program should produce on standard output zero or - more lines of authorized_keys output (see AUTHORIZED_KEYS in - .Xr sshd 8 ) . - If a key supplied by AuthorizedKeysCommand does not successfully authenticate -@@ -271,6 +283,42 @@ directory. - Multiple files may be listed, separated by whitespace. - The default is - .Dq .ssh/authorized_keys .ssh/authorized_keys2 . -+.It Cm AuthorizedPrincipalsCommand -+Specifies a program to be used to generate the list of allowed -+certificate principals as per -+.Cm AuthorizedPrincipalsFile . -+The program must be owned by root, not writable by group or others and -+specified by an absolute path. -+.Pp -+Arguments to -+.Cm AuthorizedPrincipalsCommand -+may be provided using the following tokens, which will be expanded -+at runtime: %% is replaced by a literal '%', %u is replaced by the -+username being authenticated and %h is replaced by the home directory -+of the user being authenticated. -+.Pp -+The program should produce on standard output zero or -+more lines of -+.Cm AuthorizedPrincipalsFile -+output. -+If either -+.Cm AuthorizedPrincipalsCommand -+or -+.Cm AuthorizedPrincipalsFile -+is specified, then certificates offered by the client for authentication -+must contain a principal that is listed. -+By default, no AuthorizedPrincipalsCommand is run. -+.It Cm AuthorizedPrincipalsCommandUser -+Specifies the user under whose account the AuthorizedPrincipalsCommand is run. -+It is recommended to use a dedicated user that has no other role on the host -+than running authorized principals commands. -+If -+.Cm AuthorizedPrincipalsCommand -+is specified but -+.Cm AuthorizedPrincipalsCommandUser -+is not, then -+.Xr sshd 8 -+will refuse to start. - .It Cm AuthorizedPrincipalsFile - Specifies a file that lists principal names that are accepted for - certificate authentication. -diff --git a/sshkey.c b/sshkey.c -index 3cc3f44..ecb61fd 100644 ---- a/sshkey.c -+++ b/sshkey.c -@@ -761,6 +761,12 @@ to_blob_buf(const struct sshkey *key, struct sshbuf *b, int force_plain) - if (key == NULL) - return SSH_ERR_INVALID_ARGUMENT; - -+ if (sshkey_is_cert(key)) { -+ if (key->cert == NULL) -+ return SSH_ERR_EXPECTED_CERT; -+ if (sshbuf_len(key->cert->certblob) == 0) -+ return SSH_ERR_KEY_LACKS_CERTBLOB; -+ } - type = force_plain ? sshkey_type_plain(key->type) : key->type; - typename = sshkey_ssh_name_from_type_nid(type, key->ecdsa_nid); - -@@ -1409,98 +1415,116 @@ sshkey_read(struct sshkey *ret, char **cpp) - } - - int --sshkey_write(const struct sshkey *key, FILE *f) -+sshkey_to_base64(const struct sshkey *key, char **b64p) - { -- int ret = SSH_ERR_INTERNAL_ERROR; -- struct sshbuf *b = NULL, *bb = NULL; -+ int r = SSH_ERR_INTERNAL_ERROR; -+ struct sshbuf *b = NULL; - char *uu = NULL; -+ -+ if (b64p != NULL) -+ *b64p = NULL; -+ if ((b = sshbuf_new()) == NULL) -+ return SSH_ERR_ALLOC_FAIL; -+ if ((r = sshkey_putb(key, b)) != 0) -+ goto out; -+ if ((uu = sshbuf_dtob64(b)) == NULL) { -+ r = SSH_ERR_ALLOC_FAIL; -+ goto out; -+ } -+ /* Success */ -+ if (b64p != NULL) { -+ *b64p = uu; -+ uu = NULL; -+ } -+ r = 0; -+ out: -+ sshbuf_free(b); -+ free(uu); -+ return r; -+} -+ -+static int -+sshkey_format_rsa1(const struct sshkey *key, struct sshbuf *b) -+{ -+ int r = SSH_ERR_INTERNAL_ERROR; - #ifdef WITH_SSH1 - u_int bits = 0; - char *dec_e = NULL, *dec_n = NULL; --#endif /* WITH_SSH1 */ - -- if (sshkey_is_cert(key)) { -- if (key->cert == NULL) -- return SSH_ERR_EXPECTED_CERT; -- if (sshbuf_len(key->cert->certblob) == 0) -- return SSH_ERR_KEY_LACKS_CERTBLOB; -+ if (key->rsa == NULL || key->rsa->e == NULL || -+ key->rsa->n == NULL) { -+ r = SSH_ERR_INVALID_ARGUMENT; -+ goto out; - } -- if ((b = sshbuf_new()) == NULL) -- return SSH_ERR_ALLOC_FAIL; -- switch (key->type) { --#ifdef WITH_SSH1 -- case KEY_RSA1: -- if (key->rsa == NULL || key->rsa->e == NULL || -- key->rsa->n == NULL) { -- ret = SSH_ERR_INVALID_ARGUMENT; -- goto out; -- } -- if ((dec_e = BN_bn2dec(key->rsa->e)) == NULL || -- (dec_n = BN_bn2dec(key->rsa->n)) == NULL) { -- ret = SSH_ERR_ALLOC_FAIL; -- goto out; -- } -- /* size of modulus 'n' */ -- if ((bits = BN_num_bits(key->rsa->n)) <= 0) { -- ret = SSH_ERR_INVALID_ARGUMENT; -- goto out; -- } -- if ((ret = sshbuf_putf(b, "%u %s %s", bits, dec_e, dec_n)) != 0) -- goto out; -+ if ((dec_e = BN_bn2dec(key->rsa->e)) == NULL || -+ (dec_n = BN_bn2dec(key->rsa->n)) == NULL) { -+ r = SSH_ERR_ALLOC_FAIL; -+ goto out; -+ } -+ /* size of modulus 'n' */ -+ if ((bits = BN_num_bits(key->rsa->n)) <= 0) { -+ r = SSH_ERR_INVALID_ARGUMENT; -+ goto out; -+ } -+ if ((r = sshbuf_putf(b, "%u %s %s", bits, dec_e, dec_n)) != 0) -+ goto out; -+ -+ /* Success */ -+ r = 0; -+ out: -+ if (dec_e != NULL) -+ OPENSSL_free(dec_e); -+ if (dec_n != NULL) -+ OPENSSL_free(dec_n); - #endif /* WITH_SSH1 */ -- break; --#ifdef WITH_OPENSSL -- case KEY_DSA: -- case KEY_DSA_CERT_V00: -- case KEY_DSA_CERT: -- case KEY_ECDSA: -- case KEY_ECDSA_CERT: -- case KEY_RSA: -- case KEY_RSA_CERT_V00: -- case KEY_RSA_CERT: --#endif /* WITH_OPENSSL */ -- case KEY_ED25519: -- case KEY_ED25519_CERT: -- if ((bb = sshbuf_new()) == NULL) { -- ret = SSH_ERR_ALLOC_FAIL; -- goto out; -- } -- if ((ret = sshkey_putb(key, bb)) != 0) -- goto out; -- if ((uu = sshbuf_dtob64(bb)) == NULL) { -- ret = SSH_ERR_ALLOC_FAIL; -+ -+ return r; -+} -+ -+static int -+sshkey_format_text(const struct sshkey *key, struct sshbuf *b) -+{ -+ int r = SSH_ERR_INTERNAL_ERROR; -+ char *uu = NULL; -+ -+ if (key->type == KEY_RSA1) { -+ if ((r = sshkey_format_rsa1(key, b)) != 0) - goto out; -- } -- if ((ret = sshbuf_putf(b, "%s ", sshkey_ssh_name(key))) != 0) -+ } else { -+ /* Unsupported key types handled in sshkey_to_base64() */ -+ if ((r = sshkey_to_base64(key, &uu)) != 0) - goto out; -- if ((ret = sshbuf_put(b, uu, strlen(uu))) != 0) -+ if ((r = sshbuf_putf(b, "%s %s", -+ sshkey_ssh_name(key), uu)) != 0) - goto out; -- break; -- default: -- ret = SSH_ERR_KEY_TYPE_UNKNOWN; -- goto out; - } -+ r = 0; -+ out: -+ free(uu); -+ return r; -+} -+ -+int -+sshkey_write(const struct sshkey *key, FILE *f) -+{ -+ struct sshbuf *b = NULL; -+ int r = SSH_ERR_INTERNAL_ERROR; -+ -+ if ((b = sshbuf_new()) == NULL) -+ return SSH_ERR_ALLOC_FAIL; -+ if ((r = sshkey_format_text(key, b)) != 0) -+ goto out; - if (fwrite(sshbuf_ptr(b), sshbuf_len(b), 1, f) != 1) { - if (feof(f)) - errno = EPIPE; -- ret = SSH_ERR_SYSTEM_ERROR; -+ r = SSH_ERR_SYSTEM_ERROR; - goto out; - } -- ret = 0; -+ /* Success */ -+ r = 0; - out: -- if (b != NULL) -- sshbuf_free(b); -- if (bb != NULL) -- sshbuf_free(bb); -- if (uu != NULL) -- free(uu); --#ifdef WITH_SSH1 -- if (dec_e != NULL) -- OPENSSL_free(dec_e); -- if (dec_n != NULL) -- OPENSSL_free(dec_n); --#endif /* WITH_SSH1 */ -- return ret; -+ sshbuf_free(b); -+ return r; - } - - const char * -diff --git a/sshkey.h b/sshkey.h -index 62c1c3e..98f1ca9 100644 ---- a/sshkey.h -+++ b/sshkey.h -@@ -163,6 +163,7 @@ int sshkey_from_blob(const u_char *, size_t, struct sshkey **); - int sshkey_fromb(struct sshbuf *, struct sshkey **); - int sshkey_froms(struct sshbuf *, struct sshkey **); - int sshkey_to_blob(const struct sshkey *, u_char **, size_t *); -+int sshkey_to_base64(const struct sshkey *, char **); - int sshkey_putb(const struct sshkey *, struct sshbuf *); - int sshkey_puts(const struct sshkey *, struct sshbuf *); - int sshkey_plain_to_blob(const struct sshkey *, u_char **, size_t *); --- -2.3.5 - diff --git a/scripts/git-integration/git-auth.sh b/scripts/git-integration/git-auth.sh new file mode 100755 index 00000000..c6a54012 --- /dev/null +++ b/scripts/git-integration/git-auth.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +/srv/http/aurweb/scripts/git-integration/git-auth.py "$1" "$2" |