captainarkdotnet/public/2016/03/26/webdav-with-nginx/index.html

728 lines
23 KiB
HTML

<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="twitter:card" content="summary"/>
<meta name="twitter:title" content="WebDAV with nginx"/>
<meta name="twitter:description" content=""/>
<meta name="twitter:site" content="@"/>
<meta property="og:title" content="WebDAV with nginx &middot; Sysadmining. All day. Every day." />
<meta property="og:site_name" content="Sysadmining. All day. Every day." />
<meta property="og:url" content="https://www.captainark.net/2016/03/26/webdav-with-nginx/" />
<meta property="og:image" content="/images/cover.jpg"/>
<meta property="og:description" content="" />
<meta property="og:type" content="article" />
<meta property="article:published_time" content="2016-03-26T00:00:00&#43;01:00" />
<title>WebDAV with nginx &middot; Sysadmining. All day. Every day.</title>
<meta name="description" content="This website has been hosted on an Online.net dedicated server since its creation. I&amp;rsquo;ve been one of their customers for the past 3 years now, and I still " />
<meta name="HandheldFriendly" content="True" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" href="/images/favicon.ico">
<link rel="apple-touch-icon" href="/images/apple-touch-icon.png" />
<link rel="stylesheet" type="text/css" href="/css/screen.css" />
<link rel="stylesheet" type="text/css" href="/css/nav.css" />
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Merriweather:300,700,700italic,300italic|Open+Sans:700,400|Inconsolata:700,400" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/solarized-light.min.css" integrity="sha384-bFKDPkG3geCujYJIbPornilfOgmYQoPS45Oh/8daqqo1SUwNY06OeHorpgnNvx82" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/highlight.min.js" integrity="sha384-BlPof9RtjBqeJFskKv3sK3dh4Wk70iKlpIe92FeVN+6qxaGUOUu+mZNpALZ+K7ya" crossorigin="anonymous"></script>
<script>hljs.initHighlightingOnLoad();</script>
<link href="/index.xml" rel="alternate" type="application/rss+xml" title="Sysadmining. All day. Every day." />
<meta name="generator" content="Hugo 0.53" />
<link rel="canonical" href="https://www.captainark.net/2016/03/26/webdav-with-nginx/" />
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"publisher": {
"@type": "Organization",
"name": ,
"logo": https://www.captainark.net/images/logo.png
},
"author": {
"@type": "Person",
"name": ,
"image": {
"@type": "ImageObject",
"url": https://www.captainark.net/images/author.jpg,
"width": 250,
"height": 250
},
"url": https://www.captainark.net,
"sameAs": [
],
"description": Geek | Gamer | TV Shows Aficionado
},
"headline": WebDAV with nginx,
"name": WebDAV with nginx,
"wordCount": 1419,
"timeRequired": "PT7M",
"inLanguage": {
"@type": "Language",
"alternateName": en
},
"url": https://www.captainark.net/2016/03/26/webdav-with-nginx/,
"datePublished": 2016-03-26T00:00Z,
"dateModified": 2016-03-26T00:00Z,
"description": ,
"mainEntityOfPage": {
"@type": "WebPage",
"@id": https://www.captainark.net/2016/03/26/webdav-with-nginx/
}
}
</script>
<script>
(function(f, a, t, h, o, m){
a[h]=a[h]||function(){
(a[h].q=a[h].q||[]).push(arguments)
};
o=f.createElement('script'),
m=f.getElementsByTagName('script')[0];
o.async=1; o.src=t; o.id='fathom-script';
m.parentNode.insertBefore(o,m)
})(document, window, '//stats.captainark.net/tracker.js', 'fathom');
fathom('set', 'siteId', 'BYKXT');
fathom('trackPageview');
</script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fork-awesome@1.1.5/css/fork-awesome.min.css" integrity="sha256-P64qV9gULPHiZTdrS1nM59toStkgjM0dsf5mK/UwBV4=" crossorigin="anonymous">
</head>
<body class="nav-closed">
<div class="nav">
<h3 class="nav-title">Menu</h3>
<a href="#" class="nav-close">
<span class="hidden">Close</span>
</a>
<ul>
<h3>This site</h3>
<li class="nav-opened" role="presentation">
<a href="/">Home</a>
</li>
<li class="nav-opened" role="presentation">
<a href="/about">About</a>
</li>
<li class="nav-opened" role="presentation">
<a href="/resume">Resume</a>
</li>
<h3>Other services</h3>
<li class="nav-opened" role="presentation">
<a href="https://git.captainark.net">Gitea</a>
</li>
<li class="nav-opened" role="presentation">
<a href="https://pics.captainark.net">Chevereto</a>
</li>
<li class="nav-opened" role="presentation">
<a href="https://paste.captainark.net">Privatebin</a>
</li>
<li class="nav-opened" role="presentation">
<a href="https://chat.captainark.net">Rocket.Chat</a>
</li>
</ul>
<a class="subscribe-button icon-feed" href="/index.xml">Subscribe</a>
</div>
<span class="nav-cover"></span>
<div class="site-wrapper">
<header class="main-header post-head no-cover">
<nav class="main-nav clearfix">
<a class="blog-logo" href="https://www.captainark.net/"><img src="/images/logo.png" alt="Home" /></a>
<a class="menu-button" href="#"><span class="burger">&#9776;</span><span class="word">Menu</span></a>
</nav>
</header>
<main class="content" role="main">
<article class="post post">
<header class="post-header">
<h1 class="post-title">WebDAV with nginx</h1>
<small></small>
<section class="post-meta">
<time class="post-date" datetime="2016-03-26T00:00:00&#43;01:00">
26 March 2016
</time>
</section>
</header>
<section class="post-content">
<p>This website has been hosted on an <a href="https://www.online.net">Online.net</a> dedicated server since its creation. I&rsquo;ve been one of their customers for the past 3 years now, and I still don&rsquo;t have anything bad to say about them.</p>
<p>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.</p>
<p>It took me 2 days to migrate everything, but it was worth it. If I decide to switch servers again, I&rsquo;ll have to migrate the containers instead of the services themselves. Considering they are stored on a separate BTRFS volume, it shouldn&rsquo;t take me more than a few hours at most.</p>
<p>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.</p>
<p>In this tutorial, I&rsquo;ll explain how I&rsquo;ve configured a read-only WebDAV share using <a href="https://www.nginx.com/">nginx</a> and <a href="https://letsencrypt.org/">Let&rsquo;sEncrypt</a> SSL certificates between two Debian Jessie containers.</p>
<h2 id="server-configuration">Server configuration</h2>
<h3 id="installing-the-required-packages">Installing the required packages</h3>
<p>First thing first, we need to install the packages we&rsquo;ll need for this configuration :</p>
<pre><code class="language-bash">apt update
apt -t jessie-backports install nginx letsencrypt
apt install apache2-utils
</code></pre>
<h3 id="getting-our-first-certificate-from-letsencrypt">Getting our first certificate from letsencrypt</h3>
<h4 id="letsencrypt-configuration">letsencrypt configuration</h4>
<p>Let&rsquo;s create a configuration file for letsencrypt :</p>
<pre><code class="language-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/' &gt; /etc/letsencrypt/cli.ini
</code></pre>
<p><em>Please do modify admin@example.com by your actual e-mail address.</em></p>
<p>We also need to create the directory structure where letsencrypt ACME challenge temporary files will be stored :</p>
<pre><code>mkdir -p /var/www/letsencrypt/.well-known
</code></pre>
<h4 id="nginx-configuration">nginx configuration</h4>
<p>We now need to configure nginx by adding the following in the <code>/etc/nginx/sites-available/default</code> file, anywhere in the <code>server{}</code> block that is configured to listen on port 80.</p>
<pre><code>location /.well-known/acme-challenge {
root /var/www/letsencrypt;
}
</code></pre>
<p>Let&rsquo;s make sure that we haven&rsquo;t done anything wrong :</p>
<pre><code class="language-bash">nginx -t
</code></pre>
<p>The command should give you the following output :</p>
<pre><code>nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
</code></pre>
<p>If that&rsquo;s the case, you can safely reload the nginx daemon :</p>
<pre><code>nginx -s reload
</code></pre>
<h4 id="certificate-request">Certificate request</h4>
<p>Now that letsencrypt and nginx are properly configured, we can request our certificate from letsencrypt :</p>
<pre><code class="language-bash">letsencrypt --config /etc/letsencrypt/cli.ini certonly -w /var/www/letsencrypt -d www.example.com
</code></pre>
<p><em>Please do modify www.example.com by your server&rsquo;s FQDN, and please note that the letsencrypt servers need to be able to resolve that name to your server&rsquo;s IP.</em></p>
<p>If everything goes well, your certificates will be generated and stored in the /etc/letsencrypt folder.</p>
<h3 id="webdav-configuration">WebDAV configuration</h3>
<p>Now that we&rsquo;ve obtained our certificate from letsencrypt, we can begin configuring nginx.</p>
<p>First, we need to comment two SSL directives from the default nginx configuration :</p>
<pre><code>sed -i '/ssl_/ s/^/#/' /etc/nginx/nginx.conf
</code></pre>
<p>Let&rsquo;s now create a <code>/etc/nginx/conf.d/ssl.conf</code> with the following content :</p>
<pre><code>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;
</code></pre>
<p><em>This configuration will work if you&rsquo;re using a single certificate on your server. If not, you&rsquo;ll have to remove the <code>ssl_certificate</code>, <code>ssl_certificate_key</code> and <code>ssl_trusted_certificate</code> directives from this file and move them to the correct <code>server{}</code> block.</em></p>
<p>We now need to generate a <code>dhparam.pem</code> file :</p>
<pre><code class="language-bash">mkdir /etc/nginx/ssl &amp;&amp; chmod 700 /etc/nginx/ssl
openssl dhparam -out /etc/nginx/ssl/dhparam.pem 3072
chmod 600 /etc/nginx/ssl/dhparam.pem
</code></pre>
<p>Let&rsquo;s now generate a HTTP basic authentication file. This example creates a user named example :</p>
<pre><code>mkdir /etc/nginx/auth
htpasswd -c /etc/nginx/auth/webdav example
New password:
Re-type new password:
Adding password for user user
</code></pre>
<p>This file has to be readable by the user running your webserver. For security reasons, we&rsquo;ll make it readable only by him :</p>
<pre><code>chown -R www-data:nogroup /etc/nginx/auth
chmod 700 /etc/nginx/auth
chmod 400 /etc/nginx/auth/webdav
</code></pre>
<p>Let&rsquo;s now modify our <code>/etc/nginx/sites-available/default</code> file with the following content :</p>
<pre><code>server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
server_name &quot;&quot;;
return 444;
}
server {
listen 443 default_server ssl http2;
listen [::]:443 default_server ipv6only=on ssl http2;
server_name &quot;&quot;;
return 444;
}
</code></pre>
<p>We now have to create a <code>/etc/nginx/sites-available/example</code> file that will contain our actual webdav configuration. This example makes a <code>data</code> folder stored in <code>/var/www/</code> accessible.</p>
<pre><code>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 &quot;Restricted access&quot;;
auth_basic_user_file auth/webdav;
limit_except GET {
allow &lt;YOUR IP HERE&gt;;
deny all;
}
}
}
</code></pre>
<p>The last thing we have to do is to create a symlink so that nginx will load our configuration :</p>
<pre><code>ln -s /etc/nginx/sites-available/example /etc/nginx/sites-enabled/example
</code></pre>
<p>Like before, let&rsquo;s make sure our configuration is correct and then reload the daemon :</p>
<pre><code>nginx -t
nginx -s reload
</code></pre>
<p>That&rsquo;s it for the WebDAV configuration server-side !</p>
<h3 id="nginx-monitoring">nginx monitoring</h3>
<p>If you&rsquo;re using monit, you can easily monitor the nginx daemon by copying the following in <code>/etc/monit/conf.d/nginx</code> :</p>
<pre><code>check process nginx
with pidfile &quot;/run/nginx.pid&quot;
start program = &quot;/bin/systemctl start nginx&quot;
stop program = &quot;/bin/systemctl stop nginx&quot;
alert monit@example.com
</code></pre>
<h3 id="certificates-auto-renewal">Certificates auto-renewal</h3>
<p>This goes beyond the scope of the article, but since letsencrypt certficates are only valid for 3 months, you&rsquo;ll need to renew them regularily. You can do so manually or you can setup a cron that does it for you.</p>
<p>I personnaly use the following script :</p>
<pre><code>#!/bin/bash
PRG=&quot;/usr/bin/letsencrypt&quot;
CONFIG=&quot;/etc/letsencrypt/cli.ini&quot;
MAILDEST=&quot;admin@example.com&quot;
GLOBAL=0
# www.example.com
$PRG --config $CONFIG certonly -w /var/www/letsencrypt -d www.example.com
[[ $? != 0 ]] &amp;&amp; GLOBAL=$(( $GLOBAL + 1 ))
if [[ $GLOBAL == 0 ]]; then
/usr/sbin/nginx -s reload
else
echo &quot;Something went wrong while renewing the certificates on $(hostname -f)
Manual action needed.&quot; | mail -s &quot;Letsencrypt error on $(hostname -f)&quot; $MAILDEST
fi
</code></pre>
<p>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 <code>MAILDEST</code> variable.</p>
<p>You can configure this script in the root user crontab using the <code>crontab -e</code> command :</p>
<pre><code>## LETSENCRYPT CERTIFICATE AUTORENEWAL
30 03 01 */2 * /root/bin/tlsrenew
</code></pre>
<p>This will run the script every two months, on the first day of the month, at 3:30 AM.</p>
<h2 id="client-configuration">Client configuration</h2>
<h3 id="installing-the-required-packages-1">Installing the required packages</h3>
<p>A single package is required to mount a webdav volume on Debian :</p>
<pre><code>apt update &amp;&amp; apt install davfs2
</code></pre>
<h3 id="mounting-the-share-manually">Mounting the share manually</h3>
<p>If like me, you want to mount your webdav share in a LXC container, you&rsquo;ll first need to make sure that the following line is present in its configuration file :</p>
<pre><code>lxc.cgroup.devices.allow = c 10:229 rwm
</code></pre>
<p>You&rsquo;ll also need to create the <code>/dev/fuse</code> node in the container :</p>
<pre><code>mknod /dev/fuse c 10 229
</code></pre>
<p>In any case, we have to edit the <code>/etc/davfs2/secrets</code> file to add the mount point, username and password that will be used to mount the share :</p>
<pre><code>echo '/data webdav notanactualpassword' &gt;&gt; /etc/davfs2/secrets
</code></pre>
<p>Once that&rsquo;s done, we can mount our share with the following command :</p>
<pre><code>mount -t davfs https://www.example.com/data /data -o ro,dir_mode=750,file_mode=640,uid=root,gid=root
</code></pre>
<p>You might need to edit the parameters depending on which users you want to make the share available to.</p>
<h3 id="mouting-the-share-on-boot">Mouting the share on boot</h3>
<p>A davfs volume can be mounted via the <code>/etc/fstab</code> file, but I decided to use monit instead so that the volume would be mounted again automatically should my WebDAV server reboot.</p>
<p>In order to do so, I first created a <code>davfs.txt</code> file in the <code>/var/www/data</code> folder on my WebDAV server :</p>
<pre><code>touch /var/www/data/davfs.txt
</code></pre>
<p>I then created the following <code>/root/bin/mount_davfs</code> script :</p>
<pre><code>#!/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
</code></pre>
<p>The last thing I did was create a <code>/etc/monit/conf.d/davfs</code> file with the following content :</p>
<pre><code>check file davfs with path /data/davfs.txt
alert monit@example.com
if does not exist then exec &quot;/root/bin/mount_davfs&quot;
</code></pre>
<p>That way, if monit notices that the <code>/data/davfs.txt</code> file becomes inaccessible for some reason, it will try remouting the share.</p>
<h2 id="conclusion">Conclusion</h2>
<p>That&rsquo;s all ! Hopefully this has been useful to someone. Please do comment below if you have any question or if this has been helpful !</p>
</section>
<footer class="post-footer">
<figure class="author-image">
<a class="img" href="https://www.captainark.net/" style="background-image: url(/images/author.jpg)"><span class="hidden">Antoine Joubert's Picture</span></a>
</figure>
<section class="author">
<h4><a href="https://www.captainark.net/">Antoine Joubert</a></h4>
<p>Geek | Gamer | TV Shows Aficionado</p>
<div class="author-meta">
<span class="author-location icon-location">Angers, France</span>
<span class="author-link icon-link"><a href="https://www.captainark.net">https://www.captainark.net</a></span>
</div>
</section>
<!-- isso -->
<script data-isso="https://www.captainark.net/comments/" src="https://www.captainark.net/comments/js/embed.min.js"></script>
<noscript>Please enable JavaScript to view comments</noscript>
<section id="isso-thread"></section>
<!-- end isso -->
</footer>
</article>
</main>
<aside class="read-next">
<a class="read-next-story" style="no-cover" href="/2017/11/19/installing-ghost/">
<section class="post">
<h2>Installing Ghost</h2>
</section>
</a>
<a class="read-next-story prev" style="no-cover" href="/2016/03/13/mysql-backup-script/">
<section class="post">
<h2>MySQL backup script</h2>
</section>
</a>
</aside>
<center>
<a class="fa-icons" href="mailto:contact@captainark.net">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-envelope fa-stack-1x fa-inverse"></i>
</span>
</a>
<a class="fa-icons" href="https://twitter.com/captainark">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-twitter fa-stack-1x fa-inverse"></i>
</span>
</a>
<a class="fa-icons" href="https://social.captainark.net/users/captainark">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-mastodon-alt fa-stack-1x fa-inverse"></i>
</span>
</a>
<a class="fa-icons" href="https://github.com/captainark">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-github fa-stack-1x fa-inverse"></i>
</span>
</a>
<a class="fa-icons" href="https://www.last.fm/user/captainark">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-lastfm fa-stack-1x fa-inverse"></i>
</span>
</a>
<a class="fa-icons" href="https://steamcommunity.com/id/captainark">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-steam fa-stack-1x fa-inverse"></i>
</span>
</a>
<a class="fa-icons" href="https://www.twitch.tv/captainark">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-twitch fa-stack-1x fa-inverse"></i>
</span>
</a>
</center>
<footer class="site-footer clearfix">
<section class="copyright"><a href="">Sysadmining. All day. Every day.</a> © 2015 - 2019</section>
<section class="poweredby">Proudly generated by <a class="icon-hugo" href="http://gohugo.io">HUGO</a>, with <a class="icon-theme" href="https://github.com/vjeantet/hugo-theme-casper">Casper</a> theme</section>
</footer>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js" integrity="sha384-CgeP3wqr9h5YanePjYLENwCTSSEz42NJkbFpAFgHWQz7u3Zk8D00752ScNpXqGjS" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fitvids/1.1.0/jquery.fitvids.min.js" integrity="sha384-2/VQUb0aZHixKnNLh7pD38DZk+acGpEw5LeHieWVDPR0h/H326kp/1qnRPDYmFXM" crossorigin="anonymous"></script>
<script type="text/javascript" src="/js/index.js"></script>
</body>
</html>