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
- Log into new CentOS server with admin rights
12345sudo yum update -ysudo yum install policycoreutils-python -ysudo mkdir /sftpsudo mkdir /sftpkeyssudo groupadd sftp-external - Update /etc/ssh/sshd_config
123456789101112131415161718192021sudo 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-serverSubsystem sftp internal-sftp -l VERBOSE#disconnect idle clients after 10 minutesClientAliveInterval 600ClientAliveCountMax 0#Go the end of file and add the following blockMatch Group sftp-externalX11Forwarding noAllowTcpForwarding noAllowAgentForwarding noPermitOpen nonePermitTTY noPermitTunnel noForceCommand internal-sftp -l VERBOSEChrootDirectory /sftp/%uAuthorizedKeysFile /sftpkeys/%u/.ssh/authorized_keys - Restart sshd
1sudo systemctl restart sshd; systemctl status sshd
Create script to automate account creation, account folders, and account autorized_keys files
- Save the following on your SFTP server as sftpAccountSetup.sh and chmod 755 when complete
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667#!/bin/bash# Copyright (c) 2017 infiniteloop.io# https://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 $@; doif getent passwd $arg > /dev/null; thenprintf "[ERROR] $arg local user already exists, exiting...\n"exitelseprintf "[SUCCESS] $arg local user does not exist\n"fidone}verify-LocalUserAccount $@#these variables can be changed as needed but be sure to match values from step 1sftpGroup="sftp-external"sftproot="/sftp"sftpkeys="/sftpkeys"for acct in $@; dosshpath="$sftpkeys/$acct/.ssh"in="$sftproot/$acct/incoming"out="$sftproot/$acct/outgoing"#user account creationprintf "\nStart user creation for $acct\n"userdir="/" #change to /$in if you want the account to default to the incoming directoryusershell="/sbin/nologin"useradd -M $acct -d $userdir -s $usershell -G $sftpGrouppasswd -l $acct#create incoming and outgoing foldersmkdir $in -pmkdir $out -pchown $acct:$acct $inchown $acct:$acct $out#create placeholder file for account's public keyprintf "\nConfigure $sshpath and update folder context\n"mkdir $sshpath -psemanage fcontext -a -t ssh_home_t $sshpathrestorecon -v $sshpathtouch $sshpath/authorized_keyschown $acct:$acct -R $sshpathchmod 700 $sshpathchmod 600 $sshpath/authorized_keysdone - Call sudo bash sftpAccountSetup account1 account2 account3
- The script will check for existing username and exit if found. Be sure the accounts you attempt to create do not exist already
- Get the public key for the account and add content to /sftpkeys/accountX/.ssh/authorized_keys
- 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
- Attempt connecting from your remote client, remembering to specify the private key
- tail -f /var/log/secure to view log activity
- edit /etc/ssh/sshd_config, uncomment LogLevel, set to DEBUG3 and restart sshd if you’re still having issues with key authentication
Install fail2ban
- Install and setup new config file for fail2ban
1234sudo yum install epel-release –ysudo yum install fail2ban –ysudo systemctl enable fail2bansudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local - 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.
1234567[sshd]enabled = trueport = sshlogpath = %(sshd_log)sbackend = %(sshd_backend)smaxretry = 2bantime = 30 - sudo systemctl restart fail2ban; systemctl status fail2ban
- Use fail2ban-client status sshd to view sshd lockouts
- iptables -L can also display details about the ban
- tail -f /var/log/fail2ban.log to view log activity
- Use fail2ban-client set sshd unbanip x.x.x.x to remove the ban