SFTP is super awesome
Posted on
My dad has a site that he has been working on since the windows 3.1 days. It's static site and up until now he had been paying a little over a hundred dollars a year to have it hosted.
This came up during the winter of last year, where he had to pay for a renewal for his domain as well as his hosting. The hosting company did not accept payment from where he's located, so he asked me to handle the payments on his behalf. This is when I realized that he has been overpaying for a service that you can get without paying (e.g GitLab pages). There was no getting away from the cost of the domain name, that's just the nature of having your own domain, but hosting (for static sites) can be costless.
His current workflow was to make changes on his computer then upload the files via ftp to the hosting platform. These files are served out of the ftp directory to the web.
Getting my dad to use git was a no go from the start. As much as I like it and use it everyday for personal and professional use, it isn't beginner friendly. In fact there's a good chunk of professional programmers that cannot use it properly either. On top of learning the git commands and workflow he would also need to understand ssh keys which are not that easy to explain either (or why they are important). Especially coming from the old ftp workflow.
# The solution
I still wanted to his site backed by git. I think it's a great idea as it gives us source control instead of just files that live on his hard drive. Having a git repository also lets me take advantage of the free hosting that GitLab pages provides.
This means I would need to be able to provide a ftp like interface that ties into a git repository. My first thought was that I need something that I can watch a directory for file system events, and after a set delay execute some commands. Watchman exists but its a little heavy handed for what I needed.
I decided to write my own tool dcmd
.
This also gave me a convenient excuse to dabble in rust some more.
This would cover the committing + pushing part of our solution. All that I had to do was to provide ftp interface that pointed to the relevant part of the git project.
I originally wanted to use vsftp
as it's included in arch's community repository.
At moment it seems to not be working (core dumps on access) as well as out of date.
The other thing I did not like about vsftp
is that it's underlying protocol is ftp and ftp is not secure (sends password over plain text).
This is where SFTP comes in.
It's provided by default by OpenSSH and as the S
indicates it's more secure.
I would need to do the following to get it setup
- Create a SFTP user
- Create a chroot jail for the user
- Create a symlink in the jail that maps to the static files in the git repository
- Edit my
sshd_config
file to use the jail
Now I mentioned before that my dad is not familiar with ssh (and ssh keys) so I would need a password for him. On my server I disable password logins for security. I thought this would be a problem but turns out we can have both out of the box. All I needed to add to my configuration was this:
<Rest of config>
Match User <my sftp user>
ChrootDirectory <path to my jail>
ForceCommand internal-sftp
PasswordAuthentication yes
Match all
This forces the SFTP user to the jail and allows it to login using a password. I had concerns that this would also grant ssh access to my machine but turns out it does not:
❯ ssh <user>@<host>
<user>@<hosts>'s password:
This service allows sftp connections only.
Connection to <host> closed.
Even if the password gets leaked I can be sure that my server remains secured (and why the title of this post is about SFTP being awesome).
The last thing I wanted to do is limit the blast radius if somehow the git credentials were compromised. Basically I do not want to lose any commits or have the repository deleted. For this I ended up creating a "bot" account on GitLab (just a fresh account) and gave it its own ssh keys. This bot would then have developer (not maintainer) access to the git project. This means the bot can push directly to the repository and have CI run and deploy to pages, but it can't change repository settings or force push.
The one other thing I was missing a systemd service file for dcmd
that would ensure it keeps running.
This was simple enough (I've done it before for other things) and there's a crap ton of resources on the internet for it (including the systemd man pages) so I won't be pasting the service file here.
The last thing to do before handing the credentials over to my dad was to make sure that this whole setup actually worked.
I SFTP'd in with the user I had set up and this is when I found out that you can't symlink to directories out of the chroot jail. This kind of makes sense and it would not be a good jail if the user could access files outside of it.
A bind mount remedied this problem.
mount --bind src_dir target_dir
Bind mounts basically copies a directory over to another place, but they point to the same files in the file system.
This means that if the files are added/removed in the SFTP directory they are also modified in the git repository.
This lets dcmd
do its job and create a commit and push it.
My dad confirmed that the credentials worked for him and he can go back to his old workflow. The difference this time is his work is now preserved somewhere else other than his computer and he gets to save some money! :D
This was a super fun side project and I can already think of some improvements to dcmd
if I ever want dabble in rust some more.
It's a nice feeling when I can use my expertise/skills to help out my dad.