upstream commit

add AuthorizedPrincipalsCommand that allows getting
 authorized_principals from a subprocess rather than a file, which is quite
 useful in deployments with large userbases

feedback and ok markus@

Upstream-ID: aa1bdac7b16fc6d2fa3524ef08f04c7258d247f6
This commit is contained in:
djm@openbsd.org 2015-05-21 06:43:30 +00:00 committed by Damien Miller
parent 24232a3e5a
commit bcc50d8161
5 changed files with 214 additions and 30 deletions

View File

@ -1,4 +1,4 @@
/* $OpenBSD: auth2-pubkey.c,v 1.50 2015/05/21 06:38:35 djm Exp $ */
/* $OpenBSD: auth2-pubkey.c,v 1.51 2015/05/21 06:43:30 djm Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
*
@ -554,19 +554,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++)
@ -594,23 +588,127 @@ 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;
}
}
}
fclose(f);
restore_uid();
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 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 *tmp, *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;
}
/* Turn the command into an argument vector */
if (split_argv(options.authorized_principals_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;
}
for (i = 1; i < ac; i++) {
tmp = percent_expand(av[i],
"u", user_pw->pw_name,
"h", user_pw->pw_dir,
(char *)NULL);
if (tmp == NULL)
fatal("%s: percent_expand failed", __func__);
free(av[i]);
av[i] = tmp;
}
/* Prepare a printable command for logs, etc. */
command = assemble_argv(ac, av);
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) != 0)
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.
@ -733,7 +831,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;
@ -755,14 +853,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)

View File

@ -1,4 +1,4 @@
/* $OpenBSD: servconf.c,v 1.269 2015/05/04 06:10:48 djm Exp $ */
/* $OpenBSD: servconf.c,v 1.270 2015/05/21 06:43:30 djm Exp $ */
/*
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
* All rights reserved
@ -160,6 +160,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;
@ -400,6 +402,7 @@ typedef enum {
sUsePrivilegeSeparation, sAllowAgentForwarding,
sHostCertificate,
sRevokedKeys, sTrustedUserCAKeys, sAuthorizedPrincipalsFile,
sAuthorizedPrincipalsCommand, sAuthorizedPrincipalsCommandUser,
sKexAlgorithms, sIPQoS, sVersionAddendum,
sAuthorizedKeysCommand, sAuthorizedKeysCommandUser,
sAuthenticationMethods, sHostKeyAgent, sPermitUserRC,
@ -532,6 +535,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 },
@ -1734,6 +1739,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 (options->num_auth_methods == 0) {
while ((arg = strdelim(&cp)) && *arg != '\0') {
@ -2229,6 +2262,8 @@ dump_config(ServerOptions *o)
? "none" : 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);

View File

@ -1,4 +1,4 @@
/* $OpenBSD: servconf.h,v 1.117 2015/04/29 03:48:56 dtucker Exp $ */
/* $OpenBSD: servconf.h,v 1.118 2015/05/21 06:43:31 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
@ -178,9 +178,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;
@ -216,9 +218,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); \

7
sshd.c
View File

@ -1,4 +1,4 @@
/* $OpenBSD: sshd.c,v 1.448 2015/04/27 00:21:21 djm Exp $ */
/* $OpenBSD: sshd.c,v 1.449 2015/05/21 06:43:31 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -1698,6 +1698,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.

View File

@ -33,7 +33,7 @@
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
.\"
.\" $OpenBSD: sshd_config.5,v 1.201 2015/05/21 06:38:35 djm Exp $
.\" $OpenBSD: sshd_config.5,v 1.202 2015/05/21 06:43:31 djm Exp $
.Dd $Mdocdate: May 21 2015 $
.Dt SSHD_CONFIG 5
.Os
@ -287,6 +287,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.