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
diff --git a/auth2-pubkey.c b/auth2-pubkey.c
index 553144c..c4e80b0 100644
--- a/auth2-pubkey.c
+++ b/auth2-pubkey.c
@@ -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 @@
}
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 @@
}
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 @@
{
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 @@
* 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)
diff --git a/servconf.c b/servconf.c
index 9257a17..5acaf61 100644
--- a/servconf.c
+++ b/servconf.c
@@ -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 @@
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 @@
sUsePrivilegeSeparation, sAllowAgentForwarding,
sHostCertificate,
sRevokedKeys, sTrustedUserCAKeys, sAuthorizedPrincipalsFile,
+ sAuthorizedPrincipalsCommand, sAuthorizedPrincipalsCommandUser,
sKexAlgorithms, sIPQoS, sVersionAddendum,
sAuthorizedKeysCommand, sAuthorizedKeysCommandUser,
sAuthenticationMethods, sHostKeyAgent, sPermitUserRC,
@@ -532,6 +535,8 @@
{ "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 @@
*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 @@
? "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);
diff --git a/servconf.h b/servconf.h
index 38520f4..dc2a5f6 100644
--- a/servconf.h
+++ b/servconf.h
@@ -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 @@
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 @@
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/sshd.c b/sshd.c
index 78729dd..eb49b5b 100644
--- a/sshd.c
+++ b/sshd.c
@@ -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 @@
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 e40eced..884e767 100644
--- a/sshd_config.5
+++ b/sshd_config.5
@@ -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 @@
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.