summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordjm@openbsd.org <djm@openbsd.org>2016-07-15 00:24:30 +0000
committerDamien Miller <djm@mindrot.org>2016-07-15 14:20:10 +1000
commited877ef653847d056bb433975d731b7a1132a979 (patch)
tree855230b944a0fc2eebdaa4c037f911e28ff21e17
parent5c02dd126206a26785379e80f2d3848e4470b711 (diff)
upstream commit
Add a ProxyJump ssh_config(5) option and corresponding -J ssh(1) command-line flag to allow simplified indirection through a SSH bastion or "jump host". These options construct a proxy command that connects to the specified jump host(s) (more than one may be specified) and uses port-forwarding to establish a connection to the next destination. This codifies the safest way of indirecting connections through SSH servers and makes it easy to use. ok markus@ Upstream-ID: fa899cb8b26d889da8f142eb9774c1ea36b04397
-rw-r--r--misc.c63
-rw-r--r--misc.h3
-rw-r--r--readconf.c95
-rw-r--r--readconf.h8
-rw-r--r--ssh.124
-rw-r--r--ssh.c77
-rw-r--r--ssh_config.528
7 files changed, 271 insertions, 27 deletions
diff --git a/misc.c b/misc.c
index 9d59ca6b..9421b4d3 100644
--- a/misc.c
+++ b/misc.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.c,v 1.104 2016/04/06 06:42:17 djm Exp $ */
+/* $OpenBSD: misc.c,v 1.105 2016/07/15 00:24:30 djm Exp $ */
/*
* Copyright (c) 2000 Markus Friedl. All rights reserved.
* Copyright (c) 2005,2006 Damien Miller. All rights reserved.
@@ -451,6 +451,67 @@ colon(char *cp)
return NULL;
}
+/*
+ * Parse a [user@]host[:port] string.
+ * Caller must free returned user and host.
+ * Any of the pointer return arguments may be NULL (useful for syntax checking).
+ * If user was not specified then *userp will be set to NULL.
+ * If port was not specified then *portp will be -1.
+ * Returns 0 on success, -1 on failure.
+ */
+int
+parse_user_host_port(const char *s, char **userp, char **hostp, int *portp)
+{
+ char *sdup, *cp, *tmp;
+ char *user = NULL, *host = NULL;
+ int port = -1, ret = -1;
+
+ if (userp != NULL)
+ *userp = NULL;
+ if (hostp != NULL)
+ *hostp = NULL;
+ if (portp != NULL)
+ *portp = -1;
+
+ if ((sdup = tmp = strdup(s)) == NULL)
+ return -1;
+ /* Extract optional username */
+ if ((cp = strchr(tmp, '@')) != NULL) {
+ *cp = '\0';
+ if (*tmp == '\0')
+ goto out;
+ if ((user = strdup(tmp)) == NULL)
+ goto out;
+ tmp = cp + 1;
+ }
+ /* Extract mandatory hostname */
+ if ((cp = hpdelim(&tmp)) == NULL || *cp == '\0')
+ goto out;
+ host = xstrdup(cleanhostname(cp));
+ /* Convert and verify optional port */
+ if (tmp != NULL && *tmp != '\0') {
+ if ((port = a2port(tmp)) <= 0)
+ goto out;
+ }
+ /* Success */
+ if (userp != NULL) {
+ *userp = user;
+ user = NULL;
+ }
+ if (hostp != NULL) {
+ *hostp = host;
+ host = NULL;
+ }
+ if (portp != NULL)
+ *portp = port;
+ ret = 0;
+ out:
+ free(sdup);
+ free(user);
+ free(host);
+ return ret;
+}
+
/* function to assist building execv() arguments */
void
addargs(arglist *args, char *fmt, ...)
diff --git a/misc.h b/misc.h
index 01432ba8..7c76a6a7 100644
--- a/misc.h
+++ b/misc.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: misc.h,v 1.56 2016/04/06 06:42:17 djm Exp $ */
+/* $OpenBSD: misc.h,v 1.57 2016/07/15 00:24:30 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -49,6 +49,7 @@ char *put_host_port(const char *, u_short);
char *hpdelim(char **);
char *cleanhostname(char *);
char *colon(char *);
+int parse_user_host_port(const char *, char **, char **, int *);
long convtime(const char *);
char *tilde_expand_filename(const char *, uid_t);
char *percent_expand(const char *, ...) __attribute__((__sentinel__));
diff --git a/readconf.c b/readconf.c
index 9dcc383d..cb2999d8 100644
--- a/readconf.c
+++ b/readconf.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.c,v 1.256 2016/06/03 04:09:38 dtucker Exp $ */
+/* $OpenBSD: readconf.c,v 1.257 2016/07/15 00:24:30 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -170,7 +170,7 @@ typedef enum {
oCanonicalizeFallbackLocal, oCanonicalizePermittedCNAMEs,
oStreamLocalBindMask, oStreamLocalBindUnlink, oRevokedHostKeys,
oFingerprintHash, oUpdateHostkeys, oHostbasedKeyTypes,
- oPubkeyAcceptedKeyTypes,
+ oPubkeyAcceptedKeyTypes, oProxyJump,
oIgnoredUnknownOption, oDeprecated, oUnsupported
} OpCodes;
@@ -295,6 +295,7 @@ static struct {
{ "hostbasedkeytypes", oHostbasedKeyTypes },
{ "pubkeyacceptedkeytypes", oPubkeyAcceptedKeyTypes },
{ "ignoreunknown", oIgnoreUnknown },
+ { "proxyjump", oProxyJump },
{ NULL, oBadOption }
};
@@ -1121,6 +1122,9 @@ parse_char_array:
case oProxyCommand:
charptr = &options->proxy_command;
+ /* Ignore ProxyCommand if ProxyJump already specified */
+ if (options->jump_host != NULL)
+ charptr = &options->jump_host; /* Skip below */
parse_command:
if (s == NULL)
fatal("%.200s line %d: Missing argument.", filename, linenum);
@@ -1129,6 +1133,18 @@ parse_command:
*charptr = xstrdup(s + len);
return 0;
+ case oProxyJump:
+ if (s == NULL) {
+ fatal("%.200s line %d: Missing argument.",
+ filename, linenum);
+ }
+ len = strspn(s, WHITESPACE "=");
+ if (parse_jump(s + len, options, *activep) == -1) {
+ fatal("%.200s line %d: Invalid ProxyJump \"%s\"",
+ filename, linenum, s + len);
+ }
+ return 0;
+
case oPort:
intptr = &options->port;
parse_int:
@@ -1789,6 +1805,10 @@ initialize_options(Options * options)
options->hostname = NULL;
options->host_key_alias = NULL;
options->proxy_command = NULL;
+ options->jump_user = NULL;
+ options->jump_host = NULL;
+ options->jump_port = -1;
+ options->jump_extra = NULL;
options->user = NULL;
options->escape_char = -1;
options->num_system_hostfiles = 0;
@@ -2261,6 +2281,44 @@ parse_forward(struct Forward *fwd, const char *fwdspec, int dynamicfwd, int remo
return (0);
}
+int
+parse_jump(const char *s, Options *o, int active)
+{
+ char *orig, *sdup, *cp;
+ char *host = NULL, *user = NULL;
+ int ret = -1, port = -1;
+
+ active &= o->proxy_command == NULL && o->jump_host == NULL;
+
+ orig = sdup = xstrdup(s);
+ while ((cp = strsep(&sdup, ",")) && cp != NULL) {
+ if (active) {
+ /* First argument and configuration is active */
+ if (parse_user_host_port(cp, &user, &host, &port) != 0)
+ goto out;
+ } else {
+ /* Subsequent argument or inactive configuration */
+ if (parse_user_host_port(cp, NULL, NULL, NULL) != 0)
+ goto out;
+ }
+ active = 0; /* only check syntax for subsequent hosts */
+ }
+ /* success */
+ free(orig);
+ o->jump_user = user;
+ o->jump_host = host;
+ o->jump_port = port;
+ o->proxy_command = xstrdup("none");
+ user = host = NULL;
+ if ((cp = strchr(s, ',')) != NULL && cp[1] != '\0')
+ o->jump_extra = xstrdup(cp + 1);
+ ret = 0;
+ out:
+ free(user);
+ free(host);
+ return ret;
+}
+
/* XXX the following is a near-vebatim copy from servconf.c; refactor */
static const char *
fmt_multistate_int(int val, const struct multistate *m)
@@ -2412,7 +2470,7 @@ void
dump_client_config(Options *o, const char *host)
{
int i;
- char vbuf[5];
+ char buf[8];
/* This is normally prepared in ssh_kex2 */
if (kex_assemble_names(KEX_DEFAULT_PK_ALG, &o->hostkeyalgorithms) != 0)
@@ -2490,7 +2548,6 @@ dump_client_config(Options *o, const char *host)
dump_cfg_string(oMacs, o->macs ? o->macs : KEX_CLIENT_MAC);
dump_cfg_string(oPKCS11Provider, o->pkcs11_provider);
dump_cfg_string(oPreferredAuthentications, o->preferred_authentications);
- dump_cfg_string(oProxyCommand, o->proxy_command);
dump_cfg_string(oPubkeyAcceptedKeyTypes, o->pubkey_key_types);
dump_cfg_string(oRevokedHostKeys, o->revoked_host_keys);
dump_cfg_string(oXAuthLocation, o->xauth_location);
@@ -2551,8 +2608,8 @@ dump_client_config(Options *o, const char *host)
if (o->escape_char == SSH_ESCAPECHAR_NONE)
printf("escapechar none\n");
else {
- vis(vbuf, o->escape_char, VIS_WHITE, 0);
- printf("escapechar %s\n", vbuf);
+ vis(buf, o->escape_char, VIS_WHITE, 0);
+ printf("escapechar %s\n", buf);
}
/* oIPQoS */
@@ -2566,4 +2623,30 @@ dump_client_config(Options *o, const char *host)
/* oStreamLocalBindMask */
printf("streamlocalbindmask 0%o\n",
o->fwd_opts.streamlocal_bind_mask);
+
+ /* oProxyCommand / oProxyJump */
+ if (o->jump_host == NULL)
+ dump_cfg_string(oProxyCommand, o->proxy_command);
+ else {
+ /* Check for numeric addresses */
+ i = strchr(o->jump_host, ':') != NULL ||
+ strspn(o->jump_host, "1234567890.") == strlen(o->jump_host);
+ snprintf(buf, sizeof(buf), "%d", o->jump_port);
+ printf("proxyjump %s%s%s%s%s%s%s%s%s\n",
+ /* optional user */
+ o->jump_user == NULL ? "" : o->jump_user,
+ o->jump_user == NULL ? "" : "@",
+ /* opening [ if hostname is numeric */
+ i ? "[" : "",
+ /* mandatory hostname */
+ o->jump_host,
+ /* closing ] if hostname is numeric */
+ i ? "]" : "",
+ /* optional port number */
+ o->jump_port <= 0 ? "" : ":",
+ o->jump_port <= 0 ? "" : buf,
+ /* optional additional jump spec */
+ o->jump_extra == NULL ? "" : ",",
+ o->jump_extra == NULL ? "" : o->jump_extra);
+ }
}
diff --git a/readconf.h b/readconf.h
index a8b0b918..cef55f71 100644
--- a/readconf.h
+++ b/readconf.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: readconf.h,v 1.116 2016/06/03 03:14:41 dtucker Exp $ */
+/* $OpenBSD: readconf.h,v 1.117 2016/07/15 00:24:30 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -163,6 +163,11 @@ typedef struct {
char *hostbased_key_types;
char *pubkey_key_types;
+ char *jump_user;
+ char *jump_host;
+ int jump_port;
+ char *jump_extra;
+
char *ignored_unknown; /* Pattern list of unknown tokens to ignore */
} Options;
@@ -198,6 +203,7 @@ int process_config_line(Options *, struct passwd *, const char *,
int read_config_file(const char *, struct passwd *, const char *,
const char *, Options *, int);
int parse_forward(struct Forward *, const char *, int, int);
+int parse_jump(const char *, Options *, int);
int default_ssh_port(void);
int option_clear_or_none(const char *);
void dump_client_config(Options *o, const char *host);
diff --git a/ssh.1 b/ssh.1
index 32949b05..f3492b4d 100644
--- a/ssh.1
+++ b/ssh.1
@@ -33,8 +33,8 @@
.\" (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: ssh.1,v 1.374 2016/06/29 17:14:28 jmc Exp $
-.Dd $Mdocdate: June 29 2016 $
+.\" $OpenBSD: ssh.1,v 1.375 2016/07/15 00:24:30 djm Exp $
+.Dd $Mdocdate: July 15 2016 $
.Dt SSH 1
.Os
.Sh NAME
@@ -52,6 +52,7 @@
.Op Fl F Ar configfile
.Op Fl I Ar pkcs11
.Op Fl i Ar identity_file
+.Oo Fl J Ar user Ns @ Oc Ns Ar host Ns Op : Ns Ar port
.Op Fl L Ar address
.Op Fl l Ar login_name
.Op Fl m Ar mac_spec
@@ -312,6 +313,24 @@ by appending
.Pa -cert.pub
to identity filenames.
.Pp
+.It Fl J Xo
+.Sm off
+.Oo Ar jump_user @ Oc
+.Ar jump_host
+.Ns Op : Ns Ar jump_port
+.Sm on
+.Xc
+Connect to the target host by first making a
+.Nm
+connection to
+.Ar jump_host
+and then establishing a TCP forward to the ultimate destination from
+there.
+Multiple jump hops may be specified separated by comma characters.
+This is a shortcut to specify a
+.Cm ProxyJump
+configuration directive.
+.Pp
.It Fl K
Enables GSSAPI-based authentication and forwarding (delegation) of GSSAPI
credentials to the server.
@@ -523,6 +542,7 @@ For full details of the options listed below, and their possible values, see
.It PreferredAuthentications
.It Protocol
.It ProxyCommand
+.It ProxyJump
.It ProxyUseFdpass
.It PubkeyAcceptedKeyTypes
.It PubkeyAuthentication
diff --git a/ssh.c b/ssh.c
index e7d4fd91..a9b68534 100644
--- a/ssh.c
+++ b/ssh.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ssh.c,v 1.442 2016/06/03 04:09:39 dtucker Exp $ */
+/* $OpenBSD: ssh.c,v 1.443 2016/07/15 00:24:30 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -330,7 +330,7 @@ resolve_addr(const char *name, int port, char *caddr, size_t clen)
* NB. this function must operate with a options having undefined members.
*/
static int
-check_follow_cname(char **namep, const char *cname)
+check_follow_cname(int direct, char **namep, const char *cname)
{
int i;
struct allowed_cname *rule;
@@ -342,9 +342,9 @@ check_follow_cname(char **namep, const char *cname)
return 0;
/*
* Don't attempt to canonicalize names that will be interpreted by
- * a proxy unless the user specifically requests so.
+ * a proxy or jump host unless the user specifically requests so.
*/
- if (!option_clear_or_none(options.proxy_command) &&
+ if (!direct &&
options.canonicalize_hostname != SSH_CANONICALISE_ALWAYS)
return 0;
debug3("%s: check \"%s\" CNAME \"%s\"", __func__, *namep, cname);
@@ -371,7 +371,7 @@ check_follow_cname(char **namep, const char *cname)
static struct addrinfo *
resolve_canonicalize(char **hostp, int port)
{
- int i, ndots;
+ int i, direct, ndots;
char *cp, *fullhost, newname[NI_MAXHOST];
struct addrinfo *addrs;
@@ -382,7 +382,9 @@ resolve_canonicalize(char **hostp, int port)
* Don't attempt to canonicalize names that will be interpreted by
* a proxy unless the user specifically requests so.
*/
- if (!option_clear_or_none(options.proxy_command) &&
+ direct = option_clear_or_none(options.proxy_command) &&
+ options.jump_host == NULL;
+ if (!direct &&
options.canonicalize_hostname != SSH_CANONICALISE_ALWAYS)
return NULL;
@@ -437,7 +439,7 @@ resolve_canonicalize(char **hostp, int port)
/* Remove trailing '.' */
fullhost[strlen(fullhost) - 1] = '\0';
/* Follow CNAME if requested */
- if (!check_follow_cname(&fullhost, newname)) {
+ if (!check_follow_cname(direct, &fullhost, newname)) {
debug("Canonicalized hostname \"%s\" => \"%s\"",
*hostp, fullhost);
}
@@ -510,7 +512,7 @@ int
main(int ac, char **av)
{
struct ssh *ssh = NULL;
- int i, r, opt, exit_status, use_syslog, config_test = 0;
+ int i, r, opt, exit_status, use_syslog, direct, config_test = 0;
char *p, *cp, *line, *argv0, buf[PATH_MAX], *host_arg, *logfile;
char thishost[NI_MAXHOST], shorthost[NI_MAXHOST], portstr[NI_MAXSERV];
char cname[NI_MAXHOST], uidstr[32], *conn_hash_hex;
@@ -603,7 +605,7 @@ main(int ac, char **av)
again:
while ((opt = getopt(ac, av, "1246ab:c:e:fgi:kl:m:no:p:qstvx"
- "ACD:E:F:GI:KL:MNO:PQ:R:S:TVw:W:XYy")) != -1) {
+ "ACD:E:F:GI:J:KL:MNO:PQ:R:S:TVw:W:XYy")) != -1) {
switch (opt) {
case '1':
options.protocol = SSH_PROTO_1;
@@ -728,6 +730,15 @@ main(int ac, char **av)
fprintf(stderr, "no support for PKCS#11.\n");
#endif
break;
+ case 'J':
+ if (options.jump_host != NULL)
+ fatal("Only a single -J option permitted");
+ if (options.proxy_command != NULL)
+ fatal("Cannot specify -J with ProxyCommand");
+ if (parse_jump(optarg, &options, 1) == -1)
+ fatal("Invalid -J argument");
+ options.proxy_command = xstrdup("none");
+ break;
case 't':
if (options.request_tty == REQUEST_TTY_YES)
options.request_tty = REQUEST_TTY_FORCE;
@@ -739,8 +750,10 @@ main(int ac, char **av)
debug_flag = 1;
options.log_level = SYSLOG_LEVEL_DEBUG1;
} else {
- if (options.log_level < SYSLOG_LEVEL_DEBUG3)
+ if (options.log_level < SYSLOG_LEVEL_DEBUG3) {
+ debug_flag++;
options.log_level++;
+ }
}
break;
case 'V':
@@ -1038,9 +1051,10 @@ main(int ac, char **av)
* has specifically requested canonicalisation for this case via
* CanonicalizeHostname=always
*/
- if (addrs == NULL && options.num_permitted_cnames != 0 &&
- (option_clear_or_none(options.proxy_command) ||
- options.canonicalize_hostname == SSH_CANONICALISE_ALWAYS)) {
+ direct = option_clear_or_none(options.proxy_command) &&
+ options.jump_host == NULL;
+ if (addrs == NULL && options.num_permitted_cnames != 0 && (direct ||
+ options.canonicalize_hostname == SSH_CANONICALISE_ALWAYS)) {
if ((addrs = resolve_host(host, options.port,
option_clear_or_none(options.proxy_command),
cname, sizeof(cname))) == NULL) {
@@ -1048,7 +1062,7 @@ main(int ac, char **av)
if (option_clear_or_none(options.proxy_command))
cleanup_exit(255); /* logged in resolve_host */
} else
- check_follow_cname(&host, cname);
+ check_follow_cname(direct, &host, cname);
}
/*
@@ -1073,6 +1087,41 @@ main(int ac, char **av)
/* Fill configuration defaults. */
fill_default_options(&options);
+ /*
+ * If ProxyJump option specified, then construct a ProxyCommand now.
+ */
+ if (options.jump_host != NULL) {
+ char port_s[8];
+
+ /* Consistency check */
+ if (options.proxy_command != NULL)
+ fatal("inconsistent options: ProxyCommand+ProxyJump");
+ /* Never use FD passing for ProxyJump */
+ options.proxy_use_fdpass = 0;
+ snprintf(port_s, sizeof(port_s), "%d", options.jump_port);
+ xasprintf(&options.proxy_command,
+ "ssh%s%s%s%s%s%s%s%s%s%.*s -W %%h:%%p %s",
+ /* Optional "-l user" argument if jump_user set */
+ options.jump_user == NULL ? "" : " -l ",
+ options.jump_user == NULL ? "" : options.jump_user,
+ /* Optional "-p port" argument if jump_port set */
+ options.jump_port <= 0 ? "" : " -p ",
+ options.jump_port <= 0 ? "" : port_s,
+ /* Optional additional jump hosts ",..." */
+ options.jump_extra == NULL ? "" : " -J ",
+ options.jump_extra == NULL ? "" : options.jump_extra,
+ /* Optional "-F" argumment if -F specified */
+ config == NULL ? "" : " -F ",
+ config == NULL ? "" : config,
+ /* Optional "-v" arguments if -v set */
+ debug_flag ? " -" : "",
+ debug_flag, "vvv",
+ /* Mandatory hostname */
+ options.jump_host);
+ debug("Setting implicit ProxyCommand from ProxyJump: %s",
+ options.proxy_command);
+ }
+
if (options.port == 0)
options.port = default_ssh_port();
channel_set_af(options.address_family);
diff --git a/ssh_config.5 b/ssh_config.5
index 45fe8920..86057702 100644
--- a/ssh_config.5
+++ b/ssh_config.5
@@ -33,8 +33,8 @@
.\" (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: ssh_config.5,v 1.232 2016/05/04 14:29:58 markus Exp $
-.Dd $Mdocdate: May 4 2016 $
+.\" $OpenBSD: ssh_config.5,v 1.233 2016/07/15 00:24:30 djm Exp $
+.Dd $Mdocdate: July 15 2016 $
.Dt SSH_CONFIG 5
.Os
.Sh NAME
@@ -1358,6 +1358,30 @@ For example, the following directive would connect via an HTTP proxy at
.Bd -literal -offset 3n
ProxyCommand /usr/bin/nc -X connect -x 192.0.2.0:8080 %h %p
.Ed
+.It Cm ProxyJump
+Specifies one or more jump proxies as
+.Xo
+.Sm off
+.Oo Ar user @ Oc
+.Ar host
+.Ns Op : Ns Ar port
+.Sm on
+.Xc .
+Multiple proxies may be separated by comma characters.
+Setting this option will cause
+.Xr ssh 1
+to connect to the target host by first making a
+.Xr ssh 1
+connection to the specified
+.Cm ProxyJump
+host and then establishing a
+a TCP forwarding to the ultimate target from there.
+.Pp
+Note that this option will compete with the
+.Cm ProxyCommand
+option - whichever is specified first will prevent later instances of the
+other from taking effect.
+.Pp
.It Cm ProxyUseFdpass
Specifies that
.Cm ProxyCommand