> On 26 Jul 2015, at 7:07 pm, Nicholas Marriott <[email protected]> 
> wrote:
> 
> Hi
> 
> I can't say I know a lot about bsdauth so maybe this is a stupid
> question, but could this work as a login_* authentication method instead
> of doas doing it?

i had a look at that.

the biggest technical problem is that the bsd_auth framework filters all 
environment variables from the caller before calling the actual auth handler. 
if i wrote a login_sshagent thing for bsdauth, i cant get at the SSH_AUTH_SOCK 
env var to be able to talk to the agent. doas (and other programs) would have 
to be modified to explicitly pass the sockets location as an argument somehow. 
threading that needle doesnt look like much fun.

it was pointed out to me that this isnt that useful anywhere except for doas 
and sudo, so generalising it has limited benefit. most bsd_auth uses are to 
authenticate remote users, you dont have a local agent socket to connect to in 
that situation.

cheers,
dlg

> 
> 
> 
> On Sun, Jul 26, 2015 at 06:43:57PM +1000, David Gwynne wrote:
>> this is rough, but enough to start a discussion.
>> 
>> this lets doas authenticate a user by talking to their ssh agent
>> by specifying 'ssh-agent' on a permit line in the config. if agent
>> auth fails, doas falls back to bsd auth (ie, password auth).
>> 
>> to minimise the amount of code needed in doas, most of the heavy
>> lifting is handed off to two external programs.
>> 
>> the first is a program that fetches a users keys. it has to be
>> provided by the system administrator.
>> 
>> doas does not look at ~/.ssh/authorized_keys because that would
>> allow someone on an unattended shell to modify the current users
>> keys file to gain whatever privs theyve been granted by doas.
>> instead, it followes the semantics of sshds AuthorizedKeysCommand
>> handler.
>> 
>> at work i have an AuthorizedKeysCommand thing that fetches keys
>> from active directory (ie, an ldap) so users can do key based auth
>> on all our machines instead of just the ones we nfs export their
>> homedirs to. doas can use the same backend to fetch the set of keys
>> the system trusts to auth that user. alternatively, a machine could
>> have a set of trusted keys for its users that is read from /etc or
>> such.
>> 
>> the second program is /usr/libexec/doas.sshagent. my implementation
>> reuses a lot of ssh code and links to libssh.a, so ive currently
>> got it in src/usr.bin/ssh/doas.sshagent.
>> 
>> it basically reads authorized_keys from stdin and attempts to use
>> them against the users ssh agent.
>> 
>> if one of the provided keys works, it exits with code 0. if auth
>> fails, it exits with code 8. every other code is considered an
>> error.
>> 
>> the code in doas basically creates a pipe to join the stdout of the
>> authorized_keys command to the stdin of doas.sshagent, and then
>> waits for the latter to exit with a useful code.
>> 
>> this way avoids having to add a bunch of buffering, string parsing,
>> and crypto to doas itself. the doas.sshagent code contains that on
>> its behalf.
>> 
>> anyway, the code is rough, i only just got it all hanging together.
>> 
>> thoughts?
>> 
>> Index: doas/Makefile
>> ===================================================================
>> RCS file: /cvs/src/usr.bin/doas/Makefile,v
>> retrieving revision 1.1
>> diff -u -p -r1.1 Makefile
>> --- doas/Makefile    16 Jul 2015 20:44:21 -0000      1.1
>> +++ doas/Makefile    26 Jul 2015 04:39:17 -0000
>> @@ -8,7 +8,6 @@ MAN= doas.1 doas.conf.5
>> BINOWN= root
>> BINMODE=4555
>> 
>> -CFLAGS+= -I${.CURDIR}
>> COPTS+=      -Wall
>> 
>> .include <bsd.prog.mk>
>> Index: doas/doas.c
>> ===================================================================
>> RCS file: /cvs/src/usr.bin/doas/doas.c,v
>> retrieving revision 1.21
>> diff -u -p -r1.21 doas.c
>> --- doas/doas.c      24 Jul 2015 06:36:42 -0000      1.21
>> +++ doas/doas.c      26 Jul 2015 04:39:17 -0000
>> @@ -17,6 +17,7 @@
>> 
>> #include <sys/types.h>
>> #include <sys/stat.h>
>> +#include <sys/wait.h>
>> 
>> #include <limits.h>
>> #include <login_cap.h>
>> @@ -26,6 +27,8 @@
>> #include <stdlib.h>
>> #include <err.h>
>> #include <unistd.h>
>> +#include <fcntl.h>
>> +#include <signal.h>
>> #include <pwd.h>
>> #include <grp.h>
>> #include <syslog.h>
>> @@ -33,6 +36,8 @@
>> 
>> #include "doas.h"
>> 
>> +static int ssh_agent(const char *, uid_t, gid_t);
>> +
>> static void __dead
>> usage(void)
>> {
>> @@ -291,7 +296,7 @@ main(int argc, char **argv, char **envp)
>>      struct rule *rule;
>>      uid_t uid;
>>      uid_t target = 0;
>> -    gid_t groups[NGROUPS_MAX + 1];
>> +    gid_t groups[NGROUPS_MAX + 1], gid;
>>      int ngroups;
>>      int i, ch;
>>      int sflag = 0;
>> @@ -331,7 +336,7 @@ main(int argc, char **argv, char **envp)
>>      ngroups = getgroups(NGROUPS_MAX, groups);
>>      if (ngroups == -1)
>>              err(1, "can't get groups");
>> -    groups[ngroups++] = getgid();
>> +    groups[ngroups++] = gid = getgid();
>> 
>>      if (sflag) {
>>              sh = getenv("SHELL");
>> @@ -360,13 +365,23 @@ main(int argc, char **argv, char **envp)
>>              fail();
>>      }
>> 
>> -    if (!(rule->options & NOPASS)) {
>> +    switch (rule->options & AUTHMASK) {
>> +    case NOPASS:
>> +            break;
>> +    case SSHAGENT:
>> +            if (ssh_agent(myname, uid, gid) == 0)
>> +                    break;
>> +
>> +            /* FALLTHROUGH */
>> +    case PASSAUTH:
>>              if (!auth_userokay(myname, NULL, NULL, NULL)) {
>>                      syslog(LOG_AUTHPRIV | LOG_NOTICE,
>>                          "failed password for %s", myname);
>>                      fail();
>>              }
>> +            break;
>>      }
>> +
>>      envp = copyenv((const char **)envp, rule);
>> 
>>      pw = getpwuid(target);
>> @@ -385,4 +400,136 @@ main(int argc, char **argv, char **envp)
>>      if (errno == ENOENT)
>>              errx(1, "%s: command not found", cmd);
>>      err(1, "%s", cmd);
>> +}
>> +
>> +char *keycmd = "/etc/doas/sshkeys";
>> +char *keyuser = "_doas";
>> +char *agentcmd = "/usr/libexec/doas.sshagent";
>> +
>> +static int
>> +ssh_agent(const char *myname, uid_t uid, gid_t gid)
>> +{
>> +    extern char *__progname;
>> +    struct passwd *pw;
>> +    int p[2], devnull;
>> +    const char *sock;
>> +    pid_t keys, agent;
>> +    int status;
>> +    char *argv [] = { agentcmd, NULL };
>> +    char *envv[] = { NULL, "SHELL=/bin/sh", "PATH=/bin:/usr/bin", NULL };
>> +
>> +    sock = getenv("SSH_AUTH_SOCK");
>> +    if (sock == NULL)
>> +            return (1);
>> +
>> +    pw = getpwnam(keyuser);
>> +    if (pw == NULL)
>> +            err(1, "key user \"%s\" not found", keyuser);
>> +
>> +    if (pipe(p) == -1)
>> +            err(1, "key pipe");
>> +
>> +    devnull = open("/dev/null", O_RDWR);
>> +    if (devnull == -1)
>> +            err(1, "open %s", "/dev/null");
>> +
>> +    keys = fork();
>> +    switch (keys) {
>> +    case -1:
>> +            err(1, "ssh keys fork");
>> +            /* NOTREACHED */
>> +
>> +    case 0: /* child */
>> +            close(p[0]);
>> +
>> +            if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1)
>> +                    err(1, "keys %s setresgid", keyuser);
>> +
>> +            if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
>> +                    err(1, "keys %s setresuid", keyuser);
>> +
>> +            if (dup2(devnull, STDIN_FILENO) == -1)
>> +                    err(1, "dup2 stdin");
>> +            if (dup2(p[1], STDOUT_FILENO) == -1)
>> +                    err(1, "dup2 stdout");
>> +            if (dup2(devnull, STDERR_FILENO) == -1)
>> +                    err(1, "dup2 stderr");
>> +            closefrom(STDERR_FILENO + 1);
>> +
>> +            execl(keycmd, keycmd, myname, __progname, NULL);
>> +            /* stderr is closed, so err() isnt much use */
>> +            exit(127);
>> +            /* NOTREACHED */
>> +
>> +    default: /* parent */
>> +            close(p[1]);
>> +            break;
>> +    }
>> +
>> +    agent = fork();
>> +    switch (agent) {
>> +    case -1:
>> +            /* don't leave zombie children */
>> +            kill(keys, SIGTERM);
>> +            while (waitpid(keys, NULL, 0) == -1 && errno == EINTR)
>> +                    ;
>> +
>> +            err(1, "ssh agent fork");
>> +            /* NOTREACHED */
>> +
>> +    case 0: /* child */
>> +            if (setresgid(gid, gid, gid) == -1)
>> +                    err(1, "agent setresgid");
>> +
>> +            if (setresuid(uid, uid, uid) == -1)
>> +                    err(1, "agent setresuid");
>> +
>> +            if (dup2(p[0], STDIN_FILENO) == -1)
>> +                    err(1, "agent dup2 stdin");
>> +            if (dup2(devnull, STDOUT_FILENO) == -1)
>> +                    err(1, "agent dup2 stdout");
>> +            if (dup2(devnull, STDERR_FILENO) == -1)
>> +                    err(1, "agent dup2 stderr");
>> +            closefrom(STDERR_FILENO + 1);
>> +
>> +            /* no stderr from now on */
>> +            if (asprintf(&envv[0], "SSH_AUTH_SOCK=%s", sock) == -1)
>> +                    exit(127);
>> +
>> +            execvpe(agentcmd, argv, envv);
>> +            exit(127);
>> +            break;
>> +
>> +    default: /* parent */
>> +            close(p[0]);
>> +            break;
>> +    }
>> +
>> +    close(devnull);
>> +
>> +    /* dont really care what happens to the keys process */
>> +    while (waitpid(keys, NULL, 0) == -1 && errno == EINTR)
>> +            ;
>> +
>> +    while (waitpid(agent, &status, 0) == -1) {
>> +            if (errno != EINTR)
>> +                    err(1, "agent wait");
>> +    }
>> +
>> +    if (WIFSIGNALED(status))
>> +            errx(1, "agent exited on signal %d", WTERMSIG(status));
>> +
>> +    switch (WEXITSTATUS(status)) {
>> +    case 0:
>> +            /* auth worked */
>> +            break;
>> +    case 8:
>> +            /* auth failed */
>> +            return (1);
>> +    default:
>> +            errx(1, "agent returned status %d", WEXITSTATUS(status));
>> +            /* NOTREACHED */
>> +    }
>> +
>> +    return (0);
>> }
>> Index: doas/doas.h
>> ===================================================================
>> RCS file: /cvs/src/usr.bin/doas/doas.h,v
>> retrieving revision 1.4
>> diff -u -p -r1.4 doas.h
>> --- doas/doas.h      24 Jul 2015 06:36:42 -0000      1.4
>> +++ doas/doas.h      26 Jul 2015 04:39:17 -0000
>> @@ -19,5 +19,9 @@ size_t arraylen(const char **);
>> #define PERMIT       1
>> #define DENY 2
>> 
>> +#define AUTHMASK    0x3
>> +#define PASSAUTH    0x0
>> #define NOPASS               0x1
>> -#define KEEPENV             0x2
>> +#define SSHAGENT    0x2
>> +
>> +#define KEEPENV             0x4
>> Index: doas/parse.y
>> ===================================================================
>> RCS file: /cvs/src/usr.bin/doas/parse.y,v
>> retrieving revision 1.10
>> diff -u -p -r1.10 parse.y
>> --- doas/parse.y     24 Jul 2015 06:36:42 -0000      1.10
>> +++ doas/parse.y     26 Jul 2015 04:39:17 -0000
>> @@ -56,7 +56,7 @@ int yyparse(void);
>> %}
>> 
>> %token TPERMIT TDENY TAS TCMD TARGS
>> -%token TNOPASS TKEEPENV
>> +%token TNOPASS TSSHAGENT TKEEPENV
>> %token TSTRING
>> 
>> %%
>> @@ -92,6 +92,16 @@ rule:             action ident target cmd {
>>              } ;
>> 
>> action:              TPERMIT options {
>> +                    switch ($2.options & AUTHMASK) {
>> +                    case PASSAUTH:
>> +                    case NOPASS:
>> +                    case SSHAGENT:
>> +                            break;
>> +                    default:
>> +                            yyerror("invalid authentication options");
>> +                            YYERROR;
>> +                    }
>> +
>>                      $$.action = PERMIT;
>>                      $$.options = $2.options;
>>                      $$.envlist = $2.envlist;
>> @@ -113,6 +123,8 @@ options: /* none */
>>              } ;
>> option:              TNOPASS {
>>                      $$.options = NOPASS;
>> +            } | TSSHAGENT {
>> +                    $$.options = SSHAGENT;
>>              } | TKEEPENV {
>>                      $$.options = KEEPENV;
>>              } | TKEEPENV '{' envlist '}' {
>> @@ -192,6 +204,7 @@ struct keyword {
>>      { "cmd", TCMD },
>>      { "args", TARGS },
>>      { "nopass", TNOPASS },
>> +    { "ssh-agent", TSSHAGENT },
>>      { "keepenv", TKEEPENV },
>> };
>> 
>> --- /dev/null        Sun Jul 26 14:41:12 2015
>> +++ ssh/doas.sshagent/Makefile       Sun Jul 26 14:28:42 2015
>> @@ -0,0 +1,13 @@
>> +# $OpenBSD$
>> +
>> +.PATH: ${.CURDIR}/..
>> +
>> +PROG=       doas.sshagent
>> +SRCS=       doas.sshagent.c
>> +MAN=
>> +LDADD+=     -lcrypto
>> +DPADD+=     ${LIBCRYPTO}
>> +
>> +BINDIR=     /usr/libexec
>> +
>> +.include <bsd.prog.mk>
>> --- /dev/null        Sun Jul 26 14:41:20 2015
>> +++ ssh/doas.sshagent/doas.sshagent.c        Sun Jul 26 14:16:33 2015
>> @@ -0,0 +1,136 @@
>> +/*  $OpenBSD$ */
>> +
>> +/*
>> + * Copyright (c) 2015 David Gwynne <[email protected]>
>> + *
>> + * Permission to use, copy, modify, and distribute this software for any
>> + * purpose with or without fee is hereby granted, provided that the above
>> + * copyright notice and this permission notice appear in all copies.
>> + *
>> + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
>> + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
>> + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
>> + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
>> + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
>> + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
>> + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
>> + */
>> +
>> +#include <stdio.h>
>> +#include <err.h>
>> +
>> +#include "sshkey.h"
>> +#include "authfd.h"
>> +
>> +#define EXIT_OK             0
>> +#define EXIT_ERR    1
>> +#define EXIT_FAIL   8
>> +
>> +struct sshkey *     parse_line(char *);
>> +int         auth_key(int, struct sshkey *);
>> +
>> +#define debug2 warnx
>> +
>> +struct sshkey *
>> +parse_line(char *line)
>> +{
>> +    char *cp, *key_options;
>> +    struct sshkey *key;
>> +
>> +    /* Skip leading whitespace, empty and comment lines. */
>> +    for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
>> +            ;
>> +    if (!*cp || *cp == '\n' || *cp == '#')
>> +            return (NULL);
>> +
>> +    key = sshkey_new(KEY_UNSPEC);
>> +    if (key == NULL)
>> +            err(EXIT_ERR, "sshkey_new");
>> +
>> +    if (sshkey_read(key, &cp) != 0) {
>> +            /* no key?  check if there are options for this key */
>> +            int quoted = 0;
>> +            debug2("user_key_allowed: check options: '%s'", cp);
>> +            for (key_options = cp;
>> +                *cp && (quoted || (*cp != ' ' && *cp != '\t'));
>> +                cp++) {
>> +                    if (*cp == '\\' && cp[1] == '"')
>> +                            cp++;   /* Skip both */
>> +                    else if (*cp == '"')
>> +                            quoted = !quoted;
>> +            }
>> +            /* Skip remaining whitespace. */
>> +            for (; *cp == ' ' || *cp == '\t'; cp++)
>> +                    ;
>> +            if (sshkey_read(key, &cp) != 0) {
>> +                    debug2("user_key_allowed: advance: '%s'", cp);
>> +                    /* still no key?  advance to next line */
>> +                    goto fail;
>> +            }
>> +    }
>> +
>> +    return (key);
>> +
>> +fail:
>> +    sshkey_free(key);
>> +    return (NULL);
>> +}
>> +
>> +int
>> +auth_key(int agent, struct sshkey *key)
>> +{
>> +    u_char data[256];
>> +    u_char *sig;
>> +    size_t siglen;
>> +    int rv;
>> +
>> +    arc4random_buf(data, sizeof(data));
>> +
>> +    rv = ssh_agent_sign(agent, key, &sig, &siglen, data, sizeof(data), 0);
>> +    if (rv != 0)
>> +            return (rv);
>> +
>> +    rv = sshkey_verify(key, sig, siglen, data, sizeof(data), 0);
>> +
>> +    return (rv);
>> +}
>> +
>> +int
>> +main(int argc, char *argv[])
>> +{
>> +    char *line = NULL;
>> +    size_t linesize = 0;
>> +    ssize_t linelen;
>> +    struct sshkey *key, **keys = NULL;
>> +    u_int i, nkeys = 0;
>> +    int agent;
>> +
>> +    while ((linelen = getline(&line, &linesize, stdin)) != -1) {
>> +            key = parse_line(line);
>> +            if (key == NULL)
>> +                    continue;
>> +
>> +            i = nkeys++;
>> +            keys = reallocarray(keys, nkeys, sizeof(*key));
>> +            if (keys == NULL)
>> +                    err(1, "reallocarray");
>> +
>> +            keys[i] = key;
>> +    }
>> +
>> +    if (ferror(stdin))
>> +            err(EXIT_ERR, "getline");
>> +
>> +    if (nkeys == 0)
>> +            return (EXIT_FAIL);
>> +
>> +    if (ssh_get_authentication_socket(&agent) != 0)
>> +            errx(EXIT_ERR, "ssh_get_authentication_socket");
>> +
>> +    for (i = 0; i < nkeys; i++) {
>> +            if (auth_key(agent, keys[i]) == 0)
>> +                    return (EXIT_OK);
>> +    }
>> +
>> +    return (EXIT_FAIL);
>> +}
>> 


Reply via email to