16

I'm currently trying to set up an SSH server so that access to it from outside the network is ONLY allowed using an SSH Key and does not allow access to root or by any other username/password combination.

At the same time, internal users inside the network, still need to be able to connect to the same system, but expect to log in in the more traditional sense with a user name and password.

Users both external & internal will be accessing the system from windows using PuttySSH and the external access will be coming into the system via a port forwarding firewall that will open the source port to the outside world on some arbitrarily chosen high numbered port like 55000 (or what ever the admins decide)

The following diagram attempts to show the traffic flows better.

SSH Setup

I know how to set up the actual login to only use keys, and I know how to deny root, what I don't know is how to separate the two login types.

I had considered running two copies of SSHD listening on different ports on the same IP and having two different configurations for each port.

I also considered setting up a "match" rule, but I'm not sure if I can segregate server wide configurations using those options.

Finally, the external person logging in will always be the same user let's call them "Frank" for the purposes of this question, so "Frank" will only ever be allowed to log in from the external IP, and never actually be sat in front of any system connecting internally, where as every other user of the system will only ever connect internally, and never connect from an external IP.

Franks IP that he connects from is a dynamically assigned one but the public IP he is connecting too is static and will never change, the internal IP of the port forwarder like wise will also never change and neither will the internal IP address of the SSH server.

Internal clients will always connect from an IP in the private network range that the internal SSH servers IP is part of and is a 16 bit mask EG: 192.168.0.0/16

Is this set up possible, using one config file and one SSH server instance? If so, how do I do it?

or

Am I much better using 2 running servers with different config?

For ref the SSH server is running on Ubuntu 18.04.

9
  • 6
    Untested, but look into using "Match LocalPort 22" to segregate config options.
    – Jeff Schaller
    Oct 7, 2019 at 11:55
  • Thanks Jeff, will do.
    – shawty
    Oct 7, 2019 at 11:58
  • 5
    @JeffSchaller I used to use Match address, and it works Oct 7, 2019 at 13:48
  • @JeffSchaller yep I was just reading up on that your "Match port 22" lead me in a direction I'd not yet explored, and as such have discovered that "match host" may be better as the external inbound and local use different domain addresses to reach them.
    – shawty
    Oct 7, 2019 at 14:35
  • @JeffSchaller with your pointers in these comments I was able to figure out how to do it, and without needing to use 2 ports :-) Now I guess I'll need to write up my own answer, unless of course you would like to take the points ... I did it using "Match Host" instead
    – shawty
    Oct 7, 2019 at 16:13

3 Answers 3

29

So, it turns out the answer was actually way, way simpler than I thought it would be.

I do however have to thank '@jeff schaller' for his comments, if it hadn't of been for him I wouldn't have started looking into how the SSH 'Match' configuration works.

Anyway

The trick is to set your /etc/ssh/sshd_config file up as default to be the configuration you would like to have for the access coming in from the external internet connection.

In my case, this meant setting the following

PermitRootLogin no
PasswordAuthentication no
UsePAM no

By doing this, I'm forcing ALL logins no matter where they come from to need to be key based logins using an SSH key.

I then on the windows machines used 'PuttyGen' to generate a public/private key pair which I saved to disk, and an appropriate ssh entry for my "authorized_hosts" file in the external users home directory.

I pasted this ssh key into the correct place in my users home folder, then set putty up to use the private (ppk) file generated by PuttyGen for log in and saved the profile.

I then saved the profile, and sent that and the ppk key file to the external user using a secure method (Encrypted email with a password protected zip file attached)

Once the user had the ppk and profile in their copy of putty and could log in, I then added the following as the last 2 lines on my sshd_config file

Match Host server1,server1.internalnet.local,1.2.3.4
    PasswordAuthentication yes

In the "Match" line I've changed the server names to protect the names of my own servers.

Note each server domain is separated by a comma and NO SPACES, this is important. If you put any spaces in it causes SSHD to not load the config and report an error, the 3 matches I have in there do the following:

server1 - matches on anyone using just 'server1' with no domain to connect EG: 'fred@server1'

server1.internalnet.local - matches on anyone using the fully qualified internal domain name EG: '[email protected]' (NOTE: you will need an internal DNS to make this work correctly)

1.2.3.4 - matches on the specific I.P. address assigned to the SSH server EG: '[email protected]' this can use wild cards, or even better net/mask cidr format EG: 1.2.* or 192.168.1.0/8 if you do use wild cards however, please read fchurca's answer below for some important notes.

If any of the patterns provided match the host being accessed, then the one and only single change to be made to the running config is to turn back on the ability to have an interactive password login.

You can also put other config directives in here too, and those directives will also be turned back on for internal hosts listed in the match list.

do however read this:

https://man.openbsd.org/OpenBSD-current/man5/ssh_config.5

carefully, as not every configuration option is allowed to be used inside a match block, I found this out when I tried to "UsePAM yes" to turn PAM authentication back on, only to be told squarely that wasn't allowed.

Once you've made your changes, type

sshd -T

followed by return to test them before attempting to restart the server, it'll report any errors you have.

In addition to everything above, I got a lot of help from the following two links too:

https://raymii.org/s/tutorials/Limit_access_to_openssh_features_with_the_Match_keyword.html

https://www.cyberciti.biz/faq/match-address-sshd_config-allow-root-loginfrom-one_ip_address-on-linux-unix/

19
  • 11
    Couldn't a (possibly nefarious) external user set up their own local DNS that resolves "server1" to your server's public IP, and then be able to use password instead of public key authentication? Oct 8, 2019 at 5:31
  • 1
    I guess they could, but it wouldn't within the grasp of your regular user they would need a fair bit of knowledge, and they would need to know what the internal DNS/Domain/server name is too. I've deliberately set the external name to be very different to the internal one, so to make an attack like that they would need pre existing knowledge from inside the network. I'm happy that the general 3rd party user, using this wouldn't have the skills to do this.
    – shawty
    Oct 8, 2019 at 9:53
  • 3
    If general users were what were needed to worry about, computer security would be easier.
    – erik258
    Oct 8, 2019 at 11:26
  • 1
    @pericynthion wouldn’t the sshd server itself need to be given that DNS response?
    – Wildcard
    Oct 8, 2019 at 14:33
  • @Wildcard no not in my case. I have the external domain name that's attached to the external IP, and that's controlled by an entirely separate DNS server that belongs to my domain registrar, the internal server name is registered in my own DNS server that sits on the inside of the firewall and allows me to assign any domains/hosts etc to any I.P in my internal net that I want. The internal never sends local requests out, the external never sees inside, they both stay on their own side of the firewall. Any domain names that the internal cannot satisfy from it's own setup are passed to my ISP
    – shawty
    Oct 8, 2019 at 16:23
8

1.2.* - matches on anyone in the local net using any address assigned to the SSH server that's in the 16 bit net mask for the server EG: '[email protected]'

Careful! Pattern matching in .ssh/config is based on string globbing, not necessarily ip addresses. According to the same manpage you are reading:

PATTERNS

A pattern consists of zero or more non-whitespace characters, ‘*’ (a wildcard that matches zero or more characters), or ‘?’ (a wildcard that matches exactly one character). For example, to specify a set of declarations for any host in the “.co.uk” set of domains, the following pattern could be used:

Host *.co.uk

The following pattern would match any host in the 192.168.0.[0-9] network range:

Host 192.168.0.?

If somebody tries to login from an IP that publicly reverse-resolves to 1.2.badguy.com , it will match your 1.2.* rule.

https://man.openbsd.org/OpenBSD-current/man5/ssh_config.5#PATTERNS

[Updated for completeness]

As noted elsewhere, you can use Match Address 1.2.0.0/16 instead of Match Host 1.2.*

[/Update]

3
  • I haven't tested this, but reading the documentation suggests that this could be done better by using Match Address 1.2.0.0/16.
    – marcelm
    Oct 8, 2019 at 9:49
  • Noted thanks. What I've actually done internally is 1.2.3.4 and set it to the specific I.P address assigned to that specific server. I added it in my answer above just for illustration that it could be done more than anything. In hindsight I may actually change it.
    – shawty
    Oct 8, 2019 at 9:55
  • If possible, always prefer locally knowable knowledge (i.e. one that does not not need remote resolution) against remotely obtained knowledge (i.e. DNS query). Unfortunately gethostname() and getnameinfo() (unless with NI_NAMEREQD) can always return numeric hostname (i.e. IP) if DNS does not resolve - which I believe is an erroneous design. Case in point, example in answer above. When you use Match Address the sshd will use getaddrinfo() instead, by which client address is lifted directly from peer of the socket and thus cannot be spoofed. So Match Address is substantially "safer".
    – etosan
    Dec 26, 2022 at 18:02
1

I think what you should be looking into is splitting out your ssh configs. For example; setup your /etc/ssh/ssh_config to have the global variables you need for all users by default (ssh key auth, port forwarding, etc...).

Then if your user (let's call him Bob) has a local (or nfs mounted) home directory, put a config just for him in /home/bob/.ssh/. This config will contain your match statement as well as if you need him to connect with a password rather then a cert as well as his own keep alive values, etc...

I have done this in the past and presently to lock down certain local accounts to come in from only one IP address and only allow cert based authentication while other user accounts may instead be using PAM authentication and have password based authentication.

In short, building and maintaining separate user config files based on user need is easier to manage than trying to keep it all under the default config file for ssh.

2
  • Note that ~/.ssh/config will have no effect on incoming connections, it is only used by ssh for outgoing connections. It is not read by sshd.
    – dnsbob
    Dec 26, 2022 at 15:04
  • please expand your answer on how you do path expansion in your SERVER config, as your answer confuses neophytes (by code fragment). Second putting server read configs into user's home, unless owned by root, and even then, is a security risk, as your user might learn and potentially change their configs without your approval. If I am not mistaken server does some rudimentary access right checking, but I would not bet on it. Never put server read user configs into the user's dirs.
    – etosan
    Dec 26, 2022 at 17:53

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .