captainarkdotnet/content/webdav-nginx.md

358 lines
11 KiB
Markdown

Title: WebDAV with nginx
Date: 2016-03-26
Category: Tutorial
This website has been hosted on an [Online.net](https://www.online.net) dedicated server since its creation. I've been one of their customers for the past 3 years now, and I still don't have anything bad to say about them.
They recently upgraded their personnal range, and I took the opportunity to upgrade from a single server running all of my services to 2 servers running LXC containers that are hosting my services.
It took me 2 days to migrate everything, but it was worth it. If I decide to switch servers again, I'll have to migrate the containers instead of the services themselves. Considering they are stored on a separate BTRFS volume, it shouldn't take me more than a few hours at most.
During the migration, I realized that I needed to make files that were hosted on one server accessible to the other. I could have gone with CIFS or NFS, but I wanted to have encryption built-in instead of having to rely on a VPN for that. Since I figured it was a good opportunity to learn something new, I ended up going with WebDAV.
In this tutorial, I'll explain how I've configured a read-only WebDAV share using [nginx](https://www.nginx.com/) and [Let'sEncrypt](https://letsencrypt.org/) SSL certificates between two Debian Jessie containers.
## Server configuration
### Installing the required packages
First thing first, we need to install the packages we'll need for this configuration :
```bash
apt update
apt -t jessie-backports install nginx letsencrypt
apt install apache2-utils
```
### Getting our first certificate from letsencrypt
#### letsencrypt configuration
Let's create a configuration file for letsencrypt :
```bash
mkdir /etc/letsencrypt
echo 'rsa-key-size = 3072
renew-by-default
text = True
agree-tos = True
renew-by-default = True
authenticator = webroot
email = admin@example.com
webroot-path = /var/www/letsencrypt/' > /etc/letsencrypt/cli.ini
```
*Please do modify admin@example.com by your actual e-mail address.*
We also need to create the directory structure where letsencrypt ACME challenge temporary files will be stored :
```
mkdir -p /var/www/letsencrypt/.well-known
```
#### nginx configuration
We now need to configure nginx by adding the following in the `/etc/nginx/sites-available/default` file, anywhere in the `server{}` block that is configured to listen on port 80.
```
location /.well-known/acme-challenge {
root /var/www/letsencrypt;
}
```
Let's make sure that we haven't done anything wrong :
```bash
nginx -t
```
The command should give you the following output :
```
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
```
If that's the case, you can safely reload the nginx daemon :
```
nginx -s reload
```
#### Certificate request
Now that letsencrypt and nginx are properly configured, we can request our certificate from letsencrypt :
```bash
letsencrypt --config /etc/letsencrypt/cli.ini certonly -w /var/www/letsencrypt -d www.example.com
```
*Please do modify www.example.com by your server's FQDN, and please note that the letsencrypt servers need to be able to resolve that name to your server's IP.*
If everything goes well, your certificates will be generated and stored in the /etc/letsencrypt folder.
### WebDAV configuration
Now that we've obtained our certificate from letsencrypt, we can begin configuring nginx.
First, we need to comment two SSL directives from the default nginx configuration :
```
sed -i '/ssl_/ s/^/#/' /etc/nginx/nginx.conf
```
Let's now create a `/etc/nginx/conf.d/ssl.conf` with the following content :
```
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
ssl_prefer_server_ciphers on;
add_header Strict-Transport-Security max-age=15768000;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
ssl_stapling on;
ssl_stapling_verify on;
resolver 127.0.0.1 valid=300s;
resolver_timeout 5s;
```
*This configuration will work if you're using a single certificate on your server. If not, you'll have to remove the `ssl_certificate`, `ssl_certificate_key` and `ssl_trusted_certificate` directives from this file and move them to the correct `server{}` block.*
We now need to generate a `dhparam.pem` file :
```bash
mkdir /etc/nginx/ssl && chmod 700 /etc/nginx/ssl
openssl dhparam -out /etc/nginx/ssl/dhparam.pem 3072
chmod 600 /etc/nginx/ssl/dhparam.pem
```
Let's now generate a HTTP basic authentication file. This example creates a user named example :
```
mkdir /etc/nginx/auth
htpasswd -c /etc/nginx/auth/webdav example
New password:
Re-type new password:
Adding password for user user
```
This file has to be readable by the user running your webserver. For security reasons, we'll make it readable only by him :
```
chown -R www-data:nogroup /etc/nginx/auth
chmod 700 /etc/nginx/auth
chmod 400 /etc/nginx/auth/webdav
```
Let's now modify our `/etc/nginx/sites-available/default` file with the following content :
```
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
server_name "";
return 444;
}
server {
listen 443 default_server ssl http2;
listen [::]:443 default_server ipv6only=on ssl http2;
server_name "";
return 444;
}
```
We now have to create a `/etc/nginx/sites-available/example` file that will contain our actual webdav configuration. This example makes a `data` folder stored in `/var/www/` accessible.
```
server {
listen 80;
listen [::]:80;
server_name www.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name www.example.com;
root /var/www;
location / {
index index.html;
}
location /.well-known/acme-challenge {
root /var/www/letsencrypt;
}
location /data {
client_body_temp_path /tmp;
dav_methods PUT DELETE MKCOL COPY MOVE;
dav_ext_methods PROPFIND OPTIONS;
create_full_put_path on;
dav_access user:r group:r;
auth_basic "Restricted access";
auth_basic_user_file auth/webdav;
limit_except GET {
allow <YOUR IP HERE>;
deny all;
}
}
}
```
The last thing we have to do is to create a symlink so that nginx will load our configuration :
```
ln -s /etc/nginx/sites-available/example /etc/nginx/sites-enabled/example
```
Like before, let's make sure our configuration is correct and then reload the daemon :
```
nginx -t
nginx -s reload
```
That's it for the WebDAV configuration server-side !
### nginx monitoring
If you're using monit, you can easily monitor the nginx daemon by copying the following in `/etc/monit/conf.d/nginx` :
```
check process nginx
with pidfile "/run/nginx.pid"
start program = "/bin/systemctl start nginx"
stop program = "/bin/systemctl stop nginx"
alert monit@example.com
```
### Certificates auto-renewal
This goes beyond the scope of the article, but since letsencrypt certficates are only valid for 3 months, you'll need to renew them regularily. You can do so manually or you can setup a cron that does it for you.
I personnaly use the following script :
```
#!/bin/bash
PRG="/usr/bin/letsencrypt"
CONFIG="/etc/letsencrypt/cli.ini"
MAILDEST="admin@example.com"
GLOBAL=0
# www.example.com
$PRG --config $CONFIG certonly -w /var/www/letsencrypt -d www.example.com
[[ $? != 0 ]] && GLOBAL=$(( $GLOBAL + 1 ))
if [[ $GLOBAL == 0 ]]; then
/usr/sbin/nginx -s reload
else
echo "Something went wrong while renewing the certificates on $(hostname -f)
Manual action needed." | mail -s "Letsencrypt error on $(hostname -f)" $MAILDEST
fi
```
You can add multiple domains in the script. As long as you add all 3 lines for each domain, it will not automatically reload nginx if one or more certificate could not be renewed and will send an e-mail to the address configured in the `MAILDEST` variable.
You can configure this script in the root user crontab using the `crontab -e` command :
```
## LETSENCRYPT CERTIFICATE AUTORENEWAL
30 03 01 */2 * /root/bin/tlsrenew
```
This will run the script every two months, on the first day of the month, at 3:30 AM.
## Client configuration
### Installing the required packages
A single package is required to mount a webdav volume on Debian :
```
apt update && apt install davfs2
```
### Mounting the share manually
If like me, you want to mount your webdav share in a LXC container, you'll first need to make sure that the following line is present in its configuration file :
```
lxc.cgroup.devices.allow = c 10:229 rwm
```
You'll also need to create the `/dev/fuse` node in the container :
```
mknod /dev/fuse c 10 229
```
In any case, we have to edit the `/etc/davfs2/secrets` file to add the mount point, username and password that will be used to mount the share :
```
echo '/data webdav notanactualpassword' >> /etc/davfs2/secrets
```
Once that's done, we can mount our share with the following command :
```
mount -t davfs https://www.example.com/data /data -o ro,dir_mode=750,file_mode=640,uid=root,gid=root
```
You might need to edit the parameters depending on which users you want to make the share available to.
### Mouting the share on boot
A davfs volume can be mounted via the `/etc/fstab` file, but I decided to use monit instead so that the volume would be mounted again automatically should my WebDAV server reboot.
In order to do so, I first created a `davfs.txt` file in the `/var/www/data` folder on my WebDAV server :
```
touch /var/www/data/davfs.txt
```
I then created the following `/root/bin/mount_davfs` script :
```
#!/bin/bash
mknod /dev/fuse c 10 229
mount -t davfs https://www.example.com/data /data -o ro,dir_mode=750,file_mode=640,uid=root,gid=root
```
The last thing I did was create a `/etc/monit/conf.d/davfs` file with the following content :
```
check file davfs with path /data/davfs.txt
alert monit@example.com
if does not exist then exec "/root/bin/mount_davfs"
```
That way, if monit notices that the `/data/davfs.txt` file becomes inaccessible for some reason, it will try remouting the share.
## Conclusion
That's all ! Hopefully this has been useful to someone. Please do comment below if you have any question or if this has been helpful !