Fair enough, thanks.

On Sun, Jul 26, 2015 at 07:19:06PM +1000, David Gwynne wrote:
> 
> > 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