Configuring Conditional SSH Connections

Road Sign Years after years, OpenSSH became the default SSH daemon on most of UNIX environments as well as other systems. It offers a lot of features which make it terribly customizable and powerful.

For a while, the ‘Match” keyword appeared in the list of directives. It allows conditional blocks of configuration directives. If the criteria(s) defined are matched, new directives may overwrite the default ones. This is extremely useful to build complex configurations.

Examples: Users connecting from an internal subnet can use their password but must use their RSA key to authenticate when connecting from unknown hosts. Port forwarding can be restricted to users of a specific group.

The current version of OpenSSH supports four criteria with the “Match” directive:

  • User – To match one or more local users
  • Group – To match one or more local groups
  • Host – To match a FQDN or domain name
  • Address – To match a single address or subnet.

Each condition may contain a single or comma-separated values and support patterns:

  • Host *.rootshell.be
  • User john, leo, !chris
  • Address 192.168.1.16/28

When a condition is met, all the directives below will be applied until the next “Match” section or the end-of-file. This is why, they must be defined at the end of your sshd_config file. Let’s have a look at the following example:

   # Allow administrators to use the agent & X11 forwarding
   Match Group admin
      AllowAgentForwarding yes
      X11Forwarding yes

   # Regular users cannot forward TCP sessions and cannot use a password
   Match Group users
      AllowTcpForwarding no
      PasswordAuthentication no
      Banner banner.users

   # Users from the DMZ are chroot'd
   Match Address 192.168.0.0/24
      ChrootDirectory /var/sandbox

This is very powerful! But when you start to play with such nice features, you quickly become frustrated by the limited criteria, only four in the current version. This afternoon, I discussed with my friend Chris John Riley about the “Match” feature. He wished to be able to use different authentication types depending on the port used to connect to a host. It looks indeed a cool feature. Let’s dive into the OpenSSH source code.

First, it’s good to know that OpenSSH allow multiple instances of the directives “Port” and “ListenAddress”. In my sshd_config file, I use:

   Port 22
   Port 443

Then, I wrote a small patch which implements a new “Match” criteria called “Localport“. In the source code, a function already exists to return the local port used:

   /* Returns remote/local port number for the current connection. */
   static int
   get_port(int local)
   {
        /*
         * If the connection is not a socket, return 65535.  This is
         * intentionally chosen to be an unprivileged port number.
         */
        if (!packet_connection_is_on_socket())
                return 65535;

        /* Get socket and return the port number. */
        return get_sock_port(packet_get_connection_in(), local);
   }

It’s easy to use it and compare the returned value with the port(s) defined in the “Match” condition. Now, I can use the following conditions:

   Match LocalPort 22
      PasswordAuthentication yes
      RSAAuthentication no

   Match LocalPort 443
      PasswordAuthentication no
      KerberosAuthentication yes
      AllowTcpForwarding no

Etc… Interesting isn’t? And, by using different ports, it is possible to increase the server security. It is also possible to play with QoS. Port 22 can be assigned to regular users and port 443 (with reserved bandwidth) to administrators. This way, you will be able to reach your server even in case of heavy network load.

Remark: not all directives can be used in conditional blocks! Check out the sshd_config man page for the list of supported directives.

My OpenSSH patch is available here. It is based on the source code of OpenSSH 5.5p1. As usual, there is warranty at all. Use it at your own risks.

Post Navigation