Configuring Gitea to clone repositories over SSH

5 minute read Published: 2022-07-16

I recently created a Gitea instance to host my projects. It all went fine but the one thing I found annoying was cloning repositories from my Gitea instance.

There are two common ways to clone repositories. One is over HTTPS and another is using SSH. Cloning with HTTPS is fine for public repositories but you have to enter credentials every time to clone private repositories or push your changes to remote.

You can tape over this problem and let git cache credentials by adding this in your ~/.gitconfig.

[credential]
	helper = cache --timeout=1800 # Time in seconds

but this is not a good solution. It'll still ask for your credentials when credentials cache expires and it'll ask for credentials if you have 2FA enabled on your account.

All these reasons is why I prefer cloning repositories over ssh. Once you have added your public key to Gitea, It can authenticate using public key auth and you don't ever have to enter your credentials but here is the problem.

You have a few options in Gitea to enable cloning repositories over SSH.

  1. Configure Gitea to listen for SSH traffic on some port other than 22 which is likely used by the OpenSSH daemon on the host. The URL to clone will look like, [email protected]:<port>:<user>/<repository> . This just looks ugly.

  2. If you have a way to access the host other than using SSH, You can disable OpenSSH daemon on the host and have Gitea listen on port 22 for SSH traffic. URL will look a bit cleaner with this approach but you can't do this everywhere since in rented servers usually only have SSH access.

  3. You can do what I did! It does have some compromises but it works pretty well and the tradeoffs don't bother me.

My server is a machine rented from Linode. Linode assigns an IPv6 address to the VM but you can ask for up to a /56 from them. I use chunks of this block to ensure the wireguard VPN clients have IPv6 access and to solve this problem, I created an address like <v6-block>:1234:5678:9abc:def0/128 and then assigned this address to the eth0(WAN) interface on the instance by running,

sudo ip -6 addr add <v6-block>:1234:5678:9abc:def0/128 dev eth0

Then, I updated my Gitea config to listen on this address.

SSH_LISTEN_HOST = <v6-block>:1234:5678:9abc:def0/128
SSH_LISTEN_PORT = 22
BUILTIN_SSH_SERVER_USER = git

I run Gitea using systemd unit files. The template systemd config file for this project usually runs as the git or Gitea user and does not have permission to listen on privileged ports. Here I have a few options.

  1. Run the process as root.
  2. Set capabilities on the binary and allow it to listen on privileged ports.
  3. Set capabilities using systemd.

Option 2 & 3 are relatively simple and do not require running the project as root. For my deployment, I chose option 3. To do this open gitea.service and add the capabilities with,

AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE

Reload config with sudo systemctl daemon-reload and restart Gitea with, sudo systemctl restart Gitea. If you still see Permission denied errors for attempting to listen on port 22, Try setting PrivateUsers=false. (See documentation of this flag to understand why you need to do this.)

You can also go with option 2 by using PreStart and PostStop commands to apply and remove capabilities on the binary.

Finally, Add a AAAA record for your domain that is same as the IPv6 address we came up with earlier and that's about it! (Also, Do not use stuff like Cloudflare DNS Proxy for this. They don't proxy SSH Traffic).

And that's about it! You should be able to clone repositories over SSH now.

It does have some downsides like it only works over IPv6 and this will fail if your network does not have IPv6 access but this was not an issue for me. The upside is that the address to clone repositories looks pretty and it pretty much just works.

To make things a bit easier, You can even add a Pre Start command to assign the IPv6 address to the interface since after reboots, That address will not be there.

PS: Another thing I have thought about is that few decades back, SSL/TLS creators must have come across a similar problem. As machines become more powerful, People will want to run multiple sites on the same machine. So, You have to have a way to know which certificates to present to a client because stuff will break if the client is expecting certificate forxyz.com and web server presented certificate for example.com where example.com is also served from the same machine. To fix this problem, They came up with the SNI. Clients can indicate to the server in plain text before the TLS handshake host name of the server they are trying to connect to. The web server can look at this and present the correct certificate. SSH never quite came across the same problem so in SSH Protocol, There is no way to indicate which domain you used to initiate the connection. If something like SNI existed in SSH, You could match on this field and then send traffic to the OpenSSH daemon on the host or Gitea instance.

That's about it for today.