Install a Gitea server

Wishing to detach myself from GAFAM, I have left GitHub without looking for an equivalent for a long time. Recently, the desire to display my repositories in a sexy way (and other) has risen to a breathtaking speed in my personal TODO. I had already heard about cgit, gitweb… So I tried those solutions and the sexy side was missing (although they’re very interesting). I told my browser to look for the “best alternative github”, the first result was Gitea.

Gitea is a fork of Gogs (the great painless self-hosted Git service) and it looks very nice! In this article, we are going to see how to deploy a self-host Gitea server, as usual, with the best security practices!

Information and requirements

These elements are to be taken into consideration to follow this article:

Update the system

sudo dnf -y update

Add the MariaDB repository

Copy the following snippet into /etc/yum.repos.d/MariaDB.repo.

# MariaDB 10.5 CentOS repository list - created 2021-01-01 18:12 UTC
# http://downloads.mariadb.org/mariadb/repositories/
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.5/centos8-amd64
module_hotfixes=1
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1

Install MariaDB and tools

sudo dnf -y install MariaDB-server MariaDB-client git epel-release httpd mod_ssl vim firewalld

Enable and start the service

sudo systemctl enable --now mariadb.service

Install MariaDB

sudo mysql_secure_installation
<ENTER>
n
Y
<root password>
<confirm>
Y
Y
Y
Y

Configure database

Choose a really strong password instead of P@ssw0rd.

mysql -u root -p
<root password>
SET old_passwords=0;
CREATE USER 'gitea' IDENTIFIED BY 'P@ssw0rd';
CREATE DATABASE giteadb CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_unicode_ci';
GRANT ALL PRIVILEGES ON giteadb.* TO 'gitea';
FLUSH PRIVILEGES;
EXIT;

You can generate a strong password (here 40 characters but you can increase its size) with the following statement.

tr -cd '[:alnum:]' < /dev/urandom | fold -w40 | head -n1

Note: /dev/urandom and /dev/random are using the exact same CSPRNG (Cryptographically Secure PseudoRandom Number Generator).

Test the connexion

mysql -u gitea -p giteadb
<gitea's password>
EXIT;

Download the binary

Check the latest release of the file downloaded via cURL here.

sudo curl -Lo /usr/local/bin/gitea -sSf https://dl.gitea.io/gitea/1.13.1/gitea-1.13.1-linux-amd64
sudo chmod +x /usr/local/bin/gitea

Create user to run Gitea server

sudo adduser --system --shell /bin/bash --comment "Gitea server" --user-group -m git

Create the directory structure

sudo mkdir -p /var/lib/gitea/{custom,data,log}
sudo chown -R git:git /var/lib/gitea/
sudo chmod -R 750 /var/lib/gitea/
sudo mkdir /etc/gitea
sudo chown root:git /etc/gitea
sudo chmod 770 /etc/gitea

Create the service file

Copy the following snippet into /etc/systemd/system/gitea.service.

[Unit]
Description=Gitea server
After=network.target
After=mariadb.service

[Service]
LimitMEMLOCK=infinity
LimitNOFILE=65535
LimitNPROC=64
RestartSec=2s
Type=simple
User=git
Group=git
WorkingDirectory=/var/lib/gitea/
RuntimeDirectory=gitea
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
Restart=always
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea
PrivateTmp=true
PrivateDevices=true
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target

Restart systemctl daemon

sudo systemctl daemon-reload

Enable and start Gitea service

sudo systemctl enable --now gitea.service

SELinux stuff

If SELinux is enabled Gitea will not start. You must change the SELinux content of the binary.

sudo semanage fcontext -a -t bin_t /usr/local/bin/gitea
sudo restorecon -v /usr/local/bin/gitea

You’re still not quite done with SELinux, you must allow HTTPD scripts and modules to connect to the network.

sudo setsebool -P httpd_can_network_connect on

The -P flag allows to make this modification persistent even after a reboot.

Reverse proxy configurations

General configurations

By adding ServerTokens Prod directive, you remove the Apache2 version from HTTP headers, it’s a good practice for security reasons.

echo "ServerTokens Prod" | sudo tee -a /etc/httpd/conf/httpd.conf

General TLS configurations

Add these headers and the cache configuration for OCSP stapling just before the default virtual host (<VirtualHost _default_:443>) into /etc/httpd/conf.d/ssl.conf.

# Headers
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
Header always set X-Frame-Options DENY
Header always set X-Content-Type-Options nosniff
Header always set Referrer-Policy no-referrer

# OCSP stapling
SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"

Set the Diffie-Hellman parameters. First, you must generate the parameters. Here, the size of the generated parameter set will be 4096 bits.

sudo openssl dhparam -out /etc/pki/tls/private/dhparam.pem 4096
sudo chmod 440 /etc/pki/tls/private/dhparam.pem

Then, specify to Apache2 where the file is located via the DHParameters directive.

# Key exchange
SSLOpenSSLConfCmd DHParameters "/etc/pki/tls/private/dhparam.pem"

Disable TLS session tickets and enable the addition of OCSP responses to TLS negociation just before the end of the virtual host (</VirtualHost>) into /etc/httpd/conf.d/ssl.conf.

SSLSessionTickets Off
SSLUseStapling On

Accept all protocols except those before TLSv1.2.

sudo sed -i "s/#SSLProtocol.*/SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1/" /etc/httpd/conf.d/ssl.conf

Generate a self-signed certificate (local server only)

Generate a private key for a curve. Here, I use the curve secp521r1 but you can use another one by executing openssl ecparam -list_curves.

sudo openssl ecparam -check -name secp521r1 -genkey -noout -out /etc/pki/tls/private/privkey.pem -rand /dev/urandom
checking elliptic curve parameters: ok

You can set the temporary curve used for ephemeral ECDH modes. You can use the same curve you just used to generate the private key. Add the following statement juste after the SSLOpenSSLConfCmd DHParameters directive into /etc/httpd/conf.d/ssl.conf.

SSLOpenSSLConfCmd ECDHParameters secp521r1

Create a self-signed certificate. Feel free to personalize the answers to the questions asked. This self-signed certificate is used for testing purposes so it is not essential that the information given is correct.

sudo openssl req -utf8 -new -x509 -key /etc/pki/tls/private/privkey.pem -out /etc/pki/tls/certs/cert.pem -days 365 -rand /dev/urandom
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letters code) [XX]:FR
State or Province Name (full name) []:Rhone-Alpes
Locality Name (eg, city) [Default City]:Lyon
Organizational Name (eg, company) [Default Company Ltd]:Gitea
Organizational Unit Name (eg, section) []:IT Department
Common Name (eg, your name or your server's hostname) []:gitea.lan
Email Address []:contact@gitea.lan

Set the correct permission.

sudo chmod 440 /etc/pki/tls/private/privkey.pem
sudo chmod 644 /etc/pki/tls/certs/cert.pem

Then, create the reverse proxy configuration into /etc/httpd/conf.d/vhost.conf.

<VirtualHost *:80>
    ServerName gitea.lan:80
    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</VirtualHost>

<VirtualHost *:443>
    ServerName gitea.lan:443
    ServerAlias gitea.lan
    ServerAdmin contact@gitea.lan

    SSLCertificateFile /etc/pki/tls/certs/cert.pem
    SSLCertificateKeyFile /etc/pki/tls/private/privkey.pem
    SSLCACertificateFile /etc/pki/tls/certs/cert.pem

    Protocols h2 http/1.1

    ErrorLog /var/log/gitea/error_log
    CustomLog /var/log/gitea/access_log combined

    ProxyPreserveHost On
    ProxyRequests Off
    AllowEncodedSlashes NoDecode
    ProxyPass / http://localhost:3000/ nocanon
    ProxyPassReverse / http://localhost:3000/
</VirtualHost>

Request a Let’s Encrypt certificate (public server only)

In case you wish to obtain free certificates (via Let’s Encrypt), two methods are available to you. Luck is on your side, you will find here the method to obtain a certificate from Let’s Encrypt, and here a slightly more advanced method that allows you to obtain a certificate based on elliptic curve cryptography (still via Let’s Encrypt).

The reverse proxy configuration is the exact same. Things you have to modify are the location of the certificate, private key and the chain of trust, but it is documented in the article previously mentionned.

Create the logs directory

sudo mkdir -p /var/log/gitea

Enable and start HTTPD service

sudo systemctl enable --now httpd.service

Modify authorized ports

sudo firewall-cmd --add-port={80/tcp,443/tcp} --permanent
sudo firewall-cmd --reload

Finish installation

Open your web browser and go to the domain that you’ve defined, for me it’s https://gitea.lan/install, and follow these steps:

In the Optional Settings:

Please note that all values can be changed later on the configuration file.

Configure git

Once you’ve created a standard user on Gitea (e.g. John Doe <john.doe@gitea.lan>), you want to be able to push your commits, tags and more with the git command. Here’s how to do it.

First make sure that the basic configuration is done: user.name and user.email must be defined.

git config --global user.name "John Doe"
git config --global user.email john.doe@gitea.lan

Next, generate a SSH key pair to authenticate yourself and add the private key into the SSH agent.

ssh-keygen -t ed25519 -a 100 -f ~/.ssh/gitea.lan -C git
ssh-add ~/.ssh/gitea.lan

Finally, copy the following snippet into ~/.ssh/config. Make sure to use the user git.

Host	gitea.lan
	Hostname gitea.lan
	User git
	Port 22
	IdentityFile ~/.ssh/gitea.lan

Remember to import the public key (~/.ssh/gitea.pub) into your Gitea account.

Now, as soon as you perform actions with the git command (clone, commit…) the passphrase defined at the moment of the SSH key pair generation will be asked for instead of your username-password pair.

Recommendations

Update Gitea

You will find here a script I wrote to update the Gitea binary. The steps of implementation are explained in the README.md file.

Disable Swagger UI

Gitea uses Swagger to manage the API. Swagger is shipped with an (pretty cool) interface that lists the available endpoints. Obviously, you can disable this interface. It’s not really an additional measure of security because endpoints are easily found on the Internet. Add the following snippet into /etc/gitea/app.ini.

[api]
ENABLE_SWAGGER = false

Gitea can display some information about time of template execution, Gitea and Go version, it’s a good idea to hide this kind of stuff.

[other]
SHOW_FOOTER_VERSION		= false
SHOW_FOOTER_TEMPLATE_LOAD_TIME	= false

Protection against brute-force with Fail2ban

sudo dnf -y install fail2ban-server fail2ban-firewalld

First, we want to ban brute-force user login attempts. Copy the following snippet into /etc/fail2ban/filter.d/gitea.conf.

[INCLUDES]
before = common.conf

[Definition]
failregex = .*Failed authentication attempt for .* from <ADDR>
ignoreregex =

Create the jail by copying the following snippet into /etc/fail2ban/jail.d/gitea.local.

[gitea]
enabled = true
port = 80,443
filter = gitea
action = iptables-allports[name=gitea]
logpath = /var/lib/gitea/log/gitea.log
maxretry = 3
bantime = 86400
findtime = 3600

Once again, if SELinux is enabled Fail2ban will not start. You must change the log folder’s SELinux context.

sudo semanage fcontext -a -t var_log_t /var/lib/gitea/log/
sudo restorecon -Rv /var/lib/gitea/log/

Start the Fail2ban server and enable it at boot.

sudo systemctl enable --now fail2ban.service

Check the login attempts jail.

sudo fail2ban-client status gitea
Status for the jail: gitea
|- Filter
|  |- Currently failed: 0
|  |- Total failed:	0
|  `- File list:	/var/lib/gitea/log/gitea.log
|
`- Actions
   |- Currently banned:	0
   |- Total banned:	0
   `- Banned IP list:

You can try to log in with bad credentials and execute a new time the previous command, the Currently failed and Total failed values will increment. Don’t abuse, you’ll be block otherwise!

Finally, you can unban an IP with the following command.

sudo fail2ban-client set gitea unbanip <IP>

We just finished working out how to set up Gitea. You can now do what you are used to do on GitHub!