If we deploy an application automatically we have to grant the CI (Continuous Integration) access to the server. Common practice is to do that via a GitLab Runner or a ssh account on the server.
Personally I would not recommend to use a GitLab Runner for deployments, because you have to maintain it. Another potential issue is, that you normally register runners for your whole GitLab instance or groups. That results in a scenario in which everyone can use that runner and accidentally (or not) destroy, for example, your production server. To avoid that you have to register the GitLab Runner in the Project it belongs to only. But even then your production server can be misused as a build worker and therefore create performance issues.
This post focuses on deployments via ssh.
A normal ssh deployment often contains copying files to the server and executing some commands. This can be done manually or via CI. Have you ever thought of what happens when the credentials of that user leak? Even if you have a special deployment user and do not use root. Nearly every file on the server can be read with a normal user (including configs containing credentials). And even if the deployment user only has access to the application folder it can read and write all files. When you are using docker and the user has permission to execute docker commands he has effectively root access.
One solution to that problem is to setup a chroot environment for that user, but it does not solve the docker "problem" as docker normally runs as a (root) daemon and therefore not in the chroot environment.
The solution I currently use is a combination of a custom shell for the ssh user and disallowing file-transfers. By defining what commands the ssh user can execute the potential damage can be minimized.
In the following steps you learn how to setup a custom shell and disable file-transfer. Disabling the file-transfer changes the role of the server, as it needs to retrieve the files, eg. for an update, itself. If you want to learn more about this concept you can read this post: Handling server configurations.
I assume you already created a user which the CI should use.
In this tutorial the username is
2. Change the default shell
sudo touch /home/application-deploy/shell.sh 1 sudo chown root:root /home/application-deploy/shell.sh 2 sudo chmod a+x,a+r,g-w,o-w /home/application-deploy/shell.sh 3 sudo chsh -s /home/application-deploy/shell.sh application-deploy 4 sudo touch /home/application-deploy/.hushlogin 5
- create the shell file
- set owner and group to root
- set file permissions: everyone can execute but only root can edit
- change the shell of the user to the script
- disable the login banner
For a first test change the content of
shell.sh and connect with that user to the server.
Our custom shell is working.
If you want to login as the user
application-deploy you have to connect to the server with another user and then switch users:
su --login --shell /bin/bash application-deploy
3. Hardening ssh
In order to harden the ssh server we need to disable some features of ssh for the user
To do so add the following to
/etc/ssh/sshd_config and then restart the
Match User application-deploy PasswordAuthentication no 1 AllowTcpForwarding no 2 X11Forwarding no 3 PermitTunnel no 4 GatewayPorts no 5 AllowAgentForwarding no 6
- disable password authentication - remove this line if you have to use passwords instead of ssh-keys
- disable port forwarding
- disable X11 forwarding
- disable VPN capability
- disable ssh-agent forwarding
4. Changing the shell script
Maybe you noticed the
-c at our test if parameters where sent.
In this step we will remove that and finish the custom shell.
First replace the content of the shell with the following:
-c will be removed from the parameters if it is the first parameter of our custom shell.
The changed parameters are available in the variable
From here it’s up to you and what you want to do.
You can type your commands right into the shell file or as I did delegate to the applications