TGLMAN

SSH forward of git user for forges

I had to work on more important things, but at the end, as sometime happens I got hooked on a problem, and I could not give up till I solved it.

Soooo just for testing and playing around I installed an instance of forgejo, I did it on a local/home machine, and tried to make it public on the internet using some SSH and proxy, as far as was for http everything was easy and smooth, and I could make it public in no time (well a bit more, the post of SSH tunnels is about that actually)

I tried a few things, and it looked fine, obviously one thing was missing, the support of SSH access to the repositories, being not a public machine I had to proxy SSH from a public machine, so I did a quick search on how to forward the SSH connection only for a specific user and I got a few results that mainly said "it's hard, just use jump host in the client" but after my success with other SSH tunnels I did not want to give up, so I kept searching, the first useful result was some configuration for usage of forgejo in docker and the authentication forwarding from the host SSH to docker.... here is the link

Well let's go one step back, the problem of SSH forwarding can be addressed as a couple of problems:

  • first is the SSH key based authentication used by the forge, the SSH keys needed to authenticate the user are in forgejo that sit on a different machine
  • second is after the authentication is successful the connection has to be tunneled to the destination host with all the authentication information

If the tunneling problem is approached as these two separate problems it becomes "easy to solve", just trying to 'tunnel' the connection for a specific user to other server it seems impossible.

To do the first step the blog post for docker forwarding really helped, sshd allow to configure commands to retrieve the 'authorized_keys', this commands can be anything, even a lookup using SSH on a different machine, and that is the solution!! We can just configure our SSH server to look for the SSH keys from the other machine for a specific user, here it is the configuration:

Match user forgejo 
    AllowUsers forgejo@*
    AuthorizedKeysCommandUser forgejo
    AuthorizedKeysCommand ssh -N -p 4022 -o StrictHostKeyChecking=no forgejo@127.0.0.1 /usr/bin/forgejo keys -e forgejo -u %u -t %t -k %k
    PasswordAuthentication no

The AuthorizedKeysCommand is the important part, in this case execute a specific ssh connection for forgejo, but can be anything you need, here are the docs if the possible options you can pass forward to look for the key: AuthorizedKeysCommand and Tokens used for forward parameters.

But let's go through the specific details of the command I gave in this specific case, what it does it connect to an ssh on localhost(127.0.0.1) on port 4022, this is not the actual server is just an ssh tunnel that re-bind the private machine ssh port to public one.
One not clear information from this snippet is that the user forgejo on the public machine should have its own public key in the .ssh/authorized_keys of the forgejo user in the private machine, (StrictHostKeyChecking=no may not be required if you do a first key accept connection with the forgejo user), also because we are fetching the key from forgejo from remote, is better to disable the generation of the .ssh/autorized_keys file from forgejo with the option SSH_CREATE_AUTHORIZED_KEYS_FILE=false in the app.ini.
Then the important part is the command executed that in this case is specific to forgejo /usr/bin/forgejo keys -e forgejo -u %u -t %t -k %k this run the forgejo binary requesting to authenticate the user with the provided connection parameters, the result of this command is an "authorized_keys line" with the user key configured on the forgejo server, if the authorization is valid, that will be used by the ssh server to establish the connection with the client.

At this point we successfully authenticate the user, now we need to forward the data sent by the user to the private server, to solve this it comes to help on how forgejo generate the "authorized_keys line", in its base implementation it uses the option "command" for specific what command to execute as SSH ForceCommand, in doing so it also passes forward the information to identify what user is being connected.
So looking around the configuration settings I found that is possible to customize this command from the forgejo configuration, so I changed it to use a script to forward the connection through the private server, this is the new configuration that I set in:

SSH_AUTHORIZED_KEYS_COMMAND_TEMPLATE:/home/forgejo/forward.sh {{.AppPath}} --config={{.CustomConf}} serv key-{{.Key.ID}}

Here is the reference of forgejo documentation it may be necessary as well to customize the generation of the authorized_keys as well SSH_CREATE_AUTHORIZED_KEYS_FILE

This script make sure that when it was called would send the command to the right machine obviously using SSH, Here is the 'forward.sh' script:

#!/usr/bin/bash 
/usr/bin/ssh forgejo@127.0.0.1 -p 4022 -o StrictHostKeyChecking=no SSH_ORIGINAL_COMMAND=\"${SSH_ORIGINAL_COMMAND}\" $@

This script get as parameter the original forgejo command connect from the public host to the private and execute the parameter script, also notice that it needs to send the original command, with an environment variable, this is because this variable contains the commands that git would execute, and forgejo will make sure to execute them in the right user environment, if the parameter a not passed forgejo will think that you are trying to open a normal interactive SSH session and stop you to do that, and obviously fail to do any got thing.

It has been a quite trick implementation and took me a few days to figure out all the peaces, and even more days to get down and write a blog post about it, but here it is! I hope you enjoyed!