<?xml version='1.0' encoding='UTF-8'?>
<?xml-stylesheet href="/rss/stylesheet/" type="text/xsl"?>
<rss xmlns:content='http://purl.org/rss/1.0/modules/content/' xmlns:taxo='http://purl.org/rss/1.0/modules/taxonomy/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:itunes='http://www.itunes.com/dtds/podcast-1.0.dtd' xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0" xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:atom='http://www.w3.org/2005/Atom' xmlns:podbridge='http://www.podbridge.com/podbridge-ad.dtd' version='2.0'>
<channel>
  <title>Jaryl Chng&apos;s Knowledge Base</title>
  <language>en-us</language>
  <generator>microfeed.org</generator>
  <itunes:type>episodic</itunes:type>
  <itunes:explicit>false</itunes:explicit>
  <atom:link rel="self" href="https://kb.jarylchng.com/rss/" type="application/rss+xml"/>
  <link>https://kb.jarylchng.com</link>
  <description>
    <![CDATA[<p>Welcome to the index page of my knowledge base, if you haven't done so, do visit my website at <a href="https://jarylchng.com" rel="noopener noreferrer" target="_blank">https://jarylchng.com</a>.</p><p>I will mainly use this site to document stuff, most of which will likely be in the public domain.</p>]]>
  </description>
  <itunes:author>Jaryl Chng</itunes:author>
  <itunes:image href="https://kb-static.jarylchng.com/kb-jarylchng-com/production/images/channel-c68f1f55f856ab833b4365991609dbec.png"/>
  <image>
    <title>Jaryl Chng&apos;s Knowledge Base</title>
    <url>https://kb-static.jarylchng.com/kb-jarylchng-com/production/images/channel-c68f1f55f856ab833b4365991609dbec.png</url>
    <link>https://kb.jarylchng.com</link>
  </image>
  <copyright>©2024</copyright>
  <itunes:category text="Technology"/>
  <item>
    <title>Linux Docker - &quot;Native&quot; Jellyfin Push 2FA MFA with LLDAP, Duo and DuoAuthProxy</title>
    <guid>1MzzsuokbLV</guid>
    <pubDate>Fri, 12 Apr 2024 01:01:00 GMT</pubDate>
    <itunes:explicit>false</itunes:explicit>
    <description>
      <![CDATA[<p>I wanted multifactor authentication on Jellyfin especially for my administrators which is unfortunately a missing feature natively.</p><p>Unfortunately configuring Authelia Forward Authentication or an SSO solution with OpenID Connect (OIDC) or Security Assertion Markup Language (SAML) would break native apps on non-PC devices due to drastically changing the authentication flow.</p><p>I wanted a solution that taps on Jellyfin's native authentication flow and only introduces a "pause" after clicking the log in button, which can be handled by any client, while waiting for the Duo push to be approved.</p><p>While this solution does not save a lot more space, I was inspired by <a href="https://forum.jellyfin.org/t-jellyfin-authentik-duo-2fa-solution-tutorial" rel="noopener noreferrer" target="_blank">Jellyfin, Authentik, DUO. 2FA solution</a> but wanted a smaller, non Authentik, alternative.</p><p>Also, Docker Hub images for <a href="https://duo.com/docs/authproxy-reference" rel="noopener noreferrer" target="_blank">DuoAuthProxy</a> were all outdated, so this was also an opportunity to bring it into my <a href="https://gitlab.com/users/jarylc/projects" rel="noopener noreferrer" target="_blank">list of automatically updating images powered by Gitlab CI/CD</a>.</p><p><a href="https://hub.docker.com/r/minimages/duoauthproxy" rel="noopener noreferrer" target="_blank">Just looking for the Alpine-based DuoAuthProxy docker image built by me? Click here!</a></p><p>Note: Since we will be accessing everything locally in this guide, even all within the docker network, I will not be going through setting up LDAP transit encryption (i.e. Secure LDAPS and StartTLS) as it would be out of scope.</p><h2>More Demos</h2><h3>Android app login video</h3><p> Your browser does not support the video element.</p><p><br></p><h2>Prerequisites</h2><ol><li><a href="https://jellyfin.org/" rel="noopener noreferrer" target="_blank">Jellyfin</a> on Docker set-up prior</li><li>A free/paid account with <a href="https://duo.com/" rel="noopener noreferrer" target="_blank">Duo</a> and access to the admin portal</li></ol><h2>Steps</h2><h3>Starting LLDAP</h3><p>Replace the following accordingly</p><ul><li>dc=example,dc=com (people normally follow their domain (i.e. example.com for this))</li><li>REPLACE_ME_WITH_YOUR_OWN_JWT_SECRET (this can be any long string really, string length is debatable and out of scope)</li><li>REPLACE_ME_WITH_YOUR_OWN_KEY_SEED (this can be any long string really, string length is debatable and out of scope)</li></ul><pre class="ql-syntax" spellcheck="false">docker run -d \
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; --name=LLDAP \
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; --network auth \
        -p 17170:17170 \
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -e LLDAP_LDAP_BASE_DN=dc=example,dc=com \
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -e LLDAP_JWT_SECRET=REPLACE_ME_WITH_YOUR_OWN_JWT_SECRET \
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -e LLDAP_KEY_SEED=REPLACE_ME_WITH_YOUR_OWN_KEY_SEED \
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -v /path/to/data:/data \
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; --restart unless-stopped \
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lldap/lldap:stable
</pre><p>Reference: <a href="https://github.com/lldap/lldap" rel="noopener noreferrer" target="_blank">https://github.com/lldap/lldap</a></p><h3>Configuring LLDAP</h3><p>Visit the frontend at port 17170 and create a service user for DuoAuthProxy accordingly, you can replace the values if needed.</p><p><img src="https://kb-static.jarylchng.com/kb-jarylchng-com/production/media/rich-editor/items/1MzzsuokbLV/image-0453045526fe05a2d49b0bd950cbd409.png"></p><p>For the sake of this example, the password of the svc-duoauthproxy user is `password`</p><p>Make sure to add the user to either lldap_strict_readonly (read-only) or lldap_password_manager (allow password change on Jellyfin) groups</p><p><img src="https://kb-static.jarylchng.com/kb-jarylchng-com/production/media/rich-editor/items/1MzzsuokbLV/image-752a821b48ce5a1afb8cc3c730861dbc.png"></p><h3>Creating Duo application</h3><p>Access your <a href="https://admin.duosecurity.com" rel="noopener noreferrer" target="_blank">Duo admin console</a></p><p>Go to Applications -&gt; Protect an Application</p><p>Search for `proxy` and protect an `LDAP Proxy` application</p><p><img src="https://kb-static.jarylchng.com/kb-jarylchng-com/production/media/rich-editor/items/1MzzsuokbLV/image-19c285167451cd3104c2f320eebc5e0b.png"></p><p>Copy the credentials to prepare for the next step</p><p><img src="https://kb-static.jarylchng.com/kb-jarylchng-com/production/media/rich-editor/items/1MzzsuokbLV/image-93fcb6188216f32127a7ddf081b5718e.png"></p><h3>Configuring DuoAuthProxy</h3><p>If you are using <a href="https://hub.docker.com/r/minimages/duoauthproxy" rel="noopener noreferrer" target="_blank">my image</a>, it is necessary to craft the configuration file first</p><p>Replace the following keys accordingly</p><ul><li>bind_dn</li><li>service_account_username</li><li>service_account_password</li><li>search_dn</li><li>ikey</li><li>skey</li><li>api_host</li></ul><pre class="ql-syntax" spellcheck="false">; Complete documentation about the Duo Auth Proxy can be found here:
; https://duo.com/docs/authproxy_reference

[main]
log_stdout=true

[ad_client]
host=LLDAP
port=3890
auth_type=plain
bind_dn=uid=svc-duoauthproxy,ou=people,dc=example,dc=com
service_account_username=svc-duoauthproxy
service_account_password=password
search_dn=ou=people,dc=example,dc=com
username_attribute=uid
at_attribute=mail

[ldap_server_auto]
ikey=DIXXXXXXXXXXXXXXXXXX
skey=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
api_host=api-XXXXXXXX.duosecurity.com
failmode=secure
client=ad_client
port=1812
exempt_primary_bind=false
exempt_ou_1=uid=svc-duoauthproxy,ou=people,dc=example,dc=com
</pre><p>Reference: <a href="https://duo.com/docs/authproxy_reference" rel="noopener noreferrer" target="_blank">https://duo.com/docs/authproxy_reference</a></p><h3>Starting DuoAuthProxy</h3><pre class="ql-syntax" spellcheck="false">docker run -d \
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; --name=DuoAuthProxy \
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; --network auth \
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -v /path/to/authproxy.cfg:/app/conf/authproxy.cfg \
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; --restart unless-stopped \
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; minimages/duoauthproxy
</pre><p>Feel free to use alternatives as well or even <a href="https://duo.com/docs/authproxy-reference#installation" rel="noopener noreferrer" target="_blank">host it natively or generate your own image using the official instructions</a>.</p><h3>Preparing Jellyfin</h3><p>I would assume you would already have Jellyfin set up, add Jellyfin to the auth network by re-running the container while adding `--network auth` argument to the docker run</p><h3>Installing LDAP Authentication Jellyfin Plugin</h3><p>Note: I'm currently using the unstable version of Jellyfin (10.9.0) as of posting, I had to use the unstable plugins repository.</p><p><img src="https://kb-static.jarylchng.com/kb-jarylchng-com/production/media/rich-editor/items/1MzzsuokbLV/image-20a91c182831f4b80999ec519c7eb3c2.png"><img src="https://kb-static.jarylchng.com/kb-jarylchng-com/production/media/rich-editor/items/1MzzsuokbLV/image-87410bcb3ef2dfbc2be257927231d614.png"></p><p>Please be reminded to restart Jellyfin after installing the plugin.</p><h3>Setting up LDAP Authentication Jellyfin Plugin</h3><p>Set the following configuration parameters for LDAP Server Settings section</p><ul><li>LDAP Server: DuoAuthProxy</li><li>LDAP Port: 1812</li><li>Secure LDAP: unchecked</li><li>StartTLS: unchecked</li><li>Skip SSL/TLS Verification: checked</li><li>Allow Password Change: (optional, but ensure the service account have the lldap_password_manager group if checked)</li><li>LDAP Bind User: uid=svc-duoauthproxy,ou=people,dc=example,dc=com</li><li>LDAP Bind User Password: password</li><li>LDAP Base DN for searches: ou=people,dc=example,dc=com</li></ul><p>At this point click `Save and Test LDAP Server Settings` to test connectivity, it should pass if all your settings are good.</p><p>Set the following configuration parameters for LDAP User Settings section</p><ul><li>LDAP Search Filter: (uid=*)</li><li>LDAP Search Attributes: uid, mail</li><li>LDAP Uid Attribute: uid</li><li>LDAP Username Attribute: uid</li><li>LDAP Password Attribute: userPassword</li><li>LDAP Admin Filter: (memberof=cn=lldap_admin,ou=example,dc=com)</li></ul><p>Note you should change your LDAP Search Filter and LDAP Admin Filter according to your needs, reference: <a href="https://github.com/lldap/lldap/blob/main/example_configs/jellyfin.md" rel="noopener noreferrer" target="_blank">https://github.com/lldap/lldap/blob/main/example_configs/jellyfin.md</a></p><p>At this point click `Save and Test LDAP Filter Settings` and check if there are more than 0 users and admins found, it should if all your settings are good.</p><p>Afterwhich, enter `admin` in `Test Login Name` field and `Save Search Attribute Settings and Query User` button to do a final lookup check and save.</p><p>Finally, configure Jellyfin User Settings sections to your needs and don't forget to hit the big blue `Save` button.</p><h3>(If Jellyfin has existing users) Switch users' Authentication Provider to LDAP-Authentication</h3><p><img src="https://kb-static.jarylchng.com/kb-jarylchng-com/production/media/rich-editor/items/1MzzsuokbLV/image-fb7d2f5a7d9b7f612cdcc169dd69fce5.png"></p><h3>Test the new authentication flow</h3><p>The new authentication flow should be immediately active, you can log out and test it.</p><p>If not, you can try another restart of Jellyfin first.</p><h2>Afterword</h2><p>Of course, this guide will not be a one-size fit all like the Authentik solution was for me.</p><p>A lot of settings can and should be customized to your needs.</p><p>This guide only follows through the most basic setup.</p>]]>
    </description>
    <link>https://kb.jarylchng.com/i/linux-docker-native-jellyfin-push-2fa-mfa-with-1MzzsuokbLV/</link>
    <itunes:episodeType>full</itunes:episodeType>
    <enclosure url="https://kb-static.jarylchng.com/kb-jarylchng-com/production/media/video-398b8f79d8e76c39dd60285dd980bf2e.mp4" type="video/mp4" length="1304209"/>
    <itunes:duration>00:00:09</itunes:duration>
  </item>
</channel>
</rss>