How to mount SFTP accesses

De GiLUG
Dreceres ràpides: navegació, cerca

Català - Castellano - English


SFTP is the file transfer system that, differencing from traditional FTP, passes through an SSH secure connection, and then all the communication is ciphered and server authenticity is checked. Mounting an acces to a remote directory implies to allow accessing to the folders and files in that remote computer, in a fully integrated way in our computer, as if that folder was in our local computer.

With this guide's details, also the web-server administrator can completely replace FTP service by SFTP for the web content maintenance. The tutorial has been tested with Debian distributions, but with few difference can be useful for other GNU/Linux distributions and Unix with same software.

Server computer

The server is the system/computer that contains the folders and files to serve, that is, the accessible store from other computers.

  • At least the package openssh-server must be installed, and also the package libpam-umask or libpam-modules
  • In the file /etc/pam.d/sshd we need to add (it's not necessary to restart any service):
# Default umask mask for SSH/SFTP sessions
# Shell sessions: Settings in /etc/profile or ~/.bashrc or ~/.profile will replace this
session optional        pam_umask.so umask=0000

This sets a mask on the server over the mask from the client, when creating files and directories. This means that, in a case with pam-server having umask=0077 and client uses umask=0227 , the elements will be created with umask=0277 (server shades client), without detriment for the client when changes attributes after (chmod). In short: this is to create folders and documents with permissions for us and others.

If in the system we haven't a file /etc/pam.d/sshd then we can write the options in the generic /etc/pam.conf

  • Note1: Opening the mask at all in the pam-server (umask=0000), the elements are created transparently with the client-user permissions (his local umask), then is in the client-computer where better can be configured.
  • Note2: In internet you can find the trick of replacing in the server's /etc/ssh/sshd_config file the call to sftp-server or internal-sftp by a script, that first executes umask and after makes the call to sftp-server, but this cannot work with caged SFTP (chrooted), where Shell is not enabled to allow script execution.

Chroot SFTP (caged)

Connecting with SFTP to a server as is, system directories can be explored as if we were in a local session or in a SSH terminal. To avoid this to happen, is needed to set up an specific directory that the client will see as if it were server's root (/), and preventing terminal/console sessions.

  • Requirement: OpenSSH server version 4.8 or superior.
  • Set a user group that will have only SFTP access (without Shell), and the directory for their access paths:
groupadd sftponly
mkdir /etc/sftp-roots.d
  • In the file /etc/ssh/sshd_config disable the existing line "Subsystem sftp ..." (inserting # as first character), and add at the end of file:
Subsystem       sftp    internal-sftp
Match Group sftponly
	# Variables for ChrootDirectory: %h ($HOME) %u ($USERNAME)
	ChrootDirectory /etc/sftp-roots.d/%u
	ForceCommand internal-sftp
	AllowTcpForwarding no
	X11Forwarding no
  • Restart SSH service to make changes work (open SSH sessions aren't interrupted):
service ssh restart

Create caged users and directories

The user accounts created with the following instructions will be the affected by the chroot system. It's not created an own directory/profile at /home for them (optional) nor are allowed to access via terminal/SSH (necessary restriction). In this example the user will be called "narcis" and when he connects will access to the folder /mnt/mydata :

NewUser="narcis"
Destination="/mnt/mydata"
ln -s $Destination /etc/sftp-roots.d/$NewUser
adduser  --gecos "" --no-create-home --home /etc/sftp-roots.d/$NewUser --ingroup sftponly $NewUser
usermod -s /bin/false $NewUser
chown root $Destination
chmod go-w $Destination

Note that the access directory (in the example /mnt/mydata) must be protected by the superuser (root), and the external user (in the example "narcis") cannot create nor modify anything there. Write permissions can be given for a subdirectory, as for example "school":

ModifiableFolder="/mnt/mydata/school"
mkdir $ModifiableFolder
chown $NewUser:sftponly $ModifiableFolder
chmod ug+rwX $ModifiableFolder

Create normal users and directories

It is worth showing the simple formula without caging and with own profile in /home , so certain users can access to any directory as a local user (without administration privileges), and with the possibility of entering via SSH terminal.

NewUser="narcis"
adduser  --gecos "" --add_extra_groups $NewUser

In this case, we create a user typical directory in /home/narcis , where will go if connects by SSH terminal. By SFTP the root (/) is the same as the server, so for mounting from the client computer is needed to specify /home/narcis

  • Note: Normal users can also open a local session, that is, they can access using physically server's screen and the keyboard. If it's a desktop computer working as server, may be better to create users with the graphical environment tool.

Client computer

The client is the software/computer that will access to the folders and files in the network, that is, who doesn't have the data and wants to connect to the server. Someone could always connect in a simple way with a client FTP-software as gFTP, FileZilla or similar, but this guide is intended to have it mounted and integrated for all applications.

  • At least the package sshfs must be installed.
  • We need to enable the option user_allow_other in the file /etc/fuse.conf to allow self-mounting the connection by the normal user.
Normally we will find this option disabled with a comment mark (#) before:
#user_allow_other
that we must remove to leave as:
user_allow_other
  • Create a file /usr/local/bin/sshfs-mount.sh with this content I elaborated:
#!/bin/sh
# Connect an SFTP mount point
# Version 2010.12.28
# Copyright (GNU GPL) Narcis Garcia

LocalPath="$1"
RemotePassword="$2"
RestrictToUser="$3"

Connect ()
{
	Result=0
	AlreadyMounted="$(mount | grep " $LocalPath ")"
	if [ "$AlreadyMounted" = "" ] ; then
		if [ "$(which sshfs)" = "" ] ; then
			apt-get install -qq -y sshfs
		fi
		if [ "$(which mount.fuse.sshfs)" = "" ] ; then
			ln -s $(which mount.fuse) /sbin/mount.fuse.sshfs
		fi
		if [ ! -d "$LocalPath" ] ; then  # Create the mount point with permissions
			mkdir -p "$LocalPath"
			if [ "$RestrictToUser" = "" ] ; then
				chmod ugo=rwX "$LocalPath"
			else
				LocalGroup="$(id -ng $RestrictToUser)"
				chown $RestrictToUser:$LocalGroup "$LocalPath"
				chmod u=rwX,g=rX,o= "$LocalPath"
			fi
		fi
		echo "$RemotePassword" | mount "$LocalPath" -o password_stdin,allow_other,StrictHostKeyChecking=no
		Result=$?
		if [ $Result -eq 0 ] ; then
			echo "	Right"
		else
			echo "($Result) The command was:"
			echo "echo \"\$RemotePassword\" | mount \"$LocalPath\" -o password_stdin,allow_other,StrictHostKeyChecking=no"
		fi
	else
		echo "The directory was already mounted on $(ReturnWord () { echo $3; }; ReturnWord $AlreadyMounted)"
		Result=1
	fi
	return $Result
}
TempLog="/tmp/sshfs_$(echo "$LocalPath" | tr -s "/" "-" | tr -s " " "_").log"
FirstMessage="Connecting $LocalPath to the server$(if [ -f $TempLog ] ; then echo " (was interrupted)" ; fi)... "
printf "$FirstMessage"
Line1Log="$(date +'%Y-%m-%d %H:%M:%S') $FirstMessage"
Connect > $TempLog 2>&1
cat $TempLog
if [ $(id -g) -eq 0 ] ; then
	echo "$Line1Log$(cat $TempLog)" >>/var/log/sshfs.log
fi
rm $TempLog
  • Give execution permissions to the new file:
chmod a+x /usr/local/bin/sshfs-mount.sh
  • Create a file /etc/network/if-down.d/sshfs-umounts (without extension .sh) with this content I elaborated:
#!/bin/sh
# Disconnect all the SFTP mount points
# Version 2010.10.10
# Copyright (GNU GPL) Narcis Garcia
# Note: Only works with local and remote paths without spaces

Disconnect ()
{
	local LocalPath="$1"
	umount "$LocalPath"
	Result=$?
	if [ $Result -eq 0 ] ; then
		echo "	Right"
	else
		echo "($Result)"
	fi
	return $Result
}

CurrentText="$(date +'%Y-%m-%d %H:%M:%S') The SFTP accesses will be disconnected:"
echo "$CurrentText"
if [ $(id -g) -eq 0 ] ; then
	echo "$CurrentText" >>/var/log/sshfs.log
fi
MountedList="$(mount | grep -e " fuse\.sshfs ")"
if [ "$MountedList" != "" ] ; then
	IFS=$(printf "\n\b") ; for CurrentLine in $MountedList ; do unset IFS
		LocalPath="$(ReturnWord () { echo $3; }; ReturnWord $CurrentLine)"
		TempLog="/tmp/sshfs_$(echo "$LocalPath" | tr -s "/" "-" | tr -s " " "_").log"
		FirstMessage="Disconnecting $LocalPath from server $(if [ -f $TempLog ] ; then echo "(was interrupted)" ; fi)... "
		printf "$FirstMessage"
		Line1Log="$(date +'%Y-%m-%d %H:%M:%S') $FirstMessage"
		Disconnect "$LocalPath" > $TempLog 2>&1
		cat $TempLog
		if [ $(id -g) -eq 0 ] ; then
			echo "$Line1Log$(cat $TempLog)" >>/var/log/sshfs.log
		fi
		rm $TempLog
	done
else
	CurrentText="There was no mounted SFTP point."
	echo "$CurrentText"
	if [ $(id -g) -eq 0 ] ; then
		echo "$CurrentText" >>/var/log/sshfs.log
	fi
fi
  • Give execution permissions to the new file:
chmod a+x /etc/network/if-down.d/sshfs-umounts

Create accesses for user

With this recipe we prepare the mount of a unit to be used by a user in the client computer.

  • Check the user number (uid) and the group number (gid) from the user who will use the access:
id usuarilocal

We can obtain for example:

uid=1000(ngarcia) gid=1000(grupgarcia) grups=24(cdrom),46(plugdev),1000(grupgarcia)

from where to get the user number (1000) and the main group number (1000)

  • The user and password we will use to connect must esist in the server. Add the appropiate entry to the file /etc/fstab , supposing at the example that the server is on 192.168.1.5 and that we will mount it on the folder /home/ngarcia/Documents/school in the client computer.
# school-ngarcia
narcis@192.168.1.5:/school /home/ngarcia/Documents/school fuse.sshfs uid=1000,gid=1000,port=22,ConnectionAttempts=3,ServerAliveInterval=15,ServerAliveCountMax=5760,noauto,user,idmap=user,default_permissions,intr,transform_symlinks,follow_symlinks 0 0
  • Create a file /etc/network/if-up.d/sshfs-mounts (without extension .sh) with a content as the following:
#!/bin/sh
# Syntax for entries: sshfs-mount.sh "local path" "remote password" "restrict to user?"

sshfs-mount.sh "/home/ngarcia/Documents/school" "1234" "ngarcia"

Note: If we want to mount more folders, we only need to add entries to the same file.

  • Security measures, to hidden a little the password and to restrict the access from other users:
chown root:root /etc/network/if-up.d/sshfs-mounts /etc/network/if-down.d/sshfs-umounts
chmod u=rwx,go=  /etc/network/if-up.d/sshfs-mounts /etc/network/if-down.d/sshfs-umounts
mkdir /home/ngarcia/Documents/school
chown ngarcia:grupgarcia /home/ngarcia/Documents/school
chmod u=rwX,g=rX,o= /home/ngarcia/Documents/school

Still a user in the same computer with administration permissions (or from a Live-CD) can see the content in /etc/network/if-up.d/ , with the written passwords. The way to armor this, is the client-system to be in an encrypted partition, or this data separated in an encrypted volume.

  • Client-user default permission preparation
    • When an SFTP access is prepared for one/some user(s), umask is set in ~/.bashrc and ~/.profile - At the end of each file simply add/replace a line with a command as:
umask 0027
~/.bashrc is specific to Shell scripts/terminals interpreted by Bash in a user session.
~/.profile is to Shell scripts/terminals in other interpreters as Dash (no-Bash), in a user session.
      • If you want to know which shell uses the system as default (for example with scripts), execute in a terminal: ls -l /bin/sh
      • If you want to know which shell use the terminal/console sessions, execute in a terminal: echo $SHELL
    • When an STP access is prepared for everybody in the system, umask is set in /etc/profile
The configuration in /etc/profile avails by default for the Shell sessions (in terminal/console and all the system scripts). At the end of the file simply add/replace a line with a command as:
umask 0027
    • For both access types, set it at the different configuration files mentioned.

Note for navigators: In /etc/login.defs there is an UMASK parameter, but only avails for non-shell sessions (for example PPP connections from outside). The graphical sessions and SFTP are considered to be called from Shell-script.

  • To apply right all the changes (basically umask), the system must be restarted.
Alternative: not automatically mount

If we don't want the connection to be mounted automatically on computer startup and network access, simply we must avoid the entry in the file /etc/network/if-up.d/sshfs-mounts and two ways for mounting will be possible:

1. With the command:
mount /home/ngarcia/Documents/school
2. Or with the folders explorer. In desktops such as Gnome or KDE often appear the units not mounted from /etc/fstab , and going to these Places, that is, opening the accesses shown by the graphic environment, it will ask for the remote password and will mount the folder.

FUSE/SSHFS options (reference annex)

Here an explanation table about some supplied options in the mount entry and execution command:

user (for investigate)
noauto Specific to /etc/fstab for not to automatically mount, but it waits the user command.
The alternative should be "auto", but with the startup decorations probably the password couldn't be supplied.
ConnectionAttempts=3 Connection tries number. For a local network (wired or wireless) it's enough with 3. Can be appropiate to increment this for links through internet or with slow computers.
ServerAliveInterval=15 Time for each "keep alive" signal send to the server, to not to break the connection when no activity. In seconds.
ServerAliveCountMax=5760 Total number of "keep alive" sends to do. After, the connection can let die if hasn't activity.
idmap=user Enable UID translation used between client and server: if for example your local running user ID is 1034 but remote connecting user ID is 1002, then all files owned remotely by 1002 will be seen localy owned by 1034. There is no names translation/comparison. Default is disabled (idmap=none).
  • You don't want idmap=user if you want to see or get remote files strinctly with original owner:group numeric IDs.
uid=1000 Client-system user identificator to translate for the remote user identificator
With the tested version (SSHFS 2.2 FUSE 2.8.1), any remote user (owner) is seen as if was the specified user.
Acts as a disguise for reading (server->client), and a translation for writing (client->server).
  • Although the disguise, the server applies its permissions in a realistic way; the small inconvenient from the client can be not to seem coherent a restriction if all is seen his property.
  • You don't want this option if you want to see or get remote files with original ownership. Anyway there is no names translation/comparison, so a remote owner 1234 (remote UID for george) can be seen here as 1234 (local UID for mike).
gid=1000 Client-system group identificator to translate for the remote usergroup identificator.
With the tested version (SSHFS 2.2 FUSE 2.8.1), any remote group is seen as if was the specified group.
Acts as a disguise for reading (server->client), and a translation for writing (client->server).
  • Although the disguise, the server applies its permissions in a realistic way; the small inconvenient from the client can be not to seem coherent a restriction if all is seen his group.
  • You don't want this option if you want to see or get remote files with original group. Anyway there is no names translation/comparison, in the same way as UID.
umask=0027 Permission mask for files and subdirectories.
With the tested version (SSHFS 2.2 FUSE 2.8.1), it makes client to see all the elements disguised as if have this permissions (0027 -> rw-r-----).
To see the permissions agreed to the server, avoid this parameter.
Note: Although the disguise, the server applies its permissions in a realistic way; the inconvenient from the client can be not to seem coherent a restriction if favorable permissions are seen.
default_permissions Enables permission control (not users-owners) from the client-system, in place of showing outdated attributes and expect error responses from server (R/W)
intr Allow requests to be interrupted
transform_symlinks Translate the absolute links in the server for relative links to work from the client
follow_symlinks (related to transform_symlinks)
port=22 TCP port to use to connext to SSH server (22 by default)
password_stdin Expect the password via stdin:
echo "password" | mount /mnt/point -o password_stdin

Is the way to automatically send the password from a script. Is convenient to use with the command call; if is put in fstab can be problematic when mounting in a graphical environment.

allow_other Normal users can access to the mounted content (not root)
Is convenient to use it only in the command call; putting it to fstab is incompatible with self-mounting the connection by the normal user.
StrictHostKeyChecking=no Assume as acceptable the first RSA key returned by the server.
Is convenient to use only in the command call, to allow a better error management with different RSA.

More information with:

man sshfs
man ssh_config

General working of umask

Is a template-mask. Is as a chmod inverse, because is used for shading the permissions to be set when creating files and directories. As higher is the octal value, more restrictive (at binary level a bit 1 shades an attribute and a bit 0 allows it).

0 allows rwX
1 allows rw-
2 allows r-X
3 allows r--
4 allows -wX
5 allows -w-
6 allows --X
7 allows ---
  • Examples:
Then with umask 0022 files are created with permissions rw-r--r-- and subdirectories with rwxr-xr-x
with umask 0007 files are created with permissions rw-rw---- and subdirectories with rwxrwx---
with umask 0027 files are created with permissions rw-r----- and subdirectories with rwxr-x---
  • Initial zero is put to note that it's written in octal (0022 instead of 022)
  • All this is without detriment for changing attributes after (chmod)

Synchronization, backups

Notes:

  • The only way to preserve ownerships and at the same time match remote names with local ones, is to have the same numeric IDs in the local system as in the remote server. This is effectively done when have identical contents in these files:
/etc/passwd
/etc/group

See also

Pending to document

  • Develop more sshfs-mount.sh to mount also folders that haven't entry in "fstab" (this should avoid disk unit showing on the folder explorers, or should make easier to have a portable script-tool)
  • "transform_symlinks" does not seem to work, and then cannot be tested the effectiveness of "follow_symlinks"
  • Utility of "user" option is pending to see.
  • Take care that scripts in /etc/network/if-up.d and /etc/network/if-down.d are executed for each network interface.