<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Marco Lazzarotto</title><link>https://lazzarotto.dev/blog/en/</link><description>Recent content on Marco Lazzarotto</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><managingEditor>postmaster@mlazzarotto.it (Marco Lazzarotto)</managingEditor><webMaster>postmaster@mlazzarotto.it (Marco Lazzarotto)</webMaster><copyright>Marco Lazzarotto</copyright><lastBuildDate>Fri, 01 May 2026 00:00:00 +0100</lastBuildDate><atom:link href="https://lazzarotto.dev/blog/en/index.xml" rel="self" type="application/rss+xml"/><item><title>Mitigating the 'Copy Fail' Vulnerability (CVE-2026-31431) with a Simple Ansible Playbook</title><link>https://lazzarotto.dev/blog/en/mitigating-the-copy-fail-vulnerability-cve-2026-31431-with-a-simple-ansible-playbook/</link><pubDate>Fri, 01 May 2026 00:00:00 +0100</pubDate><author>postmaster@mlazzarotto.it (Marco Lazzarotto)</author><guid>https://lazzarotto.dev/blog/en/mitigating-the-copy-fail-vulnerability-cve-2026-31431-with-a-simple-ansible-playbook/</guid><description>&lt;img src="https://lazzarotto.dev/blog/" alt="Featured image of post Mitigating the 'Copy Fail' Vulnerability (CVE-2026-31431) with a Simple Ansible Playbook" /&gt;&lt;p&gt;&lt;a class="link" href="https://github.com/mlazzarotto/copy-fail-CVE-2026-31431-mitigation-ansible-playbook" target="_blank" rel="noopener"
&gt;Direct link to the Github repo&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="in-a-nutshell-theres-a-new-critical-linux-bug-going-around"&gt;In a nutshell: there&amp;rsquo;s a new critical Linux bug going around
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://copy.fail/" target="_blank" rel="noopener"
&gt;&amp;lsquo;Copy Fail&amp;rsquo;&lt;/a&gt; is a new and nasty local privilege escalation bug, &lt;a class="link" href="https://cert.europa.eu/publications/security-advisories/2026-005/" target="_blank" rel="noopener"
&gt;CVE-2026-31431&lt;/a&gt;, discovered by the analyst team at &lt;a class="link" href="https://xint.io/products/xint-code" target="_blank" rel="noopener"
&gt;Xint Code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This bug essentially allows an attacker to gain &lt;em&gt;root&lt;/em&gt; privileges on many Linux operating systems (Ubuntu, Debian, RHEL, Suse, Alma, Amazon Linux) using a Python script of just 732 bytes.&lt;/p&gt;
&lt;p&gt;&amp;lsquo;Copy Fail&amp;rsquo; only requires a local unprivileged user account: no network access, no kernel debug capabilities, no pre-installed libraries. The kernel&amp;rsquo;s cryptographic API (AF_ALG) is enabled by default on virtually all major distributions, meaning the entire patch range from 2017 onward is vulnerable.&lt;/p&gt;
&lt;h2 id="understanding-the-copy-fail-vulnerability"&gt;Understanding the &amp;lsquo;Copy Fail&amp;rsquo; vulnerability
&lt;/h2&gt;&lt;p&gt;In simple terms, imagine the Linux kernel keeping a copy of files in memory to avoid reading from disk every time — the so-called &lt;em&gt;page cache&lt;/em&gt;. This cache is normally used to speed up certain kernel operations. All good.&lt;/p&gt;
&lt;p&gt;Then, however, &lt;code&gt;algif_aead&lt;/code&gt; comes into play — one of the Linux kernel modules that exposes the cryptographic subsystem to &lt;em&gt;userspace&lt;/em&gt; via the &lt;strong&gt;AF_ALG&lt;/strong&gt; interface. In plain English? An &lt;strong&gt;API&lt;/strong&gt; for cryptographic services, used for example in protocols like &lt;strong&gt;IPsec&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Back in &lt;strong&gt;2017&lt;/strong&gt;, this kernel module was modified to make it more &amp;ldquo;efficient.&amp;rdquo; It was essentially told: &amp;ldquo;Instead of making a copy of the document (the file) you need to work on, write directly onto the original that the kernel passes you in the &lt;strong&gt;page cache&lt;/strong&gt;&amp;rdquo; — thanks to a system call named &lt;code&gt;splice()&lt;/code&gt; that passes data by reference instead of copying it.&lt;/p&gt;
&lt;p&gt;This &lt;em&gt;in-place&lt;/em&gt; optimization is the root of the problem, because it allowed a user to write data where they absolutely should not have been able to.&lt;/p&gt;
&lt;p&gt;And what data can an attacker write? They can overwrite a small portion of &lt;code&gt;/usr/bin/su&lt;/code&gt; to make it possible to run &lt;code&gt;su&lt;/code&gt; and become the &lt;code&gt;root&lt;/code&gt; user.&lt;/p&gt;
&lt;h3 id="the-ghost-aspect"&gt;The &amp;lsquo;ghost&amp;rsquo; aspect
&lt;/h3&gt;&lt;p&gt;One of the most insidious things about this vulnerability is that the modification only happens in &lt;em&gt;RAM&lt;/em&gt;. The file on disk remains untouched.&lt;/p&gt;
&lt;p&gt;This means all classic file integrity monitoring systems (such as Tripwire, AIDE, Wazuh, etc.) see nothing unusual.
This is extremely dangerous because it leaves no traces on disk.&lt;/p&gt;
&lt;h3 id="container-escape"&gt;Container escape
&lt;/h3&gt;&lt;p&gt;Earlier we mentioned the &lt;strong&gt;page cache&lt;/strong&gt; — that portion of memory used to speed up kernel operations. Well, the &lt;strong&gt;page cache&lt;/strong&gt; is shared across all containers on the same host (Podman, Docker, Kubernetes, etc.).&lt;/p&gt;
&lt;p&gt;This means that a vulnerable container, or one infected through a &lt;strong&gt;supply chain&lt;/strong&gt; attack, can perform &lt;strong&gt;container escape&lt;/strong&gt; in a frighteningly simple way.&lt;/p&gt;
&lt;h2 id="the-ansible-playbook-for-immediate-mitigation"&gt;The Ansible playbook for immediate mitigation
&lt;/h2&gt;&lt;p&gt;Drawing inspiration from the &lt;a class="link" href="https://kb.morrolinux.ovh/books/iac/page/playbook-di-mitigazione-cve-2026-31431" target="_blank" rel="noopener"
&gt;Morrolinux KB&lt;/a&gt; and this &lt;a class="link" href="https://gist.github.com/m3nu/c19269ef4fd6fa53b03eb388f77464da" target="_blank" rel="noopener"
&gt;Github gist by user m3nu&lt;/a&gt;, I created an Ansible playbook available at &lt;a class="link" href="https://github.com/mlazzarotto/copy-fail-CVE-2026-31431-mitigation-ansible-playbook" target="_blank" rel="noopener"
&gt;copy-fail-CVE-2026-31431-mitigation-ansible-playbook&lt;/a&gt; that allows you to apply the mitigation and roll it back after a kernel update.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Apply Mitigation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;mitigation]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;not (rollback | bool)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# --- Debian family modprobe blacklist + unload ---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Apply modprobe blacklist (Debian family)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ansible_facts[&amp;#39;os_family&amp;#39;] == &amp;#39;Debian&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Create modprobe blacklist for {{ target_module }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ansible.builtin.copy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{{ modprobe_conf_path }}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # Generated by Ansible — CVE-2026-31431 mitigation
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; install {{ target_module }} /bin/false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;root&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;root&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0644&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Unload module {{ target_module }} if currently loaded&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;community.general.modprobe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{{ target_module }}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;absent&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;register&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;rmmod_result&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;failed_when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;rmmod_result is failed&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="p"&gt;&amp;gt;-&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#39;not currently loaded&amp;#39; not in (rmmod_result.msg | default(&amp;#39;&amp;#39;))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; and &amp;#39;not currently loaded&amp;#39; not in (rmmod_result.stderr | default(&amp;#39;&amp;#39;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="p"&gt;&amp;gt;-&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#39;is in use&amp;#39; not in (rmmod_result.msg | default(&amp;#39;&amp;#39;))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; and &amp;#39;is in use&amp;#39; not in (rmmod_result.stderr | default(&amp;#39;&amp;#39;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Remind operator to reboot if module was in use (Debian)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;-&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#39;is in use&amp;#39; in (rmmod_result.msg | default(&amp;#39;&amp;#39;))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; or &amp;#39;is in use&amp;#39; in (rmmod_result.stderr | default(&amp;#39;&amp;#39;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ansible.builtin.debug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;REMINDER: Module was in use and could not be unloaded. Reboot required to complete mitigation.&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# --- kernel initcall blacklist (RHEL/Alma 9 and 10 only) ---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Apply kernel initcall blacklist (Red Hat family)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;ansible_facts[&amp;#39;os_family&amp;#39;] == &amp;#39;RedHat&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;ansible_facts[&amp;#39;distribution_major_version&amp;#39;] in [&amp;#39;9&amp;#39;, &amp;#39;10&amp;#39;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Check current grubby kernel args (pre-change)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ansible.builtin.command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;grubby --info=ALL&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;register&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grubby_info_pre&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;changed_when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;check_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Add &amp;#39;{{ target_kernel_arg }}&amp;#39; to all kernels via grubby&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ansible.builtin.command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;grubby --update-kernel=ALL --args=&amp;#39;{{ target_kernel_arg }}&amp;#39;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;register&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grubby_add_result&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;changed_when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;target_kernel_arg not in grubby_info_pre.stdout&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Remind operator to reboot (RHEL)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grubby_add_result.changed&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ansible.builtin.debug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;REMINDER: Kernel args changed. Reboot required to activate mitigation.&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="breaking-down-the-playbook"&gt;Breaking down the playbook
&lt;/h3&gt;&lt;p&gt;The playbook is compatible with RHEL-based (CentOS, Rocky, Alma) and Debian-based (Ubuntu and Mint) operating systems.
By default, it uses the &lt;code&gt;serial&lt;/code&gt; function to run the playbook on a limited number of hosts at a time — &lt;strong&gt;25%&lt;/strong&gt; by default. For example, with 100 hosts in the &lt;em&gt;inventory&lt;/em&gt;, the mitigation will be applied to 25 hosts in the first batch, 25 in the second, and so on.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;max_fail_percentage&lt;/code&gt; function tolerates an error rate of 20%. For example, if there are 25 hosts in a batch and 6 or more fail, the job will be aborted.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Pre Task&lt;/strong&gt; displays some information about the operating system and kernel for each host and collects data about the system&amp;rsquo;s current state.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Task&lt;/strong&gt; for Debian-based distributions disables the kernel module and blacklists it; if the module is in use, it reminds the user to reboot the system.
For RHEL-based distributions, it uses the &lt;code&gt;grubby&lt;/code&gt; command to disable the module and always reminds the user to reboot.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Post Task&lt;/strong&gt; collects additional information and presents the user with a summary of the actions performed.&lt;/p&gt;
&lt;h3 id="how-to-run-it"&gt;How to run it
&lt;/h3&gt;&lt;p&gt;First, you need to prepare the Ansible configuration file and inventory as you would for any Ansible playbook. Then run a ping to check server reachability.
Finally, use &lt;code&gt;ansible-playbook mitigation-playbook.yaml&lt;/code&gt; to run the mitigation on all servers, or use the &lt;code&gt;--limit webservers&lt;/code&gt; option to restrict the playbook to a specific group of hosts.
For &lt;strong&gt;rollback&lt;/strong&gt;, same as above, but use the &lt;code&gt;--tags rollback&lt;/code&gt; option to run only the rollback tasks.&lt;/p&gt;
&lt;h2 id="verifying-that-the-mitigation-works"&gt;Verifying that the mitigation works
&lt;/h2&gt;&lt;p&gt;After running the playbook, you need to verify that the mitigation took effect.
To do so, use these two methods depending on your hosts&amp;rsquo; distribution:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On Debian-based distributions, use &lt;code&gt;lsmod | grep algif_aead&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;On RHEL-based distributions, use &lt;code&gt;grep initcall_blacklist /proc/cmdline&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="my-take-one-more-step-for-containers"&gt;My take: one more step for containers
&lt;/h2&gt;&lt;p&gt;Personally, my opinion is that stopping at host-level configuration is not enough if you&amp;rsquo;re running containers. Since containers share the host machine&amp;rsquo;s kernel, a reboot or a configuration error that accidentally reloads the vulnerable module would leave your workloads exposed again.&lt;/p&gt;
&lt;p&gt;We know this exploit needs to open an AF_ALG family socket in order to communicate with the kernel&amp;rsquo;s Crypto API. For this reason, I strongly recommend implementing a &amp;ldquo;defense in depth&amp;rdquo; approach using &lt;strong&gt;seccomp&lt;/strong&gt;. If you configure a custom profile for your container runtime, you can tell the system to block socket syscalls targeting AF_ALG altogether. This way, even if the algif_aead module were to magically reactivate on the host, the kernel will cut off any attempt by the container to exploit it.&lt;/p&gt;
&lt;p&gt;An example &lt;strong&gt;seccomp&lt;/strong&gt; policy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;defaultAction&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;SCMP_ACT_ALLOW&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;syscalls&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;names&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;socket&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;action&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;SCMP_ACT_ERRNO&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;args&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;index&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;op&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;SCMP_CMP_EQ&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;comment&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Block AF_ALG sockets (family 38) to mitigate Copy Fail&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="stay-vigilant"&gt;Stay vigilant!
&lt;/h2&gt;&lt;p&gt;And that brings us to the end. There&amp;rsquo;s one fundamental point I want to make clear: the Ansible and modprobe-based solution we just covered is an excellent short-term fix to protect yourself immediately, but remember that it is not a permanent cure.&lt;/p&gt;
&lt;p&gt;My personal opinion is that security workarounds should never be left sitting forgotten on servers collecting dust. Keep an eye on the security bulletins from your vendors (whether that&amp;rsquo;s Canonical, Red Hat, SUSE, or others).
As soon as they release updated kernel packages with the official patch for CVE-2026-31431, schedule the installation and a proper reboot of your machines.
Once your systems have the patched kernel, you can clean up and remove the files created by this playbook.&lt;/p&gt;
&lt;p&gt;I hope this guide saved you a few headaches! If you have any doubts, questions, or want to share how you&amp;rsquo;re handling this CVE in your environments, feel free to reach out on &lt;a class="link" href="https://www.linkedin.com/in/marco-lazzarotto/" target="_blank" rel="noopener"
&gt;LinkedIn&lt;/a&gt; or, if you need personalized consulting, you can book a call in the footer of this page.&lt;/p&gt;</description></item><item><title>Wall Street Relies on AI, Europe Builds Its Digital Fortress: Two Futures Compared</title><link>https://lazzarotto.dev/blog/en/wall-street-relies-on-ai-europe-builds-its-digital-fortress-two-futures-compared/</link><pubDate>Tue, 14 Apr 2026 00:00:00 +0100</pubDate><author>postmaster@mlazzarotto.it (Marco Lazzarotto)</author><guid>https://lazzarotto.dev/blog/en/wall-street-relies-on-ai-europe-builds-its-digital-fortress-two-futures-compared/</guid><description>&lt;img src="https://lazzarotto.dev/blog/" alt="Featured image of post Wall Street Relies on AI, Europe Builds Its Digital Fortress: Two Futures Compared" /&gt;&lt;p&gt;The news is era-defining: the White House is pushing the world&amp;rsquo;s largest investment banks [1] — we&amp;rsquo;re talking about giants like Goldman Sachs, Bank of America, and Citigroup — to use Anthropic&amp;rsquo;s AI (Claude Mythos) for their cybersecurity. An alliance between government, finance, and big tech that feels like a trailer for a sci-fi movie.&lt;/p&gt;
&lt;p&gt;But behind the spotlight of this seemingly futuristic move, two fundamental questions hide that concern us all, from Trento to San Francisco:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is AI about to make cybersecurity experts obsolete?&lt;/li&gt;
&lt;li&gt;While the US ties itself to a single &amp;ldquo;black box,&amp;rdquo; what is Europe doing to avoid being left behind?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Spoiler: the answers are much more complex and interesting than a simple &amp;ldquo;yes&amp;rdquo; or &amp;ldquo;no.&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="the-myth-of-job-stealing-ai-the-data-says-otherwise"&gt;The myth of &amp;ldquo;job-stealing&amp;rdquo; AI: the data says otherwise
&lt;/h2&gt;&lt;p&gt;The first instinctive reaction is fear. If an AI can perform red teaming and find vulnerabilities, what will happen to the tens of thousands of professionals who do this for a living today? [1]&lt;/p&gt;
&lt;p&gt;Fortunately, the data comes to our rescue and paints a completely different picture. Let&amp;rsquo;s start with a striking number: according to the 2025 Fortinet Global Cybersecurity Skills Gap Report [1], 4.7 million cybersecurity professionals are missing globally. That&amp;rsquo;s not a typo. In Italy, the gap is equally serious, with about 136,000 job postings per year in the ICT sector against only 73,000 new talents entering the field [1].&lt;/p&gt;
&lt;p&gt;AI isn&amp;rsquo;t entering a saturated market to steal jobs; it&amp;rsquo;s arriving in a sector in full emergency where human forces are no longer enough.&lt;/p&gt;
&lt;h2 id="the-real-game-us-monopoly-vs-european-sovereignty"&gt;The real game: US Monopoly vs. European Sovereignty
&lt;/h2&gt;&lt;p&gt;According to Gartner [1], by 2026, 50% of Security Operations Centers (SOCs) will use AI as decision support. The goal is not to replace the analyst, but to give them a &amp;ldquo;superpower&amp;rdquo;: automating repetitive tasks, analyzing amounts of data unmanageable for a human, and flagging only the anomalies that require intuition and creativity. AI becomes an ally, a force multiplier that allows understaffed and stressed teams to focus on strategy instead of routine.&lt;/p&gt;
&lt;p&gt;In my opinion, the security professional&amp;rsquo;s profile will evolve from a hyper-specialized technician to a strategist and supervisor of intelligent systems. Their task will no longer be finding the needle in the haystack, but teaching the AI how to search for the needle and, above all, understanding if what it found is actually a needle.&lt;/p&gt;
&lt;h2 id="usa-vs-europe-monopoly-vs-sovereignty"&gt;USA vs. Europe, Monopoly vs. Sovereignty
&lt;/h2&gt;&lt;p&gt;And here we get to the geopolitical heart of the matter.&lt;/p&gt;
&lt;h3 id="act-one-the-american-bet-on-everything-now"&gt;Act One: The American bet on &amp;ldquo;everything, now&amp;rdquo;
&lt;/h3&gt;&lt;p&gt;On one side, we have &lt;strong&gt;Team USA&lt;/strong&gt;. The government acts as a sponsor, and Wall Street adopts a proprietary and powerful solution en masse. Fast, efficient, centralized. The quintessence of the American approach. Even Jamie Dimon of JPMorgan, a known skeptic, was invited to the table [1] (though he did not participate). The risk? A frightening vendor lock-in. Entrusting the security of the planet&amp;rsquo;s most critical financial infrastructure to a single private provider creates a single point of failure with unimaginable consequences. And, let&amp;rsquo;s not forget, a historical precedent that teaches us to be cautious when government agencies (like the NSA) and tech giants dialogue too closely.&lt;/p&gt;
&lt;h3 id="act-two-the-european-marathon-for-autonomy"&gt;Act Two: The European marathon for autonomy
&lt;/h3&gt;&lt;p&gt;On the other side is &lt;strong&gt;Team Europe&lt;/strong&gt;, playing a completely different game. Slower, more fragmented, but with a very clear objective: digital sovereignty. Europe has understood that technological dependence is the new energy dependence. And it&amp;rsquo;s moving. [1]&lt;/p&gt;
&lt;p&gt;Contrary to what people think, these are not just words. The network of European Digital Innovation Hubs (EDIH), with over 200 active hubs, is helping SMEs digitize by choosing European technological solutions. It&amp;rsquo;s not just about innovating, but doing so while building an autonomous ecosystem. Furthermore, national initiatives like the &amp;ldquo;Fondo per la Repubblica Digitale&amp;rdquo; in Italy (with about 220 million euros) demonstrate an investment in closing the skills gap [1].&lt;/p&gt;
&lt;h2 id="two-philosophies-one-choice-what-digital-future-do-we-want"&gt;Two philosophies, one choice: what digital future do we want?
&lt;/h2&gt;&lt;p&gt;What we are witnessing is not just a technical debate, but a clash between two philosophies. The American approach privileges speed and power, even at the cost of centralizing power in the hands of a few giants. The European approach is more cautious, aiming to build a resilient, open, and distributed alternative, even if it requires more time and coordination.&lt;/p&gt;
&lt;p&gt;For those working in the sector, the lesson is twofold. First: AI is not a threat, but the tool that will define the next decade. Ignoring it is not an option. Second: the choice of tools we use is never neutral. Behind software lies a business model, an ideology, and a geopolitical strategy.&lt;/p&gt;
&lt;p&gt;The real question, then, isn&amp;rsquo;t whether we will use AI for our security, but which AI we will choose: the closed and centralized one or the open and federated one? [1]&lt;/p&gt;
&lt;p&gt;The answer to this question will decide who controls the keys to our digital future.&lt;/p&gt;</description></item><item><title>Why I moved my blog from Cloudflare Pages to Kubernetes: a story of sub-directories and freedom</title><link>https://lazzarotto.dev/blog/en/why-i-moved-my-blog-from-cloudflare-pages-to-kubernetes-a-story-of-sub-directories-and-freedom/</link><pubDate>Mon, 13 Apr 2026 00:00:00 +0100</pubDate><author>postmaster@mlazzarotto.it (Marco Lazzarotto)</author><guid>https://lazzarotto.dev/blog/en/why-i-moved-my-blog-from-cloudflare-pages-to-kubernetes-a-story-of-sub-directories-and-freedom/</guid><description>&lt;img src="https://lazzarotto.dev/blog/" alt="Featured image of post Why I moved my blog from Cloudflare Pages to Kubernetes: a story of sub-directories and freedom" /&gt;&lt;h2 id="the-initial-idyll-why-cloudflare-pages-seemed-like-the-perfect-choice"&gt;The initial idyll: why Cloudflare Pages seemed like the perfect choice
&lt;/h2&gt;&lt;p&gt;July 25, 2022—this was the first commit on my blog&amp;rsquo;s Github repo. Previously, I used WordPress with satisfaction, but I realized I was using less than 10% of the CMS&amp;rsquo;s features for my blog, which required continuous maintenance to avoid security risks. I started looking for different tools that could give me the possibility to write a blog in a simple format (Markdown), without the need for a backend or a database, and with minimal maintenance.&lt;/p&gt;
&lt;p&gt;It was during that search that I discovered &lt;strong&gt;Hugo&lt;/strong&gt; and was immediately very satisfied from several points of view, so I decided to save my articles on Github and host the site on &lt;strong&gt;Cloudflare Pages&lt;/strong&gt;, since my domain was already configured there.&lt;/p&gt;
&lt;p&gt;The integration was perfect: a push of a new article to the &lt;code&gt;master&lt;/code&gt; branch (back then the default branch still had that name, later replaced by &lt;em&gt;main&lt;/em&gt;) gave the signal to Cloudflare Pages (now called &lt;em&gt;Workers &amp;amp; Pages&lt;/em&gt;) that it was time to use Hugo to regenerate the static content of the site and publish it.&lt;/p&gt;
&lt;p&gt;It was perfect; it never missed a beat, and once set up, it was the most convenient and simplest thing in the world.&lt;/p&gt;
&lt;h2 id="a-slight-detour-unifying-domains-to-attract-more-traffic-to-the-portfolio"&gt;A slight detour: unifying domains to attract more traffic to the portfolio
&lt;/h2&gt;&lt;p&gt;Years pass, we arrive in 2026, and I start my career as a freelancer in the networking and DevOps field. I need to bring more traffic to my portfolio site &lt;a class="link" href="https://lazzarotto.dev" target="_blank" rel="noopener"
&gt;lazzarotto.dev&lt;/a&gt; to attract potential clients.&lt;/p&gt;
&lt;p&gt;One of the best suggestions I received was to move the blog from &lt;em&gt;mlazzarotto.it&lt;/em&gt; (which brought 1,000 visits in the last 90 days) and merge it with the &lt;em&gt;lazzarotto.dev&lt;/em&gt; domain to unify the branding.&lt;/p&gt;
&lt;p&gt;The reason was quite simple: in Google&amp;rsquo;s eyes, I had two distinct websites competing with each other:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;mlazzarotto.it (the blog)&lt;/li&gt;
&lt;li&gt;lazzarotto.dev (the portfolio)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here I had two paths:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;move the blog to the subdomain &lt;code&gt;blog.lazzarotto.dev&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;move the blog to the path &lt;code&gt;lazzarotto.dev/blog&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Doing some research online, I realized that the second option was the one recommended by all the &amp;ldquo;pros&amp;rdquo; in web design and SEO.&lt;/p&gt;
&lt;h2 id="the-unexpected-obstacle-the-problem-of-mydomaindevblog"&gt;The unexpected obstacle: the problem of mydomain.dev/blog
&lt;/h2&gt;&lt;p&gt;Cloudflare &lt;em&gt;Workers &amp;amp; Pages&lt;/em&gt; works great when the static site to be published is exposed at the root of the domain (or subdomain) like &lt;code&gt;https://mlazzarotto.it/&lt;/code&gt;, but it quickly becomes a nightmare trying to publish the website on the subpath &lt;code&gt;https://lazzarotto.dev/blog&lt;/code&gt; due to Cloudflare&amp;rsquo;s logic.&lt;/p&gt;
&lt;p&gt;I won&amp;rsquo;t deny that I spent several hours trying to get the &lt;strong&gt;workers&lt;/strong&gt; to work to publish the site correctly, and I even got close, but there was always some issue with resources (CSS and JS) not being referenced correctly by Hugo. Therefore, the site was visible but without styling and without dynamic components.&lt;/p&gt;
&lt;h2 id="the-moment-of-decision-evaluating-alternatives"&gt;The moment of decision: evaluating alternatives
&lt;/h2&gt;&lt;p&gt;Even though my professional focus isn&amp;rsquo;t primarily on hosting services for static sites, I explored the most common alternatives for my case.&lt;/p&gt;
&lt;p&gt;One of the first alternatives I discarded was GitHub Pages, mainly due to an uptime that isn&amp;rsquo;t always impeccable for my standards. Added to this is my personal preference to distance myself from the Microsoft ecosystem and its current direction.&lt;/p&gt;
&lt;p&gt;The image below was taken from &lt;a class="link" href="https://mrshu.github.io/github-statuses/" target="_blank" rel="noopener"
&gt;https://mrshu.github.io/github-statuses/&lt;/a&gt; on April 11, 2026, and objectively, it doesn&amp;rsquo;t thrill me, even though my blog doesn&amp;rsquo;t require 99.9999% uptime.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://lazzarotto.dev/blog/img/github_last_90_days_uptime_11042026.png"
loading="lazy"
alt="Github uptime last 90 days"
&gt;&lt;/p&gt;
&lt;p&gt;There are other solutions like &lt;strong&gt;Netlify&lt;/strong&gt; and &lt;strong&gt;Vercel&lt;/strong&gt;, but the problem of using a &lt;em&gt;subpath&lt;/em&gt; to make this blog available at &lt;code&gt;https://lazzarotto.dev/blog&lt;/code&gt; remained.
Therefore, since I am already hosting my portfolio on Kubernetes using Traefik as a reverse proxy, I decided to keep the blog in my homelab as well.&lt;/p&gt;
&lt;h2 id="the-new-architecture-a-look-at-the-new-setup"&gt;The new architecture: a look at the new setup
&lt;/h2&gt;&lt;p&gt;The new architecture involves using Gitea for the repository and &lt;strong&gt;Continuous Integration&lt;/strong&gt;, Kubernetes for the blog deployment, and ArgoCD for &lt;strong&gt;Continuous Delivery&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;With these three tools, I managed to replicate the combination I used previously—Github + Cloudflare Pages—in a fairly simple (relatively) way that still allows me to have a new post published in less than 5 minutes.&lt;/p&gt;
&lt;p&gt;Below, I will illustrate how the pipeline works.&lt;/p&gt;
&lt;h3 id="gitea-controlling-my-code"&gt;Gitea: controlling my code
&lt;/h3&gt;&lt;p&gt;I&amp;rsquo;ve been using Gitea for a while to keep my personal projects locally, including the code for my portfolio site. I also use it as Continuous Integration software thanks to the &lt;strong&gt;Gitea Actions&lt;/strong&gt; feature, which is essentially a copy of Github Actions.&lt;/p&gt;
&lt;p&gt;In the blog repo, I created a workflow that builds a Docker image, uploads it to the Docker registry (also on Gitea), and then modifies the Kubernetes manifest so that ArgoCD updates the deployment.&lt;/p&gt;
&lt;p&gt;The workflow configuration is quite simple and uses some external packages from Github.&lt;/p&gt;
&lt;h3 id="kubernetes-the-orchestrator-of-everything"&gt;Kubernetes: the orchestrator of everything
&lt;/h3&gt;&lt;p&gt;While Gitea handles the source code, Kubernetes has the task of orchestrating the pods (2 replicas) and ensuring the application is always working thanks to the implementation of health checks.&lt;/p&gt;
&lt;p&gt;The deployment happens automatically thanks to the integration of ArgoCD, which deploys the new &lt;strong&gt;replicaSet&lt;/strong&gt; as soon as Gitea modifies the manifest.&lt;/p&gt;
&lt;p&gt;Everything happens while maintaining high reliability and without downtime.&lt;/p&gt;
&lt;h3 id="the-big-picture"&gt;The big picture
&lt;/h3&gt;&lt;p&gt;Below you can see the diagram of how my workflow and the pipeline between Gitea, ArgoCD, and Kubernetes work.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://lazzarotto.dev/blog/img/blog_workflow.webp"
loading="lazy"
alt="Workflow diagram"
&gt;&lt;/p&gt;
&lt;h2 id="final-thoughts-was-it-the-right-move"&gt;Final thoughts: was it the right move?
&lt;/h2&gt;&lt;p&gt;So, was it the right move? Absolutely, yes.
I traded the simplicity of a managed service for the total flexibility of my own solution, solving my specific problem and building a platform I can experiment on for a long time. For me, it&amp;rsquo;s a win on all fronts.&lt;/p&gt;
&lt;p&gt;Now let&amp;rsquo;s compare the pros and cons of this solution.&lt;/p&gt;
&lt;h3 id="pros"&gt;Pros
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;Complete control over the workflow&lt;/li&gt;
&lt;li&gt;Private source code in my homelab, meaning it won&amp;rsquo;t be used by Github for &lt;strong&gt;Copilot&lt;/strong&gt; training&lt;/li&gt;
&lt;li&gt;No costs (obviously, I&amp;rsquo;m not counting the biggest cost here: my time; but since the goal was also to practice with new tools, I consider it a training investment in all respects)&lt;/li&gt;
&lt;li&gt;Ability to test different types of workflows and practice with the tools&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="cons"&gt;Cons
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;Uptime not at the same level as Cloudflare (SPOF everywhere—after all, it&amp;rsquo;s my lab and I have no redundancy)&lt;/li&gt;
&lt;li&gt;Speed might be slightly lower (but I still use Cloudflare for caching)&lt;/li&gt;
&lt;li&gt;More elements that can break and therefore potentially more time wasted fixing what breaks.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="coming-soon-how-i-automated-everything-with-argocd"&gt;Coming soon: how I automated everything with ArgoCD
&lt;/h2&gt;&lt;p&gt;In the next article on this blog, I will analyze in detail how I implemented the automatic deployment via ArgoCD.&lt;/p&gt;</description></item><item><title>Attack on LiteLLM: Why pip install betrayed you and requirements.txt saved you</title><link>https://lazzarotto.dev/blog/en/attack-on-litellm-why-pip-install-betrayed-you-and-requirements.txt-saved-you/</link><pubDate>Wed, 25 Mar 2026 00:00:00 +0100</pubDate><author>postmaster@mlazzarotto.it (Marco Lazzarotto)</author><guid>https://lazzarotto.dev/blog/en/attack-on-litellm-why-pip-install-betrayed-you-and-requirements.txt-saved-you/</guid><description>&lt;img src="https://lazzarotto.dev/blog/" alt="Featured image of post Attack on LiteLLM: Why pip install betrayed you and requirements.txt saved you" /&gt;&lt;h2 id="introduction-the-illusion-of-security-in-an-innocent-command"&gt;Introduction: The Illusion of Security in an Innocent Command
&lt;/h2&gt;&lt;p&gt;Who doesn&amp;rsquo;t know the &lt;code&gt;pip&lt;/code&gt; command? It&amp;rsquo;s one of the most used commands for anyone developing in Python or regularly using open-source software distributed on the &lt;strong&gt;PyPi&lt;/strong&gt; repository. &lt;code&gt;pip&lt;/code&gt; is that command that (at least in my experience) never disappoints and is practically essential (though it&amp;rsquo;s recently been giving way to other tools like &lt;strong&gt;Poetry&lt;/strong&gt; and &lt;strong&gt;uv&lt;/strong&gt;) for anyone needing to install libraries for Python development. But sometimes, this sense of security can lead us into a nasty trap.&lt;/p&gt;
&lt;p&gt;But what happens when this trust is betrayed? That&amp;rsquo;s exactly what happened on March 24, 2026.
A carefully laid trap by a hacker group known as &lt;strong&gt;&lt;a class="link" href="https://www.redhotcyber.com/post/il-piu-grande-supply-chain-attack-e-servito-1000-ambienti-saas-compromessi/" target="_blank" rel="noopener"
&gt;TeamPCP&lt;/a&gt;&lt;/strong&gt; turned &lt;code&gt;pip install&lt;/code&gt; into a weapon to harvest secrets (like system variables, SSH keys, cloud provider credentials) and attempt lateral movement through Kubernetes clusters.&lt;/p&gt;
&lt;h2 id="anatomy-of-a-foretold-disaster-what-happened-to-litellm"&gt;Anatomy of a Foretold Disaster: What Happened to LiteLLM?
&lt;/h2&gt;&lt;p&gt;The injected payload was no joke: it was a credential stealer designed to scour the environment for env vars, SSH keys, AWS/GCP/Azure credentials, and Kubernetes tokens, then exfiltrate everything to a malicious domain (models.litellm.cloud).&lt;/p&gt;
&lt;p&gt;The team behind &lt;strong&gt;LiteLLM&lt;/strong&gt; has CI/CD workflows to take their software from source code to a package on the PyPi repo. One of these workflows is responsible for scanning their code and dependencies (using &lt;strong&gt;Trivy&lt;/strong&gt;) before distributing the package.&lt;/p&gt;
&lt;p&gt;And it was precisely &lt;strong&gt;Trivy&lt;/strong&gt; (apparently) that allowed the &lt;strong&gt;TeamPCP&lt;/strong&gt; group to bypass this security workflow to upload the two vulnerable versions of LiteLLM to PyPi.&lt;/p&gt;
&lt;h2 id="the-betrayal-of-pip-install-package"&gt;The Betrayal of &lt;code&gt;pip install &amp;lt;package&amp;gt;&lt;/code&gt;
&lt;/h2&gt;&lt;p&gt;As I wrote above, the &lt;strong&gt;pip&lt;/strong&gt; command is that one command you usually have blind faith in, entrusting it with the task of updating or installing the various necessary libraries. By using it, however, we hand over &amp;ldquo;control&amp;rdquo; to it.&lt;/p&gt;
&lt;p&gt;Leaving control to pip, at least in this case, led to the entry of vulnerable LiteLLM versions into thousands of systems, which became insecure and potentially compromised. This happened simply because the decision was made to update libraries without caring which version was being installed.&lt;/p&gt;
&lt;p&gt;And this isn&amp;rsquo;t just theory. The LiteLLM team confirmed that those who used their official Docker image were not affected. Why? Simple: that image used a &lt;code&gt;requirements.txt&lt;/code&gt; file with pinned versions! It&amp;rsquo;s concrete proof that this practice, on its own, acted as a shield for who knows how many companies.&lt;/p&gt;
&lt;h3 id="example-of-an-insecure-dockerfile"&gt;Example of an Insecure Dockerfile
&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;# VULNERABLE Dockerfile
FROM python:3.11-slim
WORKDIR /app
RUN pip install litellm fastapi uvicorn
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A build based on a Dockerfile like this, executed during the attack&amp;rsquo;s time window, would have installed version 1.82.7 without batting an eye, opening a huge security hole in the infrastructure.&lt;/p&gt;
&lt;h2 id="the-unexpected-hero-requirementstxt-and-the-power-of-pinning"&gt;The Unexpected Hero: &lt;code&gt;requirements.txt&lt;/code&gt; and the Power of Pinning
&lt;/h2&gt;&lt;p&gt;With &lt;strong&gt;pinning&lt;/strong&gt;, we&amp;rsquo;re telling pip, &amp;ldquo;install this version and never, ever update it.&amp;rdquo; In practice, we&amp;rsquo;re virtually fixing a specific version with a thumbtack. This is not just a best practice (in Python, as well as in Docker or other package management software) to avoid &lt;strong&gt;breaking changes&lt;/strong&gt; introduced in new packages, but it also prevents the reckless installation of new versions with potential &lt;em&gt;problems&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;One of those problems it prevents, in fact, (though in a relative sense) is the installation of new, untested versions that are potentially affected by major bugs or vulnerabilities.&lt;/p&gt;
&lt;h2 id="leveling-up-from-a-quick-fix-to-a-supply-chain-security-strategy"&gt;Leveling Up: From a Quick Fix to a Supply-Chain Security Strategy
&lt;/h2&gt;&lt;p&gt;Supply-chain security is one of the most critical challenges for modern companies that distribute software. Part of the challenge is that there is no single, functional definition of supply-chain security, and it&amp;rsquo;s a challenge that spans an extremely broad area, including everything from physical threats to cyber threats (as in this case).&lt;/p&gt;
&lt;p&gt;If we take, for example, a piece of software like LiteLLM, whose &lt;a class="link" href="https://github.com/BerriAI/litellm/blob/main/requirements.txt" target="_blank" rel="noopener"
&gt;&lt;code&gt;requirements.txt&lt;/code&gt;&lt;/a&gt; file contains about &lt;strong&gt;80 lines of dependencies&lt;/strong&gt;, and if we consider that each dependency will likely use other dependencies, it creates a chain of opportunity for an attacker to potentially infiltrate tens or hundreds of thousands of different hosts.&lt;/p&gt;
&lt;p&gt;So what actions should be taken to prevent an attack of this type?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vendor and Library Assessment: it&amp;rsquo;s necessary to carefully evaluate every library (whether open-source or commercial) before integration, checking the maintainer&amp;rsquo;s reputation;&lt;/li&gt;
&lt;li&gt;Secure Development Practices: adopt the NIST SSDF framework to define security requirements and automatic scans with tools like Dependabot or Snyk, and integrate code signing tools to ensure the distributed software has not been tampered with during the build or distribution;&lt;/li&gt;
&lt;li&gt;Immediate Practical Solutions: use Sigstore and Cosign for code signing in CI/CD pipelines to sign containers and binaries.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusions-trust-is-good-control-is-better"&gt;Conclusions: Trust is Good, Control is Better
&lt;/h2&gt;&lt;p&gt;Trust in the open-source world is certainly fundamental, but it must be paired with robust security practices, like &lt;strong&gt;pinning&lt;/strong&gt; or tools like &lt;strong&gt;Dependabot&lt;/strong&gt; that verify the dependencies used.&lt;/p&gt;
&lt;p&gt;In your opinion, will attacks like this become the new normal in the open-source world?&lt;/p&gt;
&lt;p&gt;Protecting the software supply chain isn&amp;rsquo;t an option, it&amp;rsquo;s a necessity. If the security of your CI/CD pipelines is a priority and you want to understand how to implement robust strategies like those described in this article, visit my &lt;a class="link" href="https://lazzarotto.dev" target="_blank" rel="noopener"
&gt;portfolio&lt;/a&gt; to discover how I can help you.&lt;/p&gt;</description></item><item><title>Kubernetes, Longhorn, and Non-Root Images: A Permissions Fix Chronicle</title><link>https://lazzarotto.dev/blog/en/kubernetes-longhorn-and-non-root-images-a-permissions-fix-chronicle/</link><pubDate>Sun, 08 Feb 2026 00:00:00 +0000</pubDate><author>postmaster@mlazzarotto.it (Marco Lazzarotto)</author><guid>https://lazzarotto.dev/blog/en/kubernetes-longhorn-and-non-root-images-a-permissions-fix-chronicle/</guid><description>&lt;img src="https://lazzarotto.dev/blog/" alt="Featured image of post Kubernetes, Longhorn, and Non-Root Images: A Permissions Fix Chronicle" /&gt;&lt;h1 id="intro"&gt;Intro
&lt;/h1&gt;&lt;p&gt;For my new venture as a freelance &lt;strong&gt;DevOps Engineer&lt;/strong&gt;, I decided to build a website using &lt;strong&gt;Flask&lt;/strong&gt; as the backend and a template (named &amp;ldquo;Simone - Personal Portfolio Template&amp;rdquo;), purchased from ThemeForest, as the frontend.&lt;br&gt;
Nothing too complicated; HTML, CSS, and Javascript do 90% of the work, and the remaining 10% consists of the Flask backend, which handles internal functionalities like the contact form with Captcha, page routing, the endpoint for Kubernetes &lt;em&gt;livenessProbe&lt;/em&gt;, and all the search engine optimization bits (robots.txt and sitemap).&lt;/p&gt;
&lt;p&gt;For a couple of months now, I&amp;rsquo;ve been hosting this website &lt;a class="link" href="https://lazzarotto.dev/?utm_source=mlazzarotto.it&amp;amp;utm_medium=hugo_post&amp;amp;utm_campaign=internal_referral" target="_blank" rel="noopener"
&gt;lazzarotto.dev&lt;/a&gt; on my homelab using Kubernetes on my 3-node K3s cluster, with two replicas for HA (definitely overkill) and &lt;strong&gt;Longhorn&lt;/strong&gt; for storage (actually only used for Flask&amp;rsquo;s log files).
Until last week, I was using the most common image for a Flask website, which is &lt;code&gt;python:3.14-slim&lt;/code&gt;; it&amp;rsquo;s a Docker image based on the well-known Linux distro &lt;strong&gt;Debian Trixie&lt;/strong&gt;.&lt;/p&gt;
&lt;h1 id="non-root-and-distroless-image"&gt;Non-Root and Distroless Image
&lt;/h1&gt;&lt;p&gt;The website was working fine with the previously mentioned Docker image, but for a while now, I had been hearing about these &lt;em&gt;distroless&lt;/em&gt; and non-root images on Reddit (and elsewhere), which offered a higher level of security compared to traditional Docker images.&lt;br&gt;
For this reason, and because I wanted to &amp;ldquo;get my hands dirty&amp;rdquo; and learn, I decided to convert my &lt;strong&gt;Dockerfile&lt;/strong&gt; to have a root-less and distro-less Docker image for my website.&lt;/p&gt;
&lt;p&gt;So, I still used the &lt;code&gt;python:3.14-slim&lt;/code&gt; image as the &lt;strong&gt;builder&lt;/strong&gt; stage in my Dockerfile, but I used &lt;code&gt;gcr.io/distroless/python3-debian13:nonroot&lt;/code&gt; as the &lt;strong&gt;release&lt;/strong&gt; stage.&lt;br&gt;
So, I built the image, deployed it to Kubernetes with the new image, and the pod started, but it would crash after a few seconds. Looking at the pod&amp;rsquo;s logs, I discovered that &lt;em&gt;loguru&lt;/em&gt; (the logging library) couldn&amp;rsquo;t write the log file to the folder that was supposed to be mounted by the Longhorn PVC.&lt;/p&gt;
&lt;h1 id="troubleshooting-a-distroless-container"&gt;Troubleshooting a Distroless Container
&lt;/h1&gt;&lt;p&gt;If you&amp;rsquo;ve ever worked with a &lt;em&gt;distroless&lt;/em&gt; container, you&amp;rsquo;ll surely know how complicated it is to troubleshoot an app inside one. For those who don&amp;rsquo;t know, a &lt;em&gt;distroless&lt;/em&gt; image is a Docker image that is missing any basic Linux programs, including: &lt;code&gt;apt&lt;/code&gt;/&lt;code&gt;yum&lt;/code&gt;, &lt;code&gt;ls&lt;/code&gt;, &lt;code&gt;tar&lt;/code&gt;, &lt;code&gt;sh&lt;/code&gt;, or &lt;code&gt;bash&lt;/code&gt;. And so, it&amp;rsquo;s impossible to use &lt;code&gt;docker exec&lt;/code&gt; or &lt;code&gt;kubectl exec&lt;/code&gt; to get into the container and check the status of the mountpoint in question.&lt;/p&gt;
&lt;p&gt;At this point, my thought was, &amp;ldquo;I&amp;rsquo;ve changed two things: the distro and the user running the app.&amp;rdquo; And I had no idea which of the two changes to the image had introduced the log writing issue.&lt;br&gt;
What I had left to do was to rebuild the Docker image, still without a &amp;ldquo;root&amp;rdquo; user, but with a base distro in order to have access to at least &lt;code&gt;ls&lt;/code&gt;, &lt;code&gt;touch&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;I should point out that all of this was happening while my website was offline because I don&amp;rsquo;t have a testing environment in my homelab (not yet, at least).&lt;/p&gt;
&lt;p&gt;Once I deployed the pod with the new &amp;ldquo;debug&amp;rdquo; image, I was able to log into the pod (or rather, into the pod&amp;rsquo;s container) and realize that the &lt;code&gt;/app/logs&lt;/code&gt; folder wasn&amp;rsquo;t owned by the current user (if I remember correctly, it should be a user with UID 1000), but was instead owned by &lt;strong&gt;root&lt;/strong&gt;.&lt;/p&gt;
&lt;h1 id="how-to-waste-time-asking-gemini-for-the-solution"&gt;How to Waste Time Asking Gemini for the Solution
&lt;/h1&gt;&lt;p&gt;I initially turned to Gemini 2.5 Flash (I know, I should have used the Pro model, but I thought the solution would be simple) to find a solution to the permissions problem with the &lt;code&gt;logs&lt;/code&gt; folder.&lt;br&gt;
Its answers seemed really good, and it was always very &lt;em&gt;self-confident&lt;/em&gt;, as is always the case with its hallucinations, ça va sans dire.
I spent about half an hour on it, but in the end, I gave up and went back to searching on Google, the &amp;ldquo;old-fashioned&amp;rdquo; way.&lt;/p&gt;
&lt;h1 id="the-solution"&gt;The Solution
&lt;/h1&gt;&lt;p&gt;The solution came from an &lt;em&gt;&lt;a class="link" href="https://github.com/longhorn/longhorn/issues/8088#issuecomment-1986701122" target="_blank" rel="noopener"
&gt;issue&lt;/a&gt;&lt;/em&gt; on the &lt;strong&gt;Longhorn&lt;/strong&gt; project&amp;rsquo;s GitHub repo that saved me.
In short, the problem arises because Longhorn, when it &amp;ldquo;mounts&amp;rdquo; the PVC in the container, sets the owner user and group to &lt;em&gt;root&lt;/em&gt;, which prevents the &lt;em&gt;non-root&lt;/em&gt; user in my image from accessing that PVC.&lt;/p&gt;
&lt;p&gt;It still took me some time to put the pieces together, I must admit, but in the end, I chose solution number 7 (&lt;strong&gt;Case 7&lt;/strong&gt;), which was the best for my use case because it didn&amp;rsquo;t require me to create custom &lt;em&gt;StorageClasses&lt;/em&gt; or overhaul my manifests, but to act directly on the &lt;em&gt;securityContext&lt;/em&gt; of my Deployment.&lt;br&gt;
Basically, it&amp;rsquo;s about telling Kubernetes: &amp;lsquo;Hey, when you mount this volume, please make sure it belongs to a group that my application user can write to.&amp;rsquo; Simple and effective.&lt;/p&gt;
&lt;h1 id="conclusions-and-manifest"&gt;Conclusions and Manifest
&lt;/h1&gt;&lt;p&gt;So there you have it, an entire afternoon of debugging to add three lines of YAML to my &lt;em&gt;securityContext&lt;/em&gt;. But beyond the technical fix, this experience reminded me of two important things.&lt;br&gt;
First: minimal and non-root images are fantastic for security, but they can make debugging a real nightmare if you&amp;rsquo;re not prepared. Second: never underestimate the power of an &amp;ldquo;old-fashioned&amp;rdquo; search on GitHub issues when AI leads you astray.&lt;/p&gt;
&lt;p&gt;Now my site is back online, more secure than before, and I&amp;rsquo;ve learned a valuable lesson about storage permissions in Kubernetes.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m adding the relevant part of my manifest that I used for the app&amp;rsquo;s deployment below.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;apps/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deployment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;lazzarotto-dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;dmz&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;lazzarotto-dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matchLabels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;lazzarotto-dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;revisionHistoryLimit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;lazzarotto-dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;securityContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runAsGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runAsNonRoot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runAsUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fsGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;lazzarotto-dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;git.mlazzarotto.it/marco/lazzarotto_dev:20260205-113032&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Always&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;...]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>Boosting Navidrome Security: SSO Auth with Traefik and Authentik</title><link>https://lazzarotto.dev/blog/en/boosting-navidrome-security-sso-auth-with-traefik-and-authentik/</link><pubDate>Sun, 11 May 2025 00:00:00 +0000</pubDate><author>postmaster@mlazzarotto.it (Marco Lazzarotto)</author><guid>https://lazzarotto.dev/blog/en/boosting-navidrome-security-sso-auth-with-traefik-and-authentik/</guid><description>&lt;img src="https://lazzarotto.dev/blog/" alt="Featured image of post Boosting Navidrome Security: SSO Auth with Traefik and Authentik" /&gt;&lt;h1 id="introduction"&gt;Introduction
&lt;/h1&gt;&lt;p&gt;Music streaming services like Navidrome provide a fantastic way to access your personal music collection from anywhere. However, exposing such services to the internet comes with security concerns.&lt;/p&gt;
&lt;p&gt;This guide demonstrates how to secure your Navidrome instance using Authentik&amp;rsquo;s Single Sign-On (SSO) capabilities behind a Traefik reverse proxy.&lt;/p&gt;
&lt;p&gt;By implementing this setup, you&amp;rsquo;ll add an additional security layer to your music server while maintaining convenient access for legitimate users.&lt;/p&gt;
&lt;h2 id="assumptions-and-dns-records"&gt;Assumptions and DNS Records
&lt;/h2&gt;&lt;p&gt;This guide assumes you have already installed and configured:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Authentik as your identity provider&lt;/li&gt;
&lt;li&gt;Traefik as your reverse proxy&lt;/li&gt;
&lt;li&gt;Navidrome as your music streaming server&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the purposes of this tutorial, we&amp;rsquo;ll use the following domains:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;music.example.com&lt;/code&gt;: Your public-facing Navidrome instance&lt;/li&gt;
&lt;li&gt;&lt;code&gt;example.com&lt;/code&gt;: Your external domain&lt;/li&gt;
&lt;li&gt;&lt;code&gt;internal.example.com&lt;/code&gt;: Your internal subdomain&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="understanding-the-authentication-flow"&gt;Understanding The Authentication Flow
&lt;/h1&gt;&lt;p&gt;The authentication flow works as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A user attempts to access &lt;code&gt;music.example.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Traefik intercepts the request and forwards it to Authentik via the &lt;code&gt;authentik-forward-auth&lt;/code&gt; middleware&lt;/li&gt;
&lt;li&gt;If the user isn&amp;rsquo;t authenticated, they&amp;rsquo;re redirected to the Authentik login page&lt;/li&gt;
&lt;li&gt;After successful authentication, Authentik sends the user back to Navidrome along with authentication headers&lt;/li&gt;
&lt;li&gt;Navidrome reads these headers to identify the user and grants access accordingly&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Importantly, our configuration deliberately excludes authentication for two specific paths:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/share/&lt;/code&gt;: Allows anonymous access to shared music links&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/rest/&lt;/code&gt;: Permits API access for mobile applications like Symfonium or DSub&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This selective authentication provides both security and functionality where needed.&lt;/p&gt;
&lt;h1 id="authentik-configuration"&gt;Authentik Configuration
&lt;/h1&gt;&lt;h2 id="application-and-provider"&gt;Application and Provider
&lt;/h2&gt;&lt;p&gt;To set up Navidrome in Authentik:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;From the Authentik admin dashboard, select &amp;ldquo;Applications&amp;rdquo; and click &amp;ldquo;Create with Provider&amp;rdquo;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On the &amp;ldquo;Application&amp;rdquo; screen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enter &amp;ldquo;Navidrome&amp;rdquo; as the name&lt;/li&gt;
&lt;li&gt;Set an appropriate slug (e.g., &amp;ldquo;navidrome&amp;rdquo;)&lt;/li&gt;
&lt;li&gt;Add an optional description and icon&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On the &amp;ldquo;Choose A Provider&amp;rdquo; screen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Select &amp;ldquo;Proxy Provider&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On the &amp;ldquo;Configure Provider&amp;rdquo; screen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enter a name for the provider (e.g., &amp;ldquo;navidrome-proxy&amp;rdquo;)&lt;/li&gt;
&lt;li&gt;Select the authentication flow that you typically use&lt;/li&gt;
&lt;li&gt;Choose &amp;ldquo;Forward auth (single application)&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Enter &lt;code&gt;https://music.example.com&lt;/code&gt; as the external host&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Skip the &amp;ldquo;Configure Bindings&amp;rdquo; screen and complete the wizard&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="outpost"&gt;Outpost
&lt;/h2&gt;&lt;p&gt;Next, connect your application to the Authentik outpost:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Navigate to &amp;ldquo;Outposts&amp;rdquo; in the Authentik admin interface&lt;/li&gt;
&lt;li&gt;Edit the &amp;ldquo;authentik Embedded Outpost&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Under the &amp;ldquo;Applications&amp;rdquo; section, add your newly created Navidrome application&lt;/li&gt;
&lt;li&gt;Save the changes&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id="traefik-configuration"&gt;Traefik Configuration
&lt;/h1&gt;&lt;p&gt;The Traefik configuration manages traffic routing and authentication. Here&amp;rsquo;s the setup:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Traefik configuration for Navidrome with Authentik SSO&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;routers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;to-authentik-outpost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entryPoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;webSecure&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Host(`music.example.com`) &amp;amp;&amp;amp; PathPrefix(`/outpost.goauthentik.io/`)&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;authentik&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;middlewares&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;authentik-forward-auth&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;to-protected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entryPoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;webSecure&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Host(`music.example.com`) &amp;amp;&amp;amp; !(PathPrefix(`/share/`) || PathPrefix(`/rest/`))&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;navidrome&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;middlewares&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;authentik-forward-auth&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;to-subsonic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entryPoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;webSecure&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Host(`music.example.com`) &amp;amp;&amp;amp; PathPrefix(`/rest/`)&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;navidrome&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;150&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;to-navidrome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entryPoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;webSecure&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Host(`music.example.com`) &amp;amp;&amp;amp; PathPrefix(`/share/`)&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;navidrome&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;navidrome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;loadBalancer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;servers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;http://navidrome.internal.example.com:4533&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;authentik&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;loadBalancer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;servers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;https://authentik.internal.example.com:9443&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;middlewares&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;authentik-forward-auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;forwardAuth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;http://authentik.internal.example.com:9000/outpost.goauthentik.io/auth/traefik&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;trustForwardHeader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;authResponseHeaders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;X-authentik-username&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;X-authentik-groups&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;X-authentik-entitlements&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;X-authentik-email&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;X-authentik-name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;X-authentik-uid&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;X-authentik-jwt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;X-authentik-meta-jwks&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;X-authentik-meta-outpost&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;X-authentik-meta-provider&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;X-authentik-meta-app&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;X-authentik-meta-version&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="configuration-breakdown"&gt;Configuration Breakdown
&lt;/h2&gt;&lt;p&gt;The Traefik configuration consists of several key components:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Routers:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;to-authentik-outpost&lt;/code&gt;: Handles Authentik-specific paths with highest priority (200)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;to-protected&lt;/code&gt;: Routes general Navidrome traffic through authentication&lt;/li&gt;
&lt;li&gt;&lt;code&gt;to-subsonic&lt;/code&gt;: Allows unauthenticated access to the Subsonic API (&lt;code&gt;/rest/&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;to-navidrome&lt;/code&gt;: Permits direct access to shared links (&lt;code&gt;/share/&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Services:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;navidrome&lt;/code&gt;: Points to your Navidrome instance&lt;/li&gt;
&lt;li&gt;&lt;code&gt;authentik&lt;/code&gt;: Points to your Authentik instance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Middleware:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;authentik-forward-auth&lt;/code&gt;: The critical component that forwards authentication requests to Authentik and passes user information via headers to Navidrome&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The configuration uses routing priorities to ensure that authentication exceptions work correctly, with higher numbers taking precedence.&lt;/p&gt;
&lt;h1 id="navidrome-configuration"&gt;Navidrome Configuration
&lt;/h1&gt;&lt;p&gt;For Navidrome to properly integrate with this authentication setup, you&amp;rsquo;ll need to configure two important settings:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;ReverseProxyWhitelist&lt;/code&gt;: Set this to the IP address of your Traefik server to ensure Navidrome trusts the authentication headers&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ReverseProxyUserHeader&lt;/code&gt;: Set this to &lt;code&gt;X-authentik-username&lt;/code&gt; so Navidrome knows which header contains the username&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can configure these settings in your Navidrome environment variables or configuration file:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ND_REVERSEPROXYWHITELIST=&amp;lt;your_traefik_ip&amp;gt;
ND_REVERSEPROXYUSERHEADER=X-authentik-username
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For more configuration options, refer to the &lt;a class="link" href="https://www.navidrome.org/docs/usage/configuration-options/#advanced-configuration" target="_blank" rel="noopener"
&gt;Navidrome documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id="conclusion"&gt;Conclusion
&lt;/h1&gt;&lt;p&gt;With this setup complete, your Navidrome instance is now secured with Authentik SSO. When users visit &lt;code&gt;music.example.com&lt;/code&gt;, they&amp;rsquo;ll be presented with the Authentik login page. After successful authentication, they&amp;rsquo;ll gain access to Navidrome.&lt;/p&gt;
&lt;p&gt;One of the most convenient aspects of this integration is automatic user provisioning. If a user authenticates through Authentik but doesn&amp;rsquo;t yet exist in Navidrome, a new account will be created automatically using the username from Authentik (and a random password).&lt;/p&gt;
&lt;p&gt;This approach provides robust security while maintaining convenient access for legitimate users, with selective authentication bypassing for specific functionality like shared links and mobile app access.&lt;/p&gt;
&lt;h1 id="resources-used"&gt;Resources Used
&lt;/h1&gt;&lt;p&gt;&lt;a class="link" href="https://github.com/brokenscripts/authentik_traefik" target="_blank" rel="noopener"
&gt;https://github.com/brokenscripts/authentik_traefik&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://www.navidrome.org/docs/usage/reverse-proxy" target="_blank" rel="noopener"
&gt;https://www.navidrome.org/docs/usage/reverse-proxy&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://docs.goauthentik.io/docs/add-secure-apps/providers/proxy/server_traefik" target="_blank" rel="noopener"
&gt;https://docs.goauthentik.io/docs/add-secure-apps/providers/proxy/server_traefik&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Exposing Seafile 12 Behind Traefik: The Complete Configuration Guide</title><link>https://lazzarotto.dev/blog/en/exposing-seafile-12-behind-traefik-the-complete-configuration-guide/</link><pubDate>Thu, 01 May 2025 00:00:00 +0000</pubDate><author>postmaster@mlazzarotto.it (Marco Lazzarotto)</author><guid>https://lazzarotto.dev/blog/en/exposing-seafile-12-behind-traefik-the-complete-configuration-guide/</guid><description>&lt;img src="https://lazzarotto.dev/blog/" alt="Featured image of post Exposing Seafile 12 Behind Traefik: The Complete Configuration Guide" /&gt;&lt;h1 id="introduction"&gt;Introduction
&lt;/h1&gt;&lt;p&gt;Seafile is a powerful, open-source file syncing and sharing platform that provides an alternative to commercial cloud storage services. When deploying Seafile in a production environment, you&amp;rsquo;ll typically want to place it behind a reverse proxy like Traefik to handle SSL termination, routing, and additional security features.&lt;/p&gt;
&lt;p&gt;In this guide, I&amp;rsquo;ll walk you through the exact Traefik configuration needed to properly expose Seafile 12 to the internet. This setup handles all three essential components of Seafile: the web interface, the file transfer service (seafhttp), and WebDAV access (seafdav).&lt;/p&gt;
&lt;h1 id="understanding-seafiles-architecture"&gt;Understanding Seafile&amp;rsquo;s Architecture
&lt;/h1&gt;&lt;p&gt;Before diving into the configuration, it&amp;rsquo;s important to understand that Seafile consists of multiple services that need to be properly routed:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Main Web UI&lt;/strong&gt; - The primary interface for users to interact with Seafile&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Seafhttp&lt;/strong&gt; - Handles file uploads and downloads&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Seafdav&lt;/strong&gt; - Provides WebDAV access to files&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each of these services needs its own routing rules in Traefik.&lt;/p&gt;
&lt;h1 id="traefik-configuration"&gt;Traefik Configuration
&lt;/h1&gt;&lt;p&gt;Let&amp;rsquo;s start with the complete Traefik configuration needed for Seafile 12:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;routers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;routerSeafile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entryPoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;websecure&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;seafile&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Host(`sf.mydomain.com`)&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;middlewares&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;HSTS&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{}&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;routerSeafHttp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entryPoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;websecure&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;seafhttp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Host(`sf.mydomain.com`) &amp;amp;&amp;amp; PathPrefix(`/seafhttp`)&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;middlewares&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;HSTS&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;seafhttp-strip-prefix&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;removeDuplicateSlashes&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{}&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;routerSeafdav&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;entryPoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;websecure&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;seafdav&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Host(`sf.mydomain.com`) &amp;amp;&amp;amp; PathPrefix(`/seafdav`)&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;middlewares&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;HSTS&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{}&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;seafile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;loadBalancer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;servers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;http://seafile.local:8000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;serversTransport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;insecureTransport&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;seafhttp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;loadBalancer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;servers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;http://seafile.local:8082&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;serversTransport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;insecureTransport&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;seafdav&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;loadBalancer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;servers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;http://seafile.local:8080&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;serversTransport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;insecureTransport&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="middleware-configuration"&gt;Middleware Configuration
&lt;/h1&gt;&lt;p&gt;In addition to the core routing rules, we need to define some middlewares to handle security and path manipulation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;middlewares&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;HSTS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stsSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;15552000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stsIncludeSubdomains&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;forceSTSHeader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stsPreload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;frameDeny&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;browserXssFilter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;seafhttp-strip-prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stripPrefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;prefixes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;/seafhttp&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;forceSlash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;removeDuplicateSlashes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;replacePathRegex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/{2,}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;replacement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;serversTransports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;insecureTransport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;insecureSkipVerify&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="configuration-breakdown"&gt;Configuration Breakdown
&lt;/h1&gt;&lt;p&gt;Let&amp;rsquo;s break down the key parts of this configuration:&lt;/p&gt;
&lt;h2 id="1-router-definitions"&gt;1. Router Definitions
&lt;/h2&gt;&lt;p&gt;We define three separate routers, each handling a different component of Seafile:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;routerSeafile&lt;/strong&gt;: Routes requests to the main Seafile UI&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;routerSeafHttp&lt;/strong&gt;: Routes file transfer requests to the seafhttp service&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;routerSeafdav&lt;/strong&gt;: Routes WebDAV requests to the seafdav service&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="2-routing-rules"&gt;2. Routing Rules
&lt;/h2&gt;&lt;p&gt;Each router uses a combination of hostname and path-based rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Main UI: Matches requests to &lt;code&gt;sf.mydomain.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Seafhttp: Matches requests to &lt;code&gt;sf.mydomain.com/seafhttp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Seafdav: Matches requests to &lt;code&gt;sf.mydomain.com/seafdav&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="3-services"&gt;3. Services
&lt;/h2&gt;&lt;p&gt;Each router is connected to its corresponding service, which defines where Traefik should proxy the requests:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Main UI: Proxied to &lt;code&gt;http://seafile.local:8000&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Seafhttp: Proxied to &lt;code&gt;http://seafile.local:8082&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Seafdav: Proxied to &lt;code&gt;http://seafile.local:8080&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="4-middlewares"&gt;4. Middlewares
&lt;/h2&gt;&lt;p&gt;We apply several important middlewares:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HSTS&lt;/strong&gt;: Enforces HTTPS usage by clients&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;seafhttp-strip-prefix&lt;/strong&gt;: Removes the &lt;code&gt;/seafhttp&lt;/code&gt; prefix before forwarding to the backend&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;removeDuplicateSlashes&lt;/strong&gt;: Cleans up URLs with duplicate slashes. This was made necessary for me after the migration from Seafile 11 to 12, but you may not need it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="5-security-considerations"&gt;5. Security Considerations
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;insecureTransport&lt;/code&gt; setting allows Traefik to communicate with internal services using HTTP&lt;/li&gt;
&lt;li&gt;HSTS headers ensure clients always use HTTPS for future connections&lt;/li&gt;
&lt;li&gt;Additional security headers protect against common web vulnerabilities&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="conclusion"&gt;Conclusion
&lt;/h1&gt;&lt;p&gt;This configuration provides a secure and efficient way to expose Seafile 12 behind Traefik. The setup properly handles all three components of Seafile, ensuring that users can access the web interface, upload/download files, and use WebDAV functionality.&lt;/p&gt;
&lt;p&gt;By using Traefik as a reverse proxy, you gain several benefits including automatic SSL certificate management (if configured), powerful routing capabilities, and additional security layers. This particular configuration focuses on the specific routing needs of Seafile while implementing security best practices.&lt;/p&gt;
&lt;p&gt;Remember that this configuration assumes you&amp;rsquo;ve already set up Seafile correctly and that it&amp;rsquo;s accessible internally at the defined addresses. The configuration also assumes that you have Traefik properly configured with SSL certificates for the specified domain.&lt;/p&gt;</description></item><item><title>Boosting Seafile Security: Hiding Login Fields When Using SSO</title><link>https://lazzarotto.dev/blog/en/boosting-seafile-security-hiding-login-fields-when-using-sso/</link><pubDate>Sat, 26 Apr 2025 00:00:00 +0000</pubDate><author>postmaster@mlazzarotto.it (Marco Lazzarotto)</author><guid>https://lazzarotto.dev/blog/en/boosting-seafile-security-hiding-login-fields-when-using-sso/</guid><description>&lt;img src="https://lazzarotto.dev/blog/" alt="Featured image of post Boosting Seafile Security: Hiding Login Fields When Using SSO" /&gt;&lt;h1 id="boosting-seafile-security-hiding-login-fields-when-using-sso"&gt;Boosting Seafile Security: Hiding Login Fields When Using SSO
&lt;/h1&gt;&lt;p&gt;When it comes to securing your Seafile instance, the small details make a world of difference. Today, I&amp;rsquo;m sharing a simple yet powerful security enhancement that takes just minutes to implement but provides significant protection for your data fortress.&lt;/p&gt;
&lt;h2 id="the-problem-dual-login-methods-create-risk"&gt;The Problem: Dual Login Methods Create Risk
&lt;/h2&gt;&lt;p&gt;If you&amp;rsquo;ve set up Single Sign-On (SSO) with services like Authentik or Authelia for your Seafile instance, congratulations! You&amp;rsquo;ve taken a major step toward improving your security posture. However, there&amp;rsquo;s a sneaky vulnerability that often goes unaddressed.&lt;/p&gt;
&lt;p&gt;Even with SSO configured, Seafile continues to display its native username and password login fields by default. This creates an unnecessary attack vector – it&amp;rsquo;s like installing a state-of-the-art security system for your home but leaving a side door unlocked.&lt;/p&gt;
&lt;p&gt;Potential attackers can ignore your shiny SSO implementation and hammer away at the traditional login, attempting to brute force their way into your system. This is particularly concerning because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It undermines the security benefits of your SSO implementation&lt;/li&gt;
&lt;li&gt;The traditional login might have weaker protection against repeated attempts&lt;/li&gt;
&lt;li&gt;Users might get confused about which login method to use&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="the-solution-css-to-the-rescue"&gt;The Solution: CSS to the Rescue
&lt;/h2&gt;&lt;p&gt;The fix is delightfully simple – a few lines of CSS can completely remove those vulnerable login fields from view. The beauty of this approach is that it doesn&amp;rsquo;t just hide the fields visually; it prevents any interaction with them.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the magical CSS snippet you need:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-css" data-lang="css"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;login-panel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;login-form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;login-panel-hd&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="how-to-implement-this-fix"&gt;How to Implement This Fix
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;Log in to your Seafile administration panel&lt;/li&gt;
&lt;li&gt;Navigate to the system settings section&lt;/li&gt;
&lt;li&gt;Look for the custom CSS field under &lt;strong&gt;Settings&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Paste the CSS snippet shown above&lt;/li&gt;
&lt;li&gt;Save your changes&lt;/li&gt;
&lt;li&gt;Refresh your login page to confirm the traditional login fields have disappeared&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="why-this-matters-for-security"&gt;Why This Matters for Security
&lt;/h2&gt;&lt;p&gt;Think of this modification as closing an unnecessary port in your firewall. By removing the traditional login interface, you&amp;rsquo;re eliminating an entire attack surface. Attackers can no longer attempt to guess username/password combinations because those fields simply don&amp;rsquo;t exist anymore.&lt;/p&gt;
&lt;p&gt;This approach follows the principle of least privilege – if SSO is your chosen authentication method, there&amp;rsquo;s no reason to expose alternative login paths.&lt;/p&gt;
&lt;h2 id="the-user-experience-benefit"&gt;The User Experience Benefit
&lt;/h2&gt;&lt;p&gt;Beyond security, this change also streamlines the user experience. Your users won&amp;rsquo;t face the confusion of seeing multiple login options – they&amp;rsquo;ll be guided directly to your SSO provider, reducing potential confusion and support tickets.&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;Closing Thoughts
&lt;/h2&gt;&lt;p&gt;Security isn&amp;rsquo;t always about complex implementations. Sometimes, the most effective security measures are the simplest ones – like removing unnecessary entry points. This small CSS tweak represents the perfect intersection of improved security and better user experience.&lt;/p&gt;
&lt;p&gt;Remember: in the world of security, what can&amp;rsquo;t be seen often can&amp;rsquo;t be exploited. Happy securing!&lt;/p&gt;</description></item><item><title>Installation of Zabbix-agent in Home Assistant OS</title><link>https://lazzarotto.dev/blog/en/installation-of-zabbix-agent-in-home-assistant-os/</link><pubDate>Fri, 13 Dec 2024 00:00:00 +0000</pubDate><author>postmaster@mlazzarotto.it (Marco Lazzarotto)</author><guid>https://lazzarotto.dev/blog/en/installation-of-zabbix-agent-in-home-assistant-os/</guid><description>&lt;h1 id="introduction"&gt;Introduction
&lt;/h1&gt;&lt;p&gt;Home Assistant OS is a highly specialized Linux distribution optimized for running the Home Assistant home automation platform.&lt;/p&gt;
&lt;p&gt;One of its main features, which can be both an advantage in terms of security and a disadvantage in terms of flexibility, is the inability to access the OS console directly.&lt;/p&gt;
&lt;p&gt;This limitation makes the installation of additional software such as the Zabbix agent, an essential tool for system monitoring, particularly complex.&lt;/p&gt;
&lt;h1 id="the-solution"&gt;The Solution
&lt;/h1&gt;&lt;p&gt;Fortunately, the open source community once again comes to our aid.&lt;/p&gt;
&lt;p&gt;Thanks to the invaluable contribution of GitHub user &lt;a class="link" href="https://github.com/pschmitt/" target="_blank" rel="noopener"
&gt;pschmitt&lt;/a&gt;, an elegant solution has been developed to circumvent this limitation.&lt;/p&gt;
&lt;p&gt;The proposed approach is to install the Zabbix agent as a native Home Assistant add-on, integrating seamlessly with the existing ecosystem.&lt;/p&gt;
&lt;h1 id="the-repository"&gt;The Repository
&lt;/h1&gt;&lt;p&gt;User &lt;strong&gt;pschmitt&lt;/strong&gt; maintains a public GitHub repository, accessible at &lt;a class="link" href="https://github.com/pschmitt/home-assistant-addons/" target="_blank" rel="noopener"
&gt;https://github.com/pschmitt/home-assistant-addons/&lt;/a&gt;, where he has made available the code needed to install the Zabbix agent.&lt;/p&gt;
&lt;p&gt;This repository is specifically designed to be compatible with the Home Assistant add-on system, ensuring a clean and secure installation.
your home automation installation.&lt;/p&gt;
&lt;h1 id="installation-procedure"&gt;Installation Procedure
&lt;/h1&gt;&lt;p&gt;To integrate the repository into your Home Assistant system, you have two methods:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Manual Method&lt;/strong&gt;:
You can follow the standard procedure for adding third-party repositories, detailed in the official Home Assistant documentation at: &lt;a class="link" href="https://www.home-assistant.io/common-tasks/os#installing-third-party-add-ons" target="_blank" rel="noopener"
&gt;https://www.home-assistant.io/common-tasks/os#installing-third-party-add-ons&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Automatic Method&lt;/strong&gt;:
For a faster and more foolproof procedure, you can use the direct link that automates the entire repository addition process:
&lt;a class="link" href="https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Fpschmitt%2Fhome-assistant-addons" target="_blank" rel="noopener"
&gt;&lt;img src="https://camo.githubusercontent.com/f165b9b02bc37394563bf986b42dbad5cd3715746b71e8332fc5746064480f14/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4164642532307265706f7369746f7279253230746f2532306d792d486f6d65253230417373697374616e742d3431424446353f6c6f676f3d686f6d652d617373697374616e74267374796c653d666f722d7468652d6261646765"
loading="lazy"
alt="Add addon to Home Assistant"
&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id="completing-the-installation"&gt;Completing the Installation
&lt;/h1&gt;&lt;p&gt;Upon successful completion of the repository addition, two new add-ons will be available within your Home Assistant: &lt;strong&gt;zabbix-agent&lt;/strong&gt; and &lt;strong&gt;zabbix-agent2&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;These add-ons represent the classic and newer versions of the Zabbix agent, respectively. After choosing and installing the add-on that best suits your needs and configuring it properly, you will be able to monitor your Home Assistant server through the Zabbix platform.&lt;/p&gt;
&lt;p&gt;This integration will allow you to keep all the critical parameters of your Home Assistant system under control, providing professional supervision of your home automation installation.&lt;/p&gt;</description></item><item><title>Hugo - Static Site Generator</title><link>https://lazzarotto.dev/blog/en/hugo-static-site-generator/</link><pubDate>Sat, 13 Aug 2022 00:00:00 +0000</pubDate><author>postmaster@mlazzarotto.it (Marco Lazzarotto)</author><guid>https://lazzarotto.dev/blog/en/hugo-static-site-generator/</guid><description>&lt;img src="https://lazzarotto.dev/blog/" alt="Featured image of post Hugo - Static Site Generator" /&gt;&lt;h1 id="what-is-a-static-site-generator"&gt;What is a static site generator
&lt;/h1&gt;&lt;p&gt;A static website generator is software that generates a static HTML website using raw data and code as &amp;ldquo;input&amp;rdquo; templates.&lt;/p&gt;
&lt;p&gt;In essence, a static site generator automates the task of generating individual HTML pages and makes them ready to be served to users in advance.
&lt;img src="https://lazzarotto.dev/blog/img/static_site_generator_diagram_high_level.png"
loading="lazy"
alt="static site generator"
&gt;&lt;/p&gt;
&lt;h2 id="dynamic-and-static-web-sites"&gt;Dynamic and static web sites
&lt;/h2&gt;&lt;p&gt;A static website then is a website consisting of one or more HTML web pages that are always loaded in the same way, in a rather basic way in that no Javascript code is used or database operations are performed.&lt;/p&gt;
&lt;p&gt;The opposite of a static website, is the dynamic website (or CMS). The latter generates HTML pages to be served to the user dynamically, that is, when the user requests it. It can use variables such as time, date, cookies or geolocation to provide the user with a personalized user experience.
All this is supported by Javascript code.&lt;/p&gt;
&lt;p&gt;Examples of static website generators:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hugo&lt;/li&gt;
&lt;li&gt;Jekyll&lt;/li&gt;
&lt;li&gt;Gatsby&lt;/li&gt;
&lt;li&gt;Next.js&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Esempi di siti web dinamici:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Wordpress&lt;/li&gt;
&lt;li&gt;Joomla&lt;/li&gt;
&lt;li&gt;Drupal&lt;/li&gt;
&lt;li&gt;Magento&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="how-it-works"&gt;How it works
&lt;/h1&gt;&lt;p&gt;A static site generator like &lt;a class="link" href="https://gohugo.io" target="_blank" rel="noopener"
&gt;Hugo&lt;/a&gt;, is nothing more than a program whose purpose is to generate web pages from &lt;em&gt;Markdown&lt;/em&gt; code and a template of your choice.&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://en.wikipedia.org/wiki/Markdown" target="_blank" rel="noopener"
&gt;Markdown&lt;/a&gt; code, used in the form of files, serves the program as the source from which to obtain the text of the various pages of the site.
The theme, on the other hand, is code from third-party developers that deals with the formatting of web pages.&lt;/p&gt;
&lt;p&gt;Combining the two components results in HTML-formatted pages with text and formatting inside.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://lazzarotto.dev/blog/img/static_site_generator_diagram_low_level.png#center"
loading="lazy"
alt="static site generation"
&gt;&lt;/p&gt;
&lt;p&gt;All you need to use Hugo is a computer (even offline), Hugo in fact can function as a standalone web server, but only for testing purposes.
Upon execution of the &lt;code&gt;hugo publish&lt;/code&gt; command, the software will generate all web pages.&lt;/p&gt;
&lt;h1 id="why-i-chose-hugo"&gt;Why I chose Hugo
&lt;/h1&gt;&lt;p&gt;Previously I was using Wordpress, a very powerful CMS suitable for a thousand different uses, but also too much for me.
For my typical use it was too powerful, I would even say &lt;strong&gt;overkill&lt;/strong&gt;, with the hundreds of different options sometimes it was hard to find the right page to change this or that setting.&lt;/p&gt;
&lt;p&gt;Moreover, I also chose it for a reason of &lt;strong&gt;security&lt;/strong&gt; and especially &lt;strong&gt;performance&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Yeah, because in the meantime, static HTML is &lt;strong&gt;unassailable&lt;/strong&gt; &lt;em&gt;from an IT point of view&lt;/em&gt; since not relying on Javascript and databases, it&amp;rsquo;s just not possible to use &lt;em&gt;flaws to gain control&lt;/em&gt; of the site (or even worse, the server).&lt;/p&gt;
&lt;p&gt;In addition, the inherent operation of static HTML pages allows unparalleled &lt;em&gt;access speed&lt;/em&gt;.
In fact, on &lt;strong&gt;&lt;a class="link" href="https://pagespeed.web.dev" target="_blank" rel="noopener"
&gt;PageSpeed Insights&lt;/a&gt;&lt;/strong&gt; I score above 90 on the home page and above 95 on individual articles (the bottleneck is in the images, I still need to work on that).&lt;/p&gt;
&lt;h1 id="pros-and-cons"&gt;Pros and cons
&lt;/h1&gt;&lt;h2 id="pros"&gt;Pros
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Speed&lt;/li&gt;
&lt;li&gt;Simplicity: once configured it allows you to focus on writing articles&lt;/li&gt;
&lt;li&gt;Flexibility, ease of customization with themes&lt;/li&gt;
&lt;li&gt;No software to maintain&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;li&gt;Ability to write posts anywhere without using the CMS&lt;/li&gt;
&lt;li&gt;Free hosting (an article about it coming soon)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="cons"&gt;Cons
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Possibilities for use limited to blog or simple website&lt;/li&gt;
&lt;li&gt;No editor with formatting capabilities (not natively)&lt;/li&gt;
&lt;li&gt;Limited plugins that cannot be installed with a single click&lt;/li&gt;
&lt;li&gt;Initial setup not exactly trivial&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="considerations"&gt;Considerations
&lt;/h1&gt;&lt;p&gt;As you were able to see Hugo, but static site generators in general, are suitable for those who have few demands from the point of view of plugins and adding functionality, but there is also to be said that themes are not limited to the graphical aspect (as is the case on Wordpress), but bring functionality to the site.&lt;/p&gt;
&lt;p&gt;An example is the theme I use on this site, which for one supports search and light/dark mode switching.&lt;/p&gt;
&lt;p&gt;On the other hand, it is also true that the initial setup is not trivial. The documentation is there, but in English and sometimes it is not exactly straightforward to find.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Credits: &lt;a class="link" href="https://www.flaticon.com/authors/freepik" target="_blank" rel="noopener"
&gt;Icons created by Freepik - Flaticon&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Nextcloud Hub Demo – How to try it</title><link>https://lazzarotto.dev/blog/en/nextcloud-hub-demo-how-to-try-it/</link><pubDate>Thu, 28 Jul 2022 00:00:00 +0000</pubDate><author>postmaster@mlazzarotto.it (Marco Lazzarotto)</author><guid>https://lazzarotto.dev/blog/en/nextcloud-hub-demo-how-to-try-it/</guid><description>&lt;img src="https://lazzarotto.dev/blog/" alt="Featured image of post Nextcloud Hub Demo – How to try it" /&gt;&lt;p&gt;&lt;em&gt;Have you always wanted to test Nextcloud but didn&amp;rsquo;t feel like installing it because it was too complicated? Or do you just want to take a look to see what&amp;rsquo;s new in version 24 (Hub II)? Or do you want to show it to a friend?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In this article I will explain how you can get your hands on and &lt;strong&gt;test a fully functioning instance of Nextcloud Hub&lt;/strong&gt;, thus being able to try out all the features offered by this powerful tool.&lt;/p&gt;
&lt;p&gt;I wrote a couple of articles on this site in the past about how to try demo versions of Nextcloud without having to install it.&lt;/p&gt;
&lt;p&gt;Previous articles, however, referred to the site of a certain &lt;a class="link" href="https://bayton.org/2017/02/introducing-nextcloud-demo-servers" target="_blank" rel="noopener"
&gt;Jason Bayton&lt;/a&gt;, who unfortunately no longer maintained these demo instances, and they are stopped at version 14.&lt;/p&gt;
&lt;p&gt;In any case, there is no need to despair, because the eponymous software house has made a demo version of the latest version of Nextcloud available online.&lt;/p&gt;
&lt;h2 id="how"&gt;How
&lt;/h2&gt;&lt;p&gt;Very simple, go to &lt;a class="link" href="https://try.nextcloud.com" target="_blank" rel="noopener"
&gt;this link&lt;/a&gt; and click on the button &lt;strong&gt;Take me there!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Clicking on that button will bring up a page with a registration form, and after completing the fields you will get an email from Nextcloud with a link to start the demo instance.&lt;/p&gt;
&lt;p&gt;From now on what you will see on your screen will be a demo version of Nextcloud Hub, fully functional with many apps running, including Collabora Online.&lt;/p&gt;
&lt;p&gt;Collabora Online is the open source office suite used by default by Nextcloud.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WARNING&lt;/strong&gt;: Please be advised that &lt;strong&gt;after 60 minutes the trial user will be deleted&lt;/strong&gt;, so do not upload anything personal because otherwise it will all be deleted to make room for other users.&lt;/p&gt;</description></item><item><title>CentOS 8 - How to bind logstash on port 514</title><link>https://lazzarotto.dev/blog/en/centos-8-how-to-bind-logstash-on-port-514/</link><pubDate>Sun, 28 Nov 2021 00:00:00 +0200</pubDate><author>postmaster@mlazzarotto.it (Marco Lazzarotto)</author><guid>https://lazzarotto.dev/blog/en/centos-8-how-to-bind-logstash-on-port-514/</guid><description>&lt;img src="https://lazzarotto.dev/blog/" alt="Featured image of post CentOS 8 - How to bind logstash on port 514" /&gt;&lt;p&gt;The situation: you need to send logs from an old piece of equipment to &lt;a class="link" href="https://www.elastic.co/logstash/" target="_blank" rel="noopener"
&gt;logstash&lt;/a&gt; running on a &lt;strong&gt;CentOS 8&lt;/strong&gt;, for storing your logs on ElasticSearch.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The problem
&lt;/h2&gt;&lt;p&gt;The device is old and doesn&amp;rsquo;t support changing the default syslog port from &lt;strong&gt;514/udp&lt;/strong&gt; to something different, like port &lt;strong&gt;5140/udp&lt;/strong&gt;.
Unfortunately this can happen, for example on virtual appliances like &lt;strong&gt;ZeroShell&lt;/strong&gt;, where there&amp;rsquo;s no way to change the syslog port from the default one, but there&amp;rsquo;s a quick solution to this!&lt;/p&gt;
&lt;p&gt;What I&amp;rsquo;m going to show you is &lt;strong&gt;how to bind any process on any privileged port&lt;/strong&gt;, while running it as unprivileged user&lt;/p&gt;
&lt;h2 id="why"&gt;Why
&lt;/h2&gt;&lt;p&gt;This is a security feature, in that if you connect to a service on one of these ports you can be fairly sure that you have the real thing, and not a fake which some hacker has put up for you. It would be really dangerous to allow system-wide to any service to bind to a non privileged port, because ports from 1 to 1023 are indeed &lt;strong&gt;privileged!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;BTW, this is not a guide on how to set the listening port of logstash, but rather to allow &lt;strong&gt;java&lt;/strong&gt; to bind to a port &amp;lt; 1024.&lt;/p&gt;
&lt;h2 id="how"&gt;How
&lt;/h2&gt;&lt;p&gt;The procedure is very easy. First of all we need to &lt;strong&gt;find the path&lt;/strong&gt; of the &lt;strong&gt;java&lt;/strong&gt; process bundled with logstash, and the path of the &lt;strong&gt;libjli.so&lt;/strong&gt; library.&lt;/p&gt;
&lt;p&gt;The 2 paths should looks like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;/usr/share/logstash/jdk/bin/java
/usr/share/logstash/jdk/lib/jli
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once you have the path of the 2 files, you can start.
Enter the following commands as root.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;setcap &lt;span class="nv"&gt;CAP_NET_BIND_SERVICE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;+eip /usr/share/logstash/jdk/bin/java&lt;span class="p"&gt;;&lt;/span&gt; getcap /usr/share/logstash/jdk/bin/java &lt;span class="c1"&gt;# this allows java to bind to port &amp;gt; 1024&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/usr/share/logstash/jdk/lib/jli&amp;#34;&lt;/span&gt; &amp;lt; /etc/ld.so.conf.d/java.conf &lt;span class="c1"&gt;# this will be used by ldconfig&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ldconfig&lt;span class="p"&gt;;&lt;/span&gt; ldconfig -v -p &lt;span class="p"&gt;|&lt;/span&gt; grep libjli &lt;span class="c1"&gt;# checking if the library we need is found in the cache&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;reboot
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When the system is loaded, start the service and check the status and if it bounded to the right port.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl start logstash&lt;span class="p"&gt;;&lt;/span&gt; journalctl -xef -u logstash &lt;span class="c1"&gt;# start the service and check the logs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl status logstash &lt;span class="c1"&gt;# check the status of the service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ss -tlnp &lt;span class="p"&gt;|&lt;/span&gt; grep &lt;span class="m"&gt;514&lt;/span&gt; &lt;span class="c1"&gt;# check if logstash successfully bound to 514&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At this point you should have your logstash up and running, and most important listening to port 514.&lt;/p&gt;</description></item><item><title>Archive</title><link>https://lazzarotto.dev/blog/en/archive/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>postmaster@mlazzarotto.it (Marco Lazzarotto)</author><guid>https://lazzarotto.dev/blog/en/archive/</guid><description/></item><item><title>Search</title><link>https://lazzarotto.dev/blog/en/search/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>postmaster@mlazzarotto.it (Marco Lazzarotto)</author><guid>https://lazzarotto.dev/blog/en/search/</guid><description/></item></channel></rss>