A missing blog post image


Let’s say you wanna deploy a Gitea instance within an unprivileged LXC container, and use the SSH functionality (pretty handy with Git, isn’t it ?).

With a classic configuration, you’ll face an “issue” : The presence of two SSH servers (one for your host, one for the container, mainly used by Gitea).
So let’s see if we could set up an architecture allowing us to forward specific SSH queries to the Gitea container from the SSH server running on the host.

This would let us achieve these objectives :

  • Keep only one instance of SSHd open on Internet (sysadmin laziness) ;

  • Keep only the 22 port open for both use cases (KISS) ;

  • Configure only one instance of Fail2Ban (sysadmin laziness++) ;

  • Keep only one SSHd to harden (Internet is still an untrusted entity for wandering connections).

The procedure

First, in your Gitea container

Just follow the regular Gitea install guide, and add an SSH key to your account once it’s done (Settings > SSH / GPG Keys > Manage SSH Keys).

Now you have two options here :

  • Enable the built-in SSH server of Gitea on a port > 1024 if your instance is running with a regular user (something I really invite you to consider, for security purposes)

  • Use the SSH server shipped within your container (give a try to the ssh.service systemd unit)

Next, on the host

As root :

# Port of the SSH server running within your Gitea container
# IP address of your Gitea container, within your local network

# Prepare the Git system user for remote SSH connections (before forwarding)
adduser --system --shell /bin/bash --gecos "Git Version Control" --disabled-password --home /home/git git
mkdir /home/git/.ssh
chown -R git:nogroup /home/git/.ssh

# Add to the `known_hosts` the public key of the SSH server running within the Gitea container
su - git -c "ssh-keyscan -H -p $SSH_PORT $IP_ADDRESS > /home/git/.ssh/known_hosts 2> /dev/null"
# Generate a keys pair to authenticate the host git session on your Gitea container
su - git -c "ssh-keygen"
su - git -c "cat ~/.ssh/id_*.pub"
## --> Copy the public key generated !

# Mock a fake `/usr/local/bin/gitea` program, forwarding SSH commands to your Gitea container
# (Sorry for this series of ugly shell escapes...)
echo -e "#\041/usr/bin/env bash\nssh -p $SSH_PORT git@$IP_ADDRESS \"SSH_ORIGINAL_COMMAND=\\\"\$SSH_ORIGINAL_COMMAND\\\" \$0 \$@\"" > /usr/local/bin/gitea
chmod +x /usr/local/bin/gitea

# If your system users have to be within the `ssh` group to attempt connections
# (see : <https://samuel.forestier.app/blog/security/hardening-openssh-all-in-one-place>)
usermod -a -G ssh git

Before going back in the Gitea container to polish up the setup, we have to make the host keep the Gitea public identities synchronized within the fake git session (to accept incoming connections).
For this, we cron a specific task (Proxmox 5 example) :

# Replace `XXX` by the id of your container running Gitea
test "$(/usr/sbin/pct status $CONTAINER_ID)" = "status: running" && /usr/sbin/pct pull $CONTAINER_ID --user git --group nogroup /home/git/.ssh/authorized_keys /home/git/.ssh/authorized_keys

Pro tip : You can even deny password authentication for the git user on your host, see below :

echo -e "Match User git\n\tPasswordAuthentication no" >> /etc/ssh/sshd_config
systemctl reload ssh

Finally, back in your Gitea container

Whatever the SSH server you chose before, you only have to add the public key of the host git session (the one you copied previously) to /home/git/.ssh/authorized_keys, as done below :

## Host's git session public key to allow SSH forwarding ##
ssh-rsa AAAA... git@host

# gitea public key
command="/usr/local/bin/gitea serv key-3 --config='/etc/gitea/app.ini'",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAA... gitea-user@user-host
# ...

The idea is to allow SSH connections from your host git session to the container git one.


This post has been mainly inspired from this guide, where the author detailed an interesting setup, with Docker and mounted volumes.
Unfortunately, I couldn’t manage to get something similar working with LXC, mainly due to the difficult aspect of {u,g}ids mapping for mounted endpoints within unprivileged containers…

As always, tell me what you think about this setup, and how it could be improved if you experience(d) a similar situation on your side.