amuck-landowner

Restricting Multiple VMs to a Single Access Point

Aldryic C'boas

The Pony
Hey folks.  This is a per-request follow-up to MannDude's SSH Gateway inquiry, about locking multiple VMs to only accept SSH from a single access point.  As always, ALWAYS RUN NEW CODE IN A DEVELOPMENT ENVIRONMENT FOR TESTING BEFORE DEPLOYING TO PRODUCTION EQUIPMENT.  Also, all VMs referenced below are assumed to be running Debian 6/7.  Since we're only dealing with the sshd daemon, this makes little difference and should be cross-distro compatible.  However, some distros are funny about particular syntax with /etc/ssh/sshd_config entries - use caution and man pages, in that order.

For the purpose of this guide, we'll assume that you have the following four VPSes:

  • Gate (IP: 1.1.1.1, primary VPS or home connection w/ dedicated IP)
  • Anna (IP 1.1.1.2, VPS to be SSH-locked)
  • Boris (IP 1.1.1.3, VPS to be SSH-locked)
  • Vasily (IP 1.1.1.4, VPS to be SSH-locked)
(If you're not going to use a 'primary' VPS, and wish to just lock the other VPSes to your home IP, skip to step two)

STEP 1 (Gate):

For convenience sake, we'll add the three VPSes to the /etc/hosts file so that we don't have to keep remembering their IPs.  Fire up vi (or nano if you must :(), and add the following to your /etc/hosts file on Gate:


1.1.1.2 Anna
1.1.1.3 Boris
1.1.1.4 Vasily

We're not going to add anna.hostname.com, though if you do use domains for that purpose feel free.

Next, we're going to generate a new SSH key for Gate.  (What, you use the same id_rsa for everything?  Shame on you).


-13:24:12- Gate:~ :: aldryic % ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/aldryic/.ssh/id_rsa): <ENTER>
Enter passphrase (empty for no passphrase): walruscock<ENTER>
Enter same passphrase again: walruscock<ENTER>
Your identification has been saved in /home/aldryic/.ssh/id_rsa.
Your public key has been saved in /home/aldryic/.ssh/id_rsa.pub.
The key fingerprint is:
bc:88:78:1f:93:d6:42:71:d1:d5:2d:82:5e:52:26:fb aldryic@Gate
The key's randomart image is:



(no, my password is not walruscock, and you shouldn't use it either)

Nab a copy of that .pub file (or just copy/paste it to a text editor for now), we'll be using it again in a bit.

STEP 2 (Anna, Boris, Vasily):

Now to secure А́нна, Бори́с, and Васи́лий.  The 'normal' /etc/ssh/sshd_config file is a bit of a mess.. so feel free to use this one if you're so inclined:


Port 8008
Protocol 2
ListenAddress 1.1.1.2

SyslogFacility AUTHPRIV

LoginGraceTime 15s

PermitRootLogin no
RSAAuthentication no
PubkeyAuthentication no
PasswordAuthentication no

UsePAM yes

PermitEmptyPasswords no
ChallengeResponseAuthentication no

GSSAPIAuthentication no
GSSAPICleanupCredentials no

AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
AcceptEnv LC_IDENTIFICATION LC_ALL
PermitUserEnvironment no

AllowTcpForwarding no
GatewayPorts no
X11Forwarding no

PrintMotd yes
PrintLastLog yes

TCPKeepAlive yes

UsePrivilegeSeparation yes

Compression delayed
ClientAliveInterval 0
ClientAliveCountMax 3

UseDNS no

Match address 1.1.1.1/32
RSAAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFiles .ssh/authorized_keys



NOTICE: You MUST change the Port, ListenAddress (to reflect the real IP), and Match address (to reflect Gate's real IP).  Why do we want to bind a ListenAddress?  Because when a VPS has more than one public-facing IP, why would we ever want SSH to respond except where we want it?  Limit all possible breach points.

Now, take the .pub file we created earlier when running ssh-keygen and place it in your home directory.  Already know what to do with the .pub file?  Good for you, don't spoil it for everyone else :p

Now we'll run a few simple commands as root:


su aldryic
mkdir .ssh
cp ~/id_rsa.pub ~/.ssh/authorized_keys
chmod 700 ~/.ssh
chmod og-rw ~/.ssh/authorized_keys
chmod a-x ~/.ssh/authorized_keys
exit

This'll take that pubkey, and create an appropriate authorized_keys file for the sshd to use.  Replace 'aldryic' with your own username (please make sure you actually create your user account first >_>).

Next, because you should never ever EVER allow root login, we'll make sure we have sudo running, and add our user to the sudo list:


apt-get update; apt-get install sudo
gpasswd -a aldryic sudo
/etc/init.d/sudo restart

Alternately, if you don't want to screw with sudo (yeah, it can be annoying sometimes),  you can just use the su command and input the root password whenever you login and need to do root work.

All that's left now is to restart the sshd, and test the connection.  Don't (and I can't stress this hard enough), DON'T close your current SSH session.  You will just leave yourself locked out of the VM if you do this and something FUBAR'd.  Open a new putty/ssh to test with.

Followup:

The major drawback to this setup is that it requires Gate to have a permanent, static IP.  If Gate's IP changes, you're SOL and locked out of the VMs unless you're with a provider that offers a working serial console or VNC.  The best workaround to this is to create a copy of the /etc/ssh/sshd_config we made in Step 2, and use your preferred method of distribution (rsync, httpd/wget on cron, etc) to have the file distributed to each VPS.  (Remember that since we used ListenAddress, you need a distinct sshd_config for each VPS).  There are honestly way too many ways to accomplish this for me to list here - that could be a guide all it's own.  And if you're at the point where you're ready to use SSH lockdowns of this level, you're already well familiar with options such as rsync anyways :p
 

drmike

100% Tier-1 Gogent
Wow!  Thank you kindly sir.   Didn't expect all that so quickly and detailed.

Adding this to my testing and to be tried tutorial list.
 

kunnu

Active Member
Verified Provider
Sometimes my ISP change IP Address, It is possible to use alternative method to access your server?(without compromising to security)
 

Aldryic C'boas

The Pony
Sometimes my ISP change IP Address, It is possible to use alternative method to access your server?(without compromising to security)
Well, the best advice I could give there is re-reading the guide, and noticing that I specified a VM with a dedicated IP (Gate) as the primary :p  If you intend to run such a setup from your residential ISP, then I would suggest contacting them and requesting a dedicated IP.
 
  • Like
Reactions: bfj

HalfEatenPie

The Irrational One
Retired Staff
Sometimes my ISP change IP Address, It is possible to use alternative method to access your server?(without compromising to security)
In addition to what Aldryic said I'd suggest you just use a VPN in that case as dynamic IPs are... well... a pain to deal with sometimes.  Although have a few other backups in place just in case you lose access to that specific VPN and such.  

Great post Aldryic C'boas!  
 

acd

New Member
As a community "thank you", here's an automatic push-conf-to-destinations setup for slaves, heavily borrowed from my nsd3/bind slaving guide on the buyvm wiki. Do not test in production environments, you will be made to cry unmanly tears.

 

requires curl and gawk (for gsub).

 

You must provide the cabundle.pem file; a copy of the cert file used by your http server is usually sufficient.

 

/etc/sshdauto/checkupdates.sh:



#!/bin/sh

#uncomment for testing if something is breaking:
# set -x

​#at a minimum, you must change the following line:
FETCHURL="https://master.yourdomain.com/masterconf.txt"

WORKDIR=/etc/sshdauto
MASTERCONF="$WORKDIR"/mconf
INTERCONF="$WORKDIR"/interconf
DESTFILE="/etc/ssh/sshd_config"
AWKFILE="$WORKDIR"/sshd_config.awk
LOCALCONFIG="$WORKDIR"/localconfig
COMPFILE="$WORKDIR"/zcomp
CERTFILE="$WORKDIR"/cabundle.pem
SSHBIN=/usr/sbin/sshd
IPBIN=/sbin/ip

reload_cfg () {
# /etc/init.d/ssh restart 2>&1 > /dev/null
service ssh restart 2>&1 > /dev/null
}

lastupdate="`curl --cacert \"$CERTFILE\" -i -X HEAD --http1.0 \"$FETCHURL\" 2> /dev/null | awk 'BEGIN{stat200=0} /HTTP.*200.*OK/{stat200=1} {if (stat200 != 1) { exit 1 }; } /Last-Modified:/{ gsub(/^Last-Modified:[ \t]*/, \"\"); print}'`"

if [ -z "$lastupdate" ] ; then
  echo "Problem fetching from $FETCHURL";
  exit 1;
fi

touch -d "$lastupdate" "$COMPFILE"

# If the destination file doesn't exist or the file on the distribution server is newer...
if [ ! -r "$DESTFILE" ] || [ "$COMPFILE" -nt "$DESTFILE" ] ; then
  if ! curl --cacert "$CERTFILE" "$FETCHURL" 2>/dev/null > "$MASTERCONF" ; then
    echo "Could not fetch master conf";
    exit 2;
  fi

  . $LOCALCONFIG

# If localconfig had LISTENIP set and that IP is assigned to us somewhere.
  if [ -z "$LISTENIP" ] || ! "$IPBIN" addr show | grep "$LISTENIP" > /dev/null ; then
    echo "The IP address we should use is not properly configured."
    exit 3;
  fi

  awk -v lip="$LISTENIP" -f "$AWKFILE" "$MASTERCONF" > "$INTERCONF"

# Let sshd test the output file for us and let us know if it is good to go.
if "$SSHBIN" -t -f "$INTERCONF" 2>&1 > /dev/null ; then
# you could rm this, but I like to keep it around.
mv "$DESTFILE" "$DESTFILE".old
mv "$INTERCONF" "$DESTFILE"

  reload_cfg
else
echo "The generated config file was invalid, cannot continue with update."
exit 4;
fi

# uncomment if you don't want to keep your last pass masterconf around.
  # rm -f "$MASTERCONF"
fi

rm -f "$COMPFILE"
/etc/sshdauto/sshd_config.awk:


{ gsub(/%LISTENIP%/,lip); print }
An example masterconf.txt per Aldryic's guide:



Port                    8008
Protocol                2
ListenAddress           %LISTENIP%

SyslogFacility          AUTHPRIV

LoginGraceTime          15s

PermitRootLogin         no
RSAAuthentication       no
PubkeyAuthentication    no
PasswordAuthentication  no

UsePAM                  yes

PermitEmptyPasswords            no
ChallengeResponseAuthentication no

GSSAPIAuthentication            no
GSSAPICleanupCredentials        no

AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
AcceptEnv LC_IDENTIFICATION LC_ALL
PermitUserEnvironment no

AllowTcpForwarding no
GatewayPorts no
X11Forwarding no

PrintMotd yes
PrintLastLog yes

TCPKeepAlive yes

UsePrivilegeSeparation yes

Compression delayed
ClientAliveInterval 0
ClientAliveCountMax 3

UseDNS no

Match address 1.1.1.1/32
    RSAAuthentication    yes
    PubkeyAuthentication yes
    AuthorizedKeysFiles  .ssh/authorized_keys
/etc/sshdauto/localconfig:



LISTENIP=1.1.1.2
/etc/crontab line to run the script every 5 minutes:



# m h dom mon dow user  command
4-59/5 * * * * root /etc/sshdauto/checkupdates.sh

best regards,

-tw

edit: changed reload to restart, just in case something messed up happened and there is no instance to kick.

edit: escaped some quotes that I missed (oops)
 
Last edited by a moderator:
Top
amuck-landowner