Install an ownCloud server

ownCloud is a platform that acts as a cloud storage. You can freely deploy your own because ownCloud is open source. Through this article we will see how to deploy a hardened ownCloud instance. To do this we will use full SELinux support (and not disable it as everyone else does), use sockets for Redis and MariaDB instead of TCP ports.

It is essential to have a highly secure instance because you will potentially be uploading sensitive or confidential files. I would be surprised if you don’t have a problem if these files leak.

Information and requirements

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

Update the system

sudo dnf -y update

Install PHP

Install Remi’s RPM repository

sudo dnf -y install https://rpms.remirepo.net/enterprise/remi-release-8.rpm

Enable PHP 7.4 module

sudo dnf -y module enable php:remi-7.4

Install required packages

They are all necessary for the smooth running of the installation.

sudo dnf -y install bzip2 httpd firewalld vim mod_ssl redis php php-common php-pdo unzip php-pecl-zip php-xml php-intl php-mbstring php-gd php-mysqlnd php-process php-pecl-redis5 php-pecl-apcu policycoreutils-python-utils setroubleshoot-server

Add the MariaDB repository

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

[mariadb]
name = MariaDB
baseurl = https://ftp.igh.cnrs.fr/pub/mariadb/yum/10.6/centos8-amd64
module_hotfixes=1
gpgkey=https://ftp.igh.cnrs.fr/pub/mariadb/yum/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

sudo dnf -y install MariaDB-server MariaDB-client

Disable the InnoDB read only compressed option and disable TCP/IP networking options

Add the following option into /etc/my.cnf.d/server.cnf

[mariadb-10.6]
innodb_read_only_compressed=0
skip-networking

From MariaDB 10.6.0, tables that are of the COMPRESSED row format are read-only by default. This is the first step towards removing write support and deprecating the feature. Set the innodb_read_only_compressed variable to off (0) to make the tables writable.

Lastly, skip-networking is fairly simple, it just tells MariaDB to run without any of the TCP/IP networking options.

Enable and start MariaDB service

sudo systemctl enable --now mariadb.service

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 "owncloud" IDENTIFIED BY PASSWORD "*8232A1298A49F710DBEE0B330C42EEC825D4190A";
Query OK, 0 rows affected (0.008 sec)

Create the database

MariaDB [(none)]> CREATE DATABASE owncloud;
Query OK, 1 row affected (0.001 sec)

Grant all privileges

MariaDB [(none)]> GRANT ALL PRIVILEGES ON owncloud.* TO "owncloud";
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 owncloud -p owncloud
Enter password: <owncloud user's password>
MariaDB [owncloud]> EXIT;
Bye

HTTPD 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

Any valid directive in the .htaccess context will be allowed in .htaccess files.

sudo sed -i "128s/AllowOverride None/AllowOverride All/" /etc/httpd/conf/httpd.conf

If you want to access to ownCloud server without adding /owncloud at the end of the domain name, you should do the following thing.

sudo sed -i "123i Alias \"/\" \"/var/www/owncloud/\"" /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) []:owncloud.local
Email Address []:contact@owncloud.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 virtual host configuration into /etc/httpd/conf.d/vhost.conf.

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

<VirtualHost *:443>
    ServerName owncloud.local:443
    ServerAlias owncloud.local
    ServerAdmin contact@owncloud.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/owncloud/error_log
    CustomLog /var/log/owncloud/access_log combined
</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/owncloud

Edit the PHP configuration

By modifying this line, you remove the PHP version from HTTP headers, it’s a good practice for security reasons.

sudo sed -i "s/expose_php = On/expose_php = Off/" /etc/php.ini

Download ownCloud

You can check the latest version here.

curl -Lo owncloud-complete-20210721.tar.bz2 -sSf https://download.owncloud.org/community/owncloud-complete-20210721.tar.bz2

Extract ownCloud’s files

sudo tar -jxf owncloud-complete-20210721.tar.bz2 -C /var/www
rm -f owncloud-complete-20210721.tar.bz2

Set rights

sudo chown -R apache:apache /var/www/owncloud

Edit the Redis configuration

Configure a Redis password enables one of its two built-in security features, the AUTH command, which requires clients to authenticate to access the database. With this command, you generate a random password with a size of 150 characters and insert it in the Redis’ configuration file.

sudo sed -i "s/# requirepass foobared/requirepass $(tr -cd "[:alnum:]" < /dev/urandom | fold -w 149 | head -n1 | sed "s/ .*$//")/" /etc/redis.conf

Unix sockets operate at a lower level OSI model layer than TCP sockets so they should be faster. Moreover, it adds an extra level of security.

sudo sed -i "s/# unixsocket \/tmp\/redis.sock/unixsocket \/run\/redis\/redis.sock/" /etc/redis.conf

Set the correction permission for the socket file.

sudo sed -i "s/# unixsocketperm 700/unixsocketperm 770/" /etc/redis.conf

If port 0 is specified Redis will not listen on a TCP socket, so it will not be possible to exchange with it via its port. This is not a problem because we have just seen that we will communicate with it via a socket.

sudo sed -i "s/port 6379/port 0/" /etc/redis.conf

Group modification

Add the user apache to the group redis to avoid permission issue regarding the socket I/O. In order to apply group modification, I advise you to reboot you server.

sudo gpasswd -a apache redis
sudo reboot

Enable and start Redis service

sudo systemctl enable --now redis.service

Check if Redis is listening

As you can see, port 6379 is no longer in listening mode.

ss -ltup | grep redis

Flush Redis database

Make sure to put the generated Redis password after the AUTH keyword. You can retrieve it with this command: sudo grep ^requirepass /etc/redis.conf.

sudo redis-cli -s /run/redis/redis.sock
AUTH cEoQNyT5R...
SELECT 0
FLUSHDB
QUIT

Set the SELinux context

sudo semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/owncloud/config(/.*)?"
sudo semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/owncloud/apps(/.*)?"
sudo semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/owncloud/apps-external(/.*)?"
sudo semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/owncloud/data(/.*)?"
sudo semanage fcontext -a -t httpd_sys_rw_content_t /var/www/owncloud/.htaccess
sudo semanage fcontext -a -t httpd_sys_rw_content_t /var/www/owncloud/.user.ini
sudo restorecon -Rv /var/www/owncloud/

Install ownCloud

You can simply install your ownCloud instance by running the following command. Note that the administrator user will be named by the value of the --admin-user flag. Change the values of the different flags to fit you your needed.

You will be asked for the owncloud user and administrator passwords.

sudo -u apache php /var/www/owncloud/occ maintenance:install --database "mysql" --database-name "owncloud" --database-user "owncloud" --admin-user "root" --database-host "localhost:/var/lib/mysql/mysql.sock"
What is the password to access the database with user <owncloud>? <owncloud user's password>
What is the password you like to use for the admin account <root>? <administrator user's password>
ownCloud was successfully installed

Add the ownCloud URL inside the trusted domains

Add the ownCloud instance URL inside the trusted_domains array to be able to access it, otherwise you will get an error.

sudo -u apache php /var/www/owncloud/occ config:system:set trusted_domains 0 --value owncloud.local
System config value trusted_domains => 0 set to string owncloud.local

Configure Redis

Add the following content at the end of /var/www/owncloud/config/config.php and make sure to put the generated Redis password. You can retrieve it with this command: sudo grep ^requirepass /etc/redis.conf.

'memcache.local' => '\OC\Memcache\Redis',
'memcache.locking' => '\OC\Memcache\Redis',
'redis' =>
array(
        'host' => '/run/redis/redis.sock',
	'port' => 0,
	'timeout' => 0,
	'password' => 'cEoQNyT5R...',
	'dbindex' => 0
)

SELinux configurations

Because Redis is configured to listen on a Unix socket instead of a TCP/IP port (which is recommended if Redis is running on the same system as ownCloud) we must instruct SELinux to allow daemons to enable cluster mode.

sudo setsebool -P daemons_enable_cluster_mode on

This boolean allows httpd_t complete access to all of the httpd types (that is to execute, read, or write sys_content_t). Enable this boolean to allow this access.

sudo setsebool -P httpd_unified on

When disabled, this boolean prevents HTTP scripts and modules from initiating a connection to a network or remote port. Enable this boolean to allow this access.

sudo setsebool -P httpd_can_network_connect on

Modify authorized ports

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

Enable and start HTTPD service

sudo systemctl enable --now httpd.service

Check if it works

If you navigate to the URL of your ownCloud instance, you will face an Internal Server Error. This error comes from SELinux. By default, SELinux is preventing /usr/sbin/php-fpm from write access on the socket file redis.sock. Generate a local policy module to allow this access.

sudo ausearch -c 'php-fpm' --raw | audit2allow -M phpfpm-redis
sudo semodule -i phpfpm-redis.pp
sudo rm -f phpfpm-redis.*

Note that the creation of the local policy module will only be possible if you have already navigated to the URL: you need a SELinux error to generate it.

Configure ownCloud cron job

sudo -u apache php /var/www/owncloud/occ background:cron
Set mode for background jobs to 'cron'

Add the cron job for apache user by executing this command: sudo -u apache crontab -e, then add this cron job.

*/15 * * * * /usr/bin/php /var/www/owncloud/occ system:cron

Add user

You can manage many things with the occ command, please visit the documentation to learn more.

sudo -u apache php /var/www/owncloud/occ user:add --display-name="John Doe" jdoe
Enter password: <John's password>
Confirm password: <John's password again>
The user "jdoe" was created successfully
Display name set to "John Doe"

Enable server-side encryption

ownCloud includes an encryption application. When enabled, all of your ownCloud data files are automatically encrypted. Encryption is server-wide, so when it is enabled you cannot choose to keep your files unencrypted. You don’t have to do anything special, as it uses your ownCloud login as the password for your unique private encryption key. Just log in and out and manage and share your files as you normally do, and you can still change your password whenever you want. Learn more here.

Enable the encryption application

sudo -u apache php /var/www/owncloud/occ app:enable encryption
encryption enabled

Enable the the encryption

Once enabled this module will encrypt all your files transparently. The encryption is based on AES 256 keys.

sudo -u apache php /var/www/owncloud/occ encryption:enable
Encryption enabled

Default module: OC_DEFAULT_MODULE

Set encryption type to user-keys. This means that each user will have his own key to cipher and decipher his data, which is more secure.

sudo -u apache php /var/www/owncloud/occ encryption:select-encryption-type user-keys
Warning: Only available for fresh installations with no existing encrypted data! There is also no way to disable it again. Do you want to continue? (y/n) y
User key successfully enabled.

Note: In the encryption page (https://owncloud.local/index.php/settings/admin?sectionid=encryption), you can set a recovery key password. The recovery key is an extra encryption key that is used to encrypt files. It allows recovery of a user’s files if the user forgets his or her password. Again, read the documentation here.

Recommendations

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/owncloud.conf.

[INCLUDES]
before = common.conf

[Definition]
failregex = {.*Login failed: \'.*\' \(Remote IP: \'<ADDR>\'\)"}
ignoreregex =

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

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

Start the Fail2ban server and enable it at boot.

sudo systemctl enable --now fail2ban.service

At this moment, SELinux is preventing /usr/libexec/platform-python3.6 from getattr access on the file /var/www/owncloud/data/owncloud.log. To overcome this error, you have to generate a local policy module to allow this access.

sudo ausearch -c 'fail2ban-server' --raw | audit2allow -M fail2ban-getattr-owncloud-log
sudo semodule -i fail2ban-getattr-owncloud-log.pp
sudo rm -f fail2ban-getattr-owncloud-log.*

Restart the Fail2ban server.

sudo systemctl restart fail2ban.service

Now, SELinux is preventing /usr/libexec/platform-python3.6 from read access on the file owncloud.log. You have to generate a local policy module to allow this access.

sudo ausearch -c 'fail2ban-server' --raw | audit2allow -M fail2ban-read-owncloud-log
sudo semodule -i fail2ban-read-owncloud-log.pp
sudo rm -f fail2ban-read-owncloud-log.*

Restart the Fail2ban server.

sudo systemctl restart fail2ban.service

Lastly, SELinux is preventing /usr/libexec/platform-python3.6 from open access on the file /var/www/owncloud/data/owncloud.log. Without much surprise, you have to generate a local policy module to allow this access.

sudo ausearch -c 'fail2ban-server' --raw | audit2allow -M fail2ban-open-owncloud-log
sudo semodule -i fail2ban-open-owncloud-log.pp
sudo rm -f fail2ban-open-owncloud-log.*

Restart the Fail2ban server.

sudo systemctl restart fail2ban.service

Check the login attempts jail.

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

You can try to log in with bad credentials and executed 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 owncloud unbanip <IP>

Delete skeleton directories and files

For each added user, two directories and a file will be created, you can delete the following directory to avoid this behavior.

sudo rm -rf /var/www/owncloud/core/skeleton/

Backing up data

Create the folder that will receive the backups.

sudo mkdir -p /var/local/owncloud/backups

Copy the following snippet into /usr/local/sbin/ocbackup. Change <owncloud user's password> by the password you have defined to the owncloud user. Because we use the short password flag -p, you cannot have a space between the flag and the password.

#! /usr/bin/env bash

backuptime=$(date +%Y-%m-%d)
dir=/var/local/owncloud/backups/$backuptime
mkdir $dir
rsync -Aax /var/www/owncloud/data/ $dir/data
rsync -Aax /var/www/owncloud/config/ $dir/config
mariadb-dump --single-transaction -S /var/lib/mysql/mysql.sock -u owncloud -p<owncloud user's password> owncloud > $dir/database.sql
tar -cf $dir.tar.gz --absolute-names $dir
rm -rf $dir

Make it executable.

sudo chmod +x /usr/local/sbin/ocbackup

Then, add these entries into the root user’s crontab via sudo crontab -e.

0 4 * * * /usr/local/sbin/ocbackup
0 6 * * * /usr/bin/find /var/local/owncloud/backups/ -type f -name '*.tar.gz' -mtime +1 -delete

Each day, at 4 a.m., the script will save data in a directory (format: Y-m-D) into /var/local/owncloud/backups. Then, each day at 6 a.m. all backup older than 4 days will be deleted. With this configuration, data can be retrieved in n-4 days. Increase this number to have more days of retention.

Configure e-mail server

The section named General in the Admin panel allows you to define the mail server settings you want to use.

Your ownCloud server installation is now finished! You can see that all security and setup configuration checks are successfully passed in the section named General in the Admin panel.