CentOS 7 SFTP Setup

I wanted to setup SFTP + non-standard directories + fail2ban and figured an end-to-end solution had to be documented at this point by someone…somewhere.  Nope – I had to stitch together several pieces from a handful of stackoverflow posts to finally get a consolidated solution.  I made this a bit more complicated than I expected due to changing default location for the public keys, which then caused SELinux to block key exchange at first.  That issue has been fixed in the script below and will survive reboots.

 

Configure CentOS

  1. Log into new CentOS server with admin rights
    sudo yum update -y
    sudo yum install policycoreutils-python -y
    sudo mkdir /sftp
    sudo mkdir /sftpkeys
    sudo groupadd sftp-external
  2. Update /etc/ssh/sshd_config
    sudo vi /etc/ssh/sshd_config
    
    #as far as I can tell this step isn't necessary if you force it again for members of sftp-external match group below
    #Subsystem    sftp    /usr/libexec/openssh/sftp-server
    Subsystem    sftp    internal-sftp -l VERBOSE
    
    #disconnect idle clients after 10 minutes
    ClientAliveInterval 600
    ClientAliveCountMax 0
    
    #Go the end of file and add the following block
    Match Group sftp-external
        X11Forwarding no
        AllowTcpForwarding no
        AllowAgentForwarding no
        PermitOpen none
        PermitTTY no
        PermitTunnel no
        ForceCommand internal-sftp -l VERBOSE
        ChrootDirectory /sftp/%u
        AuthorizedKeysFile /sftpkeys/%u/.ssh/authorized_keys
    
    
  3. Restart sshd
    sudo systemctl restart sshd; systemctl status sshd
    

 

Create script to automate account creation, account folders, and account autorized_keys files

  1. Save the following on your SFTP server as sftpAccountSetup.sh and chmod 755 when complete
    #!/bin/bash
    # Copyright (c) 2017 infiniteloop.io
    # http://infiniteloop.io
    # Version 1.0 - April 7, 2017
    # 
    # Redistribution and use in source and binary forms, with or without modification, are permitted 
    # provided that the following conditions are met:
    # 1) Redistributions of source code must retain the above copyright notice, this list of conditions 
    #     and the following disclaimer.
    # 2)  Redistributions in binary form must reproduce the above copyright notice, this list of conditions 
    #     and the following disclaimer in the documentation and/or other materials provided with the distribution.
    #
    # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 
    # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
    # DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
    # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
    # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
    # IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
    # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    
    function verify-LocalUserAccount ()
    {
      for arg in $@; do
        if getent passwd $arg > /dev/null; then
          printf "[ERROR] $arg local user already exists, exiting...\n"
          exit
        else
          printf "[SUCCESS] $arg local user does not exist\n"
        fi
      done
    }
    
    verify-LocalUserAccount $@
    
    #these variables can be changed as needed but be sure to match values from step 1
    sftpGroup="sftp-external"
    sftproot="/sftp"
    sftpkeys="/sftpkeys"
    
    for acct in $@; do
      sshpath="$sftpkeys/$acct/.ssh"
      in="$sftproot/$acct/incoming"
      out="$sftproot/$acct/outgoing"
    
      #user account creation
      printf "\nStart user creation for $acct\n"
      userdir="/"    #change to /$in if you want the account to default to the incoming directory
      usershell="/sbin/nologin"
      useradd -M $acct -d $userdir -s $usershell -G $sftpGroup
      passwd -l $acct
    
      #create incoming and outgoing folders
      mkdir $in -p
      mkdir $out -p
      chown $acct:$acct $in
      chown $acct:$acct $out
    
      #create placeholder file for account's public key
      printf "\nConfigure $sshpath and update folder context\n"
      mkdir $sshpath -p
      semanage fcontext -a -t ssh_home_t $sshpath
      restorecon -v $sshpath
      touch $sshpath/authorized_keys
      chown $acct:$acct -R $sshpath
      chmod 700 $sshpath
      chmod 600 $sshpath/authorized_keys
    done
  2. Call sudo bash sftpAccountSetup account1 account2 account3
    1. The script will check for existing username and exit if found.  Be sure the accounts you attempt to create do not exist already
  3. Get the public key for the account and add content to /sftpkeys/accountX/.ssh/authorized_keys
    1. If you or your clients are primarily Windows shops just use puttygen.  Be sure to copy text from the puttygen GUI – it should not contain any line breaks
  4. Attempt connecting from your remote client, remembering to specify the private key
    1. tail -f /var/log/secure to view log activity
    2. edit /etc/ssh/sshd_config, uncomment LogLevel, set to DEBUG3 and restart sshd if you’re still having issues with key authentication

 

Install fail2ban

  1. Install and setup new config file for fail2ban
    sudo yum install epel-release –y
    sudo yum install fail2ban –y
    sudo systemctl enable fail2ban
    sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
  2. Edit new jail.local in the [sshd] section, the following will block your source IP after 2 bad attempts for 30 seconds.  It’s not a bad way to see the fail2ban in action but obviously increase the bantime if using in a production environment.
    [sshd]
    enabled = true
    port    = ssh
    logpath = %(sshd_log)s
    backend = %(sshd_backend)s
    maxretry = 2
    bantime = 30
    
  3. sudo systemctl restart fail2ban; systemctl status fail2ban
  4. Use fail2ban-client status sshd to view sshd lockouts
    1. iptables -L can also display details about the ban
    2. tail -f /var/log/fail2ban.log to view log activity
  5. Use fail2ban-client set sshd unbanip x.x.x.x to remove the ban