Philipp Heckel
2017-May-07 19:17 UTC
[PATCH] Add "permitlisten" support for -R style forwards
Hi there, this patch adds support for per-key restriction of -R style forwards via a "permitlisten"-option in the authorized_keys file -- similar to the "permitopen"-option for -L style forwards. This is desirable if you want to have restricted accounts/keys that can only be used for -R style forwards on certain ports. With this example authorized_keys file: restrict,permitlisten="localhost:8080" ssh-rsa AAAAB3Nza... This is allowed: $ ssh -R 8080:localhost:80 root at localhost -N While this is not allowed (note port 8081): $ ssh -R 8081:localhost:80 root at localhost -N Error: remote port forwarding failed for listen port 8081 This is a preliminary patch (no support for a servconf option "PermitListen" yet), because I wanted to get early feedback before continuing. Do you think this approach is correct? Would this be a desirable feature? Is "permitlisten" the correct name for this? Or would "permitropen", "permitremoteopen" be better suited? My WIP branch/pull can also be found here: https://github.com/openssh/openssh-portable/pull/65 Best, Philipp Heckel --- auth-options.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++ channels.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ channels.h | 3 +++ serverloop.c | 5 +++-- 4 files changed, 128 insertions(+), 2 deletions(-) diff --git a/auth-options.c b/auth-options.c index 57b49f7..61034be 100644 --- a/auth-options.c +++ b/auth-options.c @@ -82,6 +82,7 @@ auth_clear_options(void) authorized_principals = NULL; forced_tun_device = -1; channel_clear_permitted_opens(); + channel_clear_permitted_listens(); } /* @@ -383,6 +384,61 @@ auth_parse_options(struct passwd *pw, char *opts, char *file, u_long linenum) free(patterns); goto next_option; } + cp = "permitlisten=\""; + if (strncasecmp(opts, cp, strlen(cp)) == 0) { + char *host, *p; + int port; + char *patterns = xmalloc(strlen(opts) + 1); + + opts += strlen(cp); + i = 0; + while (*opts) { + if (*opts == '"') + break; + if (*opts == '\\' && opts[1] == '"') { + opts += 2; + patterns[i++] = '"'; + continue; + } + patterns[i++] = *opts++; + } + if (!*opts) { + debug("%.100s, line %lu: missing end quote", + file, linenum); + auth_debug_add("%.100s, line %lu: missing " + "end quote", file, linenum); + free(patterns); + goto bad_option; + } + patterns[i] = '\0'; + opts++; + p = patterns; + /* XXX - add streamlocal support */ + host = hpdelim(&p); + if (host == NULL || strlen(host) >= NI_MAXHOST) { + debug("%.100s, line %lu: Bad permitlisten " + "specification <%.100s>", file, linenum, + patterns); + auth_debug_add("%.100s, line %lu: " + "Bad permitlisten specification", file, + linenum); + free(patterns); + goto bad_option; + } + host = cleanhostname(host); + if (p == NULL || (port = permitopen_port(p)) < 0) { + debug("%.100s, line %lu: Bad permitlisten port " + "<%.100s>", file, linenum, p ? p : ""); + auth_debug_add("%.100s, line %lu: " + "Bad permitopen port", file, linenum); + free(patterns); + goto bad_option; + } + if ((options.allow_tcp_forwarding & FORWARD_REMOTE) != 0) + channel_add_permitted_listens(host, port); + free(patterns); + goto next_option; + } cp = "tunnel=\""; if (strncasecmp(opts, cp, strlen(cp)) == 0) { char *tun = NULL; diff --git a/channels.c b/channels.c index 4092a67..551c2f0 100644 --- a/channels.c +++ b/channels.c @@ -129,12 +129,18 @@ static ForwardPermission *permitted_opens = NULL; /* List of all permitted host/port pairs to connect by the admin. */ static ForwardPermission *permitted_adm_opens = NULL; +/* List of all permitted remote host/port pairs to connect by the user. */ +static ForwardPermission *permitted_listens = NULL; + /* Number of permitted host/port pairs in the array permitted by the user. */ static int num_permitted_opens = 0; /* Number of permitted host/port pair in the array permitted by the admin. */ static int num_adm_permitted_opens = 0; +/* Number of permitted remote host/port pairs. */ +static int num_permitted_listens = 0; + /* special-case port number meaning allow any port */ #define FWD_PERMIT_ANY_PORT 0 @@ -148,6 +154,10 @@ static int num_adm_permitted_opens = 0; */ static int all_opens_permitted = 0; +/** + * If this is true, all remote opens are permitted. + */ +static int all_listens_permitted = 0; /* -- X11 forwarding */ @@ -3503,6 +3513,23 @@ channel_add_permitted_opens(char *host, int port) all_opens_permitted = 0; } +void +channel_add_permitted_listens(char *host, int port) +{ + debug("allow remote port forwarding to host %s port %d", host, port); + + permitted_listens = xreallocarray(permitted_listens, + num_permitted_listens + 1, sizeof(*permitted_listens)); + permitted_listens[num_permitted_listens].host_to_connect = xstrdup(host); + permitted_listens[num_permitted_listens].port_to_connect = port; + permitted_listens[num_permitted_listens].listen_host = NULL; + permitted_listens[num_permitted_listens].listen_path = NULL; + permitted_listens[num_permitted_listens].listen_port = 0; + num_permitted_listens++; + + all_listens_permitted = 0; +} + /* * Update the listen port for a dynamic remote forward, after * the actual 'newport' has been allocated. If 'newport' < 0 is @@ -3592,6 +3619,21 @@ channel_clear_adm_permitted_opens(void) } void +channel_clear_permitted_listens(void) +{ + int i; + + for (i = 0; i < num_permitted_listens; i++) { + free(permitted_listens[i].host_to_connect); + free(permitted_listens[i].listen_host); + free(permitted_listens[i].listen_path); + } + free(permitted_listens); + permitted_listens = NULL; + num_permitted_listens = 0; +} + +void channel_print_adm_permitted_opens(void) { int i; @@ -3885,6 +3927,30 @@ channel_connect_to_path(const char *path, char *ctype, char *rname) return connect_to(path, PORT_STREAMLOCAL, ctype, rname); } +/* Check if connecting to that port is permitted and connect. */ +int +channel_connect_check_permitted_listens(const char *host, u_short port) +{ + int i, permit = 1; + + permit = all_listens_permitted; + if (!permit) { + for (i = 0; i < num_permitted_listens; i++) + if (open_match(&permitted_listens[i], host, port)) { + permit = 1; + break; + } + } + + if (!permit) { + logit("Received request for remote forward to host %.100s port %d, " + "but the request was denied.", host, port); + return -1; + } + + return 0; +} + void channel_send_window_changes(void) { diff --git a/channels.h b/channels.h index 4e9b77d..7d55055 100644 --- a/channels.h +++ b/channels.h @@ -267,15 +267,18 @@ struct ForwardOptions; void channel_set_af(int af); void channel_permit_all_opens(void); void channel_add_permitted_opens(char *, int); +void channel_add_permitted_listens(char *, int); int channel_add_adm_permitted_opens(char *, int); void channel_disable_adm_local_opens(void); void channel_update_permitted_opens(int, int); void channel_clear_permitted_opens(void); void channel_clear_adm_permitted_opens(void); +void channel_clear_permitted_listens(void); void channel_print_adm_permitted_opens(void); Channel *channel_connect_to_port(const char *, u_short, char *, char *, int *, const char **); Channel *channel_connect_to_path(const char *, char *, char *); +int channel_connect_check_permitted_listens(const char *host, u_short port); Channel *channel_connect_stdio_fwd(const char*, u_short, int, int); Channel *channel_connect_by_listen_address(const char *, u_short, char *, char *); diff --git a/serverloop.c b/serverloop.c index 2976f55..50d8feb 100644 --- a/serverloop.c +++ b/serverloop.c @@ -729,11 +729,12 @@ server_input_global_request(int type, u_int32_t seq, void *ctxt) fwd.listen_host, fwd.listen_port); /* check permissions */ - if ((options.allow_tcp_forwarding & FORWARD_REMOTE) == 0 || + if (channel_connect_check_permitted_listens(fwd.listen_host, fwd.listen_port) < 0 && + ((options.allow_tcp_forwarding & FORWARD_REMOTE) == 0 || no_port_forwarding_flag || options.disable_forwarding || (!want_reply && fwd.listen_port == 0) || (fwd.listen_port != 0 && - !bind_permitted(fwd.listen_port, pw->pw_uid))) { + !bind_permitted(fwd.listen_port, pw->pw_uid)))) { success = 0; packet_send_debug("Server has disabled port forwarding."); } else { -- 2.7.4