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-hosted Gitea server, as usual, with the best security practices!
Information and requirements
These elements are to be taken into consideration to follow this article:
- The manipulations are carried out on Rocky Linux 8.
- Throughout this post, we will access the Gitea server via its domain name
gitea.local
which is related to its local IP address. - In some snippets of code, the domain name
gitea.domain.fr
must be replaced by the one you assigned to your public Gitea server.
Update the system
sudo dnf -y update
Add the MariaDB repository
Copy the following snippet into /etc/yum.repos.d/MariaDB.repo
.
[mariadb]
name=MariaDB
baseurl=https://yum.mariadb.org/10.6/centos8-amd64/
module_hotfixes=1
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1
Disable MariaDB history file
It’s a good idea to prevent MariaDB from writing a history file. The MariaDB history file contains all the commands you type across all sessions. The default MariaDB history file is ~/.mysql_history
. As this is an obvious security risk, it is a good idea to disable this file.
echo "export MYSQL_HISTFILE=/dev/null" | tee -a ~/.bash_profile
source ~/.bash_profile
This directs your MariaDB history to /dev/null
.
Install MariaDB and required packages
sudo dnf -y install MariaDB-server MariaDB-client git epel-release httpd mod_ssl vim firewalld policycoreutils-python-utils
Enable and start MariaDB service
sudo systemctl enable --now mariadb
Install MariaDB
sudo mariadb-secure-installation
<ENTER>
n
Y
<root password>
<confirm>
Y
Y
Y
Y
Configure database
mariadb -u root -p
Enter password: <root user's password>
Create the user
Create the hash of its password
Choose a really strong password instead of P@ssw0rd
.
MariaDB [(none)]> SELECT PASSWORD("P@ssw0rd");
+-------------------------------------------+
| PASSWORD("P@ssw0rd") |
+-------------------------------------------+
| *8232A1298A49F710DBEE0B330C42EEC825D4190A |
+-------------------------------------------+
1 row in set (0.000 sec)
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 -w 39 | head -n1
Note: /dev/urandom
and /dev/random
are using the exact same CSPRNG (Cryptographically Secure PseudoRandom Number Generator).
Create the user with the hashed password
MariaDB [(none)]> CREATE USER "gitea" IDENTIFIED BY PASSWORD "*8232A1298A49F710DBEE0B330C42EEC825D4190A";
Query OK, 0 rows affected (0.008 sec)
Create the database
MariaDB [(none)]> CREATE DATABASE giteadb CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_unicode_ci';
Query OK, 1 row affected (0.001 sec)
Grant all privileges
MariaDB [(none)]> GRANT ALL PRIVILEGES ON giteadb.* TO 'gitea';
Query OK, 0 rows affected (0.011 sec)
Flush privileges
MariaDB [(none)]> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.001 sec)
Exit
MariaDB [(none)]> EXIT;
Bye
Test the connection
mariadb -u gitea -p owncloud
Enter password: <gitea user's password>
MariaDB [gitea]> EXIT;
Bye
Download the binary
Check the latest release of the file downloaded via cURL
here.
curl -LOsSf https://dl.gitea.io/gitea/1.16.5/gitea-1.16.5-linux-amd64
sudo mv gitea-1.16.5-linux-amd64 /usr/local/bin/gitea
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
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.
sudo sed -i "352i ServerTokens Prod" /etc/httpd/conf/httpd.conf
General TLS configurations
Add these headers and the cache configuration for OCSP stapling just after #SSLCryptoDevice ubsec
into /etc/httpd/conf.d/ssl.conf
. Also, set the Diffie-Hellman parameters’ path and the temporary curve used for ephemeral ECDH modes.
# Headers
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
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)"
# Key exchange
SSLOpenSSLConfCmd DHParameters "/etc/pki/tls/private/dhparam.pem"
SSLOpenSSLConfCmd Curves secp384r1
Next, you must generate the Diffie-Hellman’s parameters. Here, the size of the generated parameters 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
Disable TLS session tickets and enable the addition of OCSP responses to TLS negociation.
sudo sed -i "214i SSLSessionTickets Off" /etc/httpd/conf.d/ssl.conf
sudo sed -i "215i SSLUseStapling On" /etc/httpd/conf.d/ssl.conf
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 secp384r1
but you can use another one by executing openssl ecparam -list_curves
.
sudo openssl ecparam -check -name secp384r1 -genkey -noout -out /etc/pki/tls/private/privkey.pem -rand /dev/urandom
checking elliptic curve parameters: ok
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) []:Auvergne-Rhone-Alpes
Locality Name (eg, city) [Default City]:Lyon
Organizational Name (eg, company) [Default Company Ltd]:Illuad Technologies
Organizational Unit Name (eg, section) []:IT Security
Common Name (eg, your name or your server's hostname) []:gitea.local
Email Address []:contact@gitea.local
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.local:80
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</VirtualHost>
<VirtualHost *:443>
ServerName gitea.local:443
ServerAlias gitea.local
ServerAdmin contact@gitea.local
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
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.local/install
, and follow these steps:
- Fill the
Password
field with the password created earlier (gitea user’s password). - Fill the
Database Name
field withgiteadb
. - Fill the
Charset
field withutf8mb4
. - Fill the
Site Title
field with the title of your choice. - Fill the
SSH Server Domain
field with the domain name (without the protocol), for me it’sgitea.local
. - Fill the
Gitea base URL
field with the domain name (with the protocol), for me it’shttps://gitea.local
.
In the Optional Settings
:
- Expand the
Email Settings
section and fill-in the fields if you want to enable e-mail service. - Expand the
Server and Third-Party Service Settings
section and check the features you want to use, uncheck the ones you are not going to use. - Expand the
Administrator Account Settings
section and fill-in the fields to create the administrator user. - Click on
Install Gitea
to install Gitea.
Please note that all values can be changed later on the configuration file.
After installation is done, it is recommended to set rights to read-only.
sudo chmod 750 /etc/gitea
sudo chmod 640 /etc/gitea/app.ini
Configure git
Once you’ve created a standard user on Gitea (e.g. John Doe <john.doe@gitea.local>
), 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.local
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.local -C git
ssh-add ~/.ssh/gitea.local
Finally, copy the following snippet into ~/.ssh/config
. Make sure to use the user git
.
Host gitea.local
Hostname gitea.local
User git
Port 22
IdentityFile ~/.ssh/gitea.local
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
Backup
I don’t bother to explain the usefulness of a backup, so here is how to make one.
sudo su - git -c "/usr/local/bin/gitea dump --verbose --skip-custom-dir --skip-log --config /etc/gitea/app.ini --work-path /home/git"
[...]
2021/09/30 14:46:47 cmd/dump.go:264:runDump() [I] Dumping database...
2021/09/30 14:46:47 cmd/dump.go:31:addFile() [I] Adding file gitea-db.sql
2021/09/30 14:46:47 cmd/dump.go:276:runDump() [I] Adding custom configuration file from /etc/gitea/app.ini
2021/09/30 14:46:47 cmd/dump.go:31:addFile() [I] Adding file app.ini
2021/09/30 14:46:47 cmd/dump.go:283:runDump() [I] Skipping custom directory
2021/09/30 14:46:47 cmd/dump.go:304:runDump() [I] Packing data directory.../home/git/data
2021/09/30 14:46:47 cmd/dump.go:349:runDump() [I] Skip dumping log files
2021/09/30 14:23:11 cmd/dump.go:374:runDump() [I] Finish dumping in file gitea-dump-1633005981.zip
As defined by the --work-path
flag, the backup is in the /home/git
directory. You can now retrieve it locally or move it to another server by encrypting the archive (which I advise you to do).
Restore
If you want to restore a backup, it is quite simple. First, stop Gitea and extract the archive.
sudo systemctl stop gitea
unzip -q gitea-dump-1633005981.zip
Check its content.
ls
app.ini data gitea-db.sql gitea-dump-1633005981.zip repos
Once you have extracted the archive, you just have to move the files or folders to the proper locations.
sudo rm -rf /var/lib/gitea/data
sudo mv app.ini /etc/gitea/app.ini
sudo mv data/ /var/lib/gitea/
sudo mv repos/ /var/lib/gitea/data/gitea-repositories/
sudo chown -R git:git /var/lib/gitea/data
Lastly, for the database, you have to delete the current database and recreate it. Start by listing the existing databases.
mariadb -uroot -p -e "SHOW DATABASES;"
Enter password: <root password>
+--------------------+
| Database |
+--------------------+
| giteadb |
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
Drop the giteadb
database.
mariadb -uroot -p -e "DROP DATABASE giteadb;"
Enter password: <root password>
Recreate the database.
mariadb -uroot -p -e "CREATE DATABASE giteadb CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_unicode_ci'; GRANT ALL PRIVILEGES ON giteadb.* TO 'gitea'; FLUSH PRIVILEGES;"
Enter password: <root password>
Check to see if you’ve done your job right.
mariadb -uroot -p -e "SHOW CREATE DATABASE giteadb;"
Enter password:
+----------+------------------------------------------------------------------------------------------------+
| Database | Create Database |
+----------+------------------------------------------------------------------------------------------------+
| giteadb | CREATE DATABASE `giteadb` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */ |
+----------+------------------------------------------------------------------------------------------------+
Now, you can restore your database and start Gitea.
mariadb --default-character-set=utf8mb4 --binary-mode -o -ugitea -p giteadb < gitea-db.sql
Enter password: <gitea password>
sudo systemctl start gitea
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
Disable footer information
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
Set log mode
I advise you to activate the logs to be able to use Fail2ban correctly.
sudo sed -i "s/MODE = console/MODE = file/" /etc/gitea/app.ini
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/gitea.log
sudo restorecon -Rv /var/lib/gitea/log/gitea.log
Start the Fail2ban
server and enable it at boot.
sudo systemctl enable --now fail2ban
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>
Setup logs rotation
sudo dnf -y install logrotate
Copy the following snippet into /etc/logrotate.d/gitea
.
/var/lib/gitea/log/gitea.log {
missingok
notifempty
compress
weekly
rotate 7
delaycompress
create 0644 git git
}
This configuration file contains:
missingok
: if the log file is missing, go on to the next one without issuing an error message,notifempty
: do not rotate the log if it is empty,compress
: old versions of log files are compressed withGzip
,weekly
: log files are rotated if the current weekday is less than the weekday of the last rotation or if more than a week has passed since the last rotation,rotate 7
: log files are rotated count times before being removed,delaycompress
: postpone compression of the previous log file to the next rotation cycle. It can be used when some program cannot be told to close its logfile and thus might continue writing to the previous log file for some time,create 0644 git git
: immediately after rotation, the log file is created with the same name as the log file just rotated.
You can execute a dry-run to see what logrotate would do if it was actually executed now.
sudo logrotate -d /etc/logrotate.d/gitea
Finally, logrotate creates an entry in /etc/cron.daily/logrotate
to execute the configured tasks.
We just finished working out how to set up Gitea. You can now do what you are used to do on GitHub!