Lab NotesTechnical notes to myself (and you, apparently)2021-04-19T20:00:00Zhttp://labnotes.decampo.org/Ray DeCamporay@decampo.orgMoving to Eleventy From Jekyll2021-04-19T20:00:00Zhttp://labnotes.decampo.org/2021/09/28/moving-to-eleventy.html<p>After upgrading to Fedora 34, my Jekyll environment was broken again. So it was time to find another solution that didn't require so much attention. Enter <a href="https://www.11ty.dev/">Eleventy</a>, a static site generator that runs in the Node.js environment.</p>
<p>I started by following <a href="https://stedman.dev/2020/04/29/make-the-jump-from-jekyll-to-javascript/">https://stedman.dev/2020/04/29/make-the-jump-from-jekyll-to-javascript/</a>, but in the end I decided to forgo GitHub Actions and just generate the site myself and check it in under the <code>docs/</code> directory. You can look at the history of the <a href="https://github.com/RayDeCampo/raydecampo.github.io">raydecampo.github.io</a> repository, in particular the <code>eleventy</code> branch, for all the gritty details.</p>
<p>The steps went something like this (links to the appropriate commits follow the description):</p>
<ol>
<li>
<p>Following the Install and Configure section of the <a href="https://stedman.dev/2020/04/29/make-the-jump-from-jekyll-to-javascript/">stedman.dev document</a>, get Eleventy installed and running. (<a href="https://github.com/RayDeCampo/raydecampo.github.io/commit/64a6dac7587eb30c9cf889af9f426b428e64efd4">64a6dac</a>)</p>
</li>
<li>
<p>At this point, influenced by <a href="https://github.com/andeersg/andeers.com">https://github.com/andeersg/andeers.com</a>, I restructured so that the source of the blog contents were kept under the <code>src/</code> directory. (<a href="https://github.com/RayDeCampo/raydecampo.github.io/commit/51a266656fe0265c4c3f55b6d0ec956d3c880836">51a2666</a>, <a href="https://github.com/RayDeCampo/raydecampo.github.io/commit/66dbf6bd2fcec064770b73d9d176d2fe49b8c2e7">66dbf6b</a>)</p>
</li>
<li>
<p>Added permalink metadata to the blogs so that the files end up in the same place as they did when jekyll was creating the site (and blogger before that). (<a href="https://github.com/RayDeCampo/raydecampo.github.io/commit/77e6af5efc35a694b0e5fd6f2c1c00a61ae5edbd">77e6af5</a>)</p>
</li>
<li>
<p>Next, the jekyll built-in <code>site.posts</code> variable is no longer available under Eleventy. So I defined some collections and used them to fix the pages with lists of posts. (<a href="https://github.com/RayDeCampo/raydecampo.github.io/commit/7a7e5b59170e43dcd7f87510da26653ef0c6115c">7a7e5b5</a>)</p>
</li>
<li>
<p>Next up was fixing CSS. Eleventy has no built in support for SASS (kind of strange), so I added a node script entry to run SASS. I also downloaded a copy of the CSS for the jekyll theme I had been using. (<a href="https://github.com/RayDeCampo/raydecampo.github.io/commit/db0d7ac73df098eadf14053db1e884be5dc19447">db0d7ac</a>, <a href="https://github.com/RayDeCampo/raydecampo.github.io/commit/1b1433f2a73d838a1f15407e8d50658020fd3aac">1b1433f</a>)</p>
</li>
<li>
<p>Next up was installing the <a href="https://www.11ty.dev/docs/plugins/rss/">Eleventy RSS plugin</a> to restore RSS functionality. (<a href="https://github.com/RayDeCampo/raydecampo.github.io/commit/4ef3cd952615182d005143729a4fb2e3abf2e0ca">4ef3cd9</a>, <a href="https://github.com/RayDeCampo/raydecampo.github.io/commit/a6a09f8b1e585b9f7ac58721afd2f88bc40eb11d">a6a09f8</a>)</p>
</li>
<li>
<p>Next we get tags working the way they did before. Stole a <a href="https://github.com/philhawksworth/hawksworx.com/blob/master/src/site/_filters/getTagList.js">code snippet</a> from philhawksworth (H/T). (<a href="https://github.com/RayDeCampo/raydecampo.github.io/commit/3121301d881bc9077d95bb586ad65d0d81ad27ad">3121301</a>)</p>
</li>
<li>
<p>Add some metadata and fix up the parameterized data in the <code>default.html</code> template. (<a href="https://github.com/RayDeCampo/raydecampo.github.io/commit/4233afc428cf736ccf8fc47a43cdfbcf73ea3ba7">4233afc</a>)</p>
</li>
<li>
<p>Install and configure the <a href="https://www.11ty.dev/docs/plugins/syntaxhighlight/">Eleventy syntax highlighting plugin</a>. Don't forget to download a Prism CSS file. (<a href="https://github.com/RayDeCampo/raydecampo.github.io/commit/14c89fec5909a865c283ad6c09cb0f6b3d43beb6">14c89fe</a>)</p>
</li>
<li>
<p>Finally it was time to get things working on GitHub Pages. GitHub Pages will let you use either the root directory or the <code>docs/</code> directory of the repository as web root. So I changed the output directory for Eleventy to the <code>docs/</code> directory. I also needed a passthrough for the CNAME file. Then I just needed to change the configuration in GitHub (Settings -> Pages) and I was all set. (<a href="https://github.com/RayDeCampo/raydecampo.github.io/commit/1aa4e97b25b5e3540486778310ce56b433020111">1aa4e97</a>, <a href="https://github.com/RayDeCampo/raydecampo.github.io/commit/5410778b24964de100f1920e989672b1248d7764">5410778</a>, <a href="https://github.com/RayDeCampo/raydecampo.github.io/commit/6f2d68b6d3611b0a0e811854eb4a0a95c1720ac9">6f2d68b</a>)</p>
</li>
</ol>
Frame Size Issue with Java HTTP Client2021-04-19T20:00:00Zhttp://labnotes.decampo.org/2021/04/19/java-http-client-frame-size.html<p>If you hit this error when using the HTTP client built into Java:</p>
<pre class="language-text"><code class="language-text">Caused by: java.io.IOException: protocol error: Frame type(80) length(4740180) exceeds MAX_FRAME_SIZE(16384)<br /> at jdk.internal.net.http.Http2Connection.protocolError(Http2Connection.java:952) ~[java.net.http:?]<br /> at jdk.internal.net.http.Http2Connection.processFrame(Http2Connection.java:714) ~[java.net.http:?]<br /> at jdk.internal.net.http.frame.FramesDecoder.decode(FramesDecoder.java:155) ~[java.net.http:?]</code></pre>
<p>Try using a client configured for HTTP 1.1:</p>
<pre class="language-java"><code class="language-java"> client <span class="token operator">=</span> <span class="token class-name">HttpClient</span><span class="token punctuation">.</span><span class="token function">newBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">followRedirects</span><span class="token punctuation">(</span><span class="token class-name">HttpClient<span class="token punctuation">.</span>Redirect</span><span class="token punctuation">.</span>ALWAYS<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">version</span><span class="token punctuation">(</span><span class="token class-name">HttpClient<span class="token punctuation">.</span>Version</span><span class="token punctuation">.</span>HTTP_1_1<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
Development Mail Server (CentOS 8)2020-11-09T21:00:00Zhttp://labnotes.decampo.org/2020/11/09/development-mail-server.html<p>As you are probably aware, any sufficiently developed software application will eventually grow until it implements email. A frequent stepping stone along that evolutionary path is for an application to gain the ability to send email. If your application sends email, at a certain point it will become inconvenient to continue to use real email addresses and servers when developing and testing the application. The solution is to set up a development mail server.</p>
<p>An important note about the development mail server is that it should NOT participate in the delivery or relay of real email. If you configure it wrong and create an open relay for spam, expect an angry communication from your ISP, usually invoking your name in vain and threatening termination of services.</p>
<p>So the goal here is to create a black hole of email which will accept mail for the configured server only. Of course, we do want to read the email (at least some of the email) so that we can see that our application is doing what we want. So we will want to be able to access the email with an IMAP client like Thunderbird.</p>
<p>We'll be running our development mail server on a CentOS 8 server, using postfix as the SMTP server and dovecot as the IMAP server. We will configure the server to accept mail for the domain <code>example.com</code>, you should change this to whatever domain you use internally for development. The DNS name for the server will be <code>devmail.example.com</code> and it is assumed this is already configured.</p>
<h2>Firewall</h2>
<p>First things first, configure the firewall to allow SMTP and IMAP traffic. We use the zone <code>internal</code> for our intranet, you probably use a different zone:</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>ray@wxyz ~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> firewall-cmd --zone<span class="token operator">=</span>internal --add-service smtp<br />success<br /><span class="token punctuation">[</span>ray@wxyz ~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> firewall-cmd --zone<span class="token operator">=</span>internal --add-service imap<br />success<br /><span class="token punctuation">[</span>ray@wxyz ~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> firewall-cmd --zone<span class="token operator">=</span>internal --add-service smtp --permanent<br />success<br /><span class="token punctuation">[</span>ray@wxyz ~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> firewall-cmd --zone<span class="token operator">=</span>internal --add-service imap --permanent<br />success</code></pre>
<h2>Postfix</h2>
<p>Now we are ready to install the postfix server. We also create an OS user to own the virtual mailboxes.</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>ray@wxyz ~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> dnf <span class="token function">install</span> postfix<br /><span class="token punctuation">[</span>ray@wxyz ~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> <span class="token function">groupadd</span> --system --gid <span class="token number">2525</span> vmail<br /><span class="token punctuation">[</span>ray@wxyz ~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> <span class="token function">useradd</span> --system --uid <span class="token number">2525</span> --shell /sbin/nologin --home /var/mail/vhosts --no-user-group -g vmail --comment <span class="token string">"Virtual Mailbox Owner"</span> vmail<br /><span class="token punctuation">[</span>ray@wxyz ~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> <span class="token function">mkdir</span> -p /var/mail/vhosts/example.com<br /><span class="token punctuation">[</span>ray@wxyz ~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> <span class="token function">chown</span> -R vmail:vmail /var/mail/vhosts</code></pre>
<p>Next we configure postfix by editing the <code>/etc/postfix/main.cf</code> file. The <code>myhostname</code> parameter is set to the DNS name, <code>devmail.example.com</code>. Adjust the <code>inet_interfaces</code> parameter as desired. (In my case I have <code>devmail</code> defined in the <code>/etc/hosts</code> file to bind it to a specific network interface so I am using <code>devmail, localhost</code>).</p>
<p>Very importantly, set the <code>smtpd_relay_restrictions</code> parameter to <em>reject_unauth_destination</em>. This will prevent the server from attempting to relay mail to other mail servers.</p>
<p>Finally the virtual mailbox must be configured. We need to set the <code>virtual_mailbox_domains</code> setting to include our domain, <em>example.com</em>. The <code>virtual_mailbox_base</code> parameter is set to a directory which will contain the mailboxes. The <code>virtual_mailbox_maps</code> parameter is set to a file which will contain details about the users and mailboxes the server knows about. Finally the <code>virtual_uid_maps</code> and <code>virtual_gid_maps</code> are used to tell postfix to use our new <code>vmail</code> OS user when setting the permissions of the mailboxes.</p>
<p>Here is the result of diffing the provided <code>main.cf</code> with the new, configured <code>main.cf</code>:</p>
<pre class="language-diff"><code class="language-diff">[ray@wxyz ~]$ sudo diff main.cf.in main.cf<br /><span class="token coord">95a96</span><br /><span class="token inserted-arrow inserted"><span class="token prefix inserted">></span><span class="token line"> myhostname = devmail.example.com<br /></span></span><span class="token coord">135c136</span><br /><span class="token deleted-arrow deleted"><span class="token prefix deleted"><</span><span class="token line"> inet_interfaces = localhost<br /></span></span><span class="token coord">---</span><br /><span class="token inserted-arrow inserted"><span class="token prefix inserted">></span><span class="token line"> inet_interfaces = devmail, localhost<br /></span></span><span class="token coord">316a318,321</span><br /><span class="token inserted-arrow inserted"><span class="token prefix inserted">></span><span class="token line"> # Prevent relaying, see /usr/share/doc/postfix/README_FILES/SMTPD_ACCESS_README<br /></span><span class="token prefix inserted">></span><span class="token line"> # also postconf(5) man page<br /></span><span class="token prefix inserted">></span><span class="token line"> smtpd_relay_restrictions = reject_unauth_destination<br /></span><span class="token prefix inserted">></span><span class="token line"><br /></span></span><span class="token coord">738a744,753</span><br /><span class="token inserted-arrow inserted"><span class="token prefix inserted">></span><span class="token line"><br /></span><span class="token prefix inserted">></span><span class="token line"> # VIRTUAL DOMAINS<br /></span><span class="token prefix inserted">></span><span class="token line"> # See /usr/share/doc/postfix/README_FILES/VIRTUAL_README, section titled<br /></span><span class="token prefix inserted">></span><span class="token line"> # "Postfix virtual MAILBOX example: separate domains, non-UNIX accounts"<br /></span><span class="token prefix inserted">></span><span class="token line"> virtual_mailbox_domains = example.com<br /></span><span class="token prefix inserted">></span><span class="token line"> virtual_mailbox_base = /var/mail/vhosts<br /></span><span class="token prefix inserted">></span><span class="token line"> virtual_mailbox_maps = hash:/etc/postfix/vmailbox<br /></span><span class="token prefix inserted">></span><span class="token line"> virtual_uid_maps = static:2525<br /></span><span class="token prefix inserted">></span><span class="token line"> virtual_gid_maps = static:2525</span></span></code></pre>
<p>One final piece of configuration for postfix is to create the <code>/etc/postfix/vmailbox</code> file. In our case, instead of creating individual users (who wants to be responsible for administering individual accounts on a development mail server?) we'll create one catchall mailbox that all mail will be routed to.</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>ray@wxyz ~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> <span class="token function">cat</span> <span class="token operator">></span>vmailbox <span class="token operator"><<</span><span class="token string">EOF<br />@example.com example.com/all/Maildir/<br />EOF</span><br /><span class="token punctuation">[</span>ray@wxyz ~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> postmap /etc/postfix/vmailbox</code></pre>
<p>We are ready to enable and start the postfix server:</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>ray@wxyz ~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> systemctl <span class="token builtin class-name">enable</span> postfix<br />Created symlink /etc/systemd/system/multi-user.target.wants/postfix.service → /usr/lib/systemd/system/postfix.service.<br /><span class="token punctuation">[</span>ray@wxyz ~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> systemctl start postfix</code></pre>
<h3>Testing SMTP using netcat</h3>
<p>We can test the postfix server using netcat:</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>ray@stinger ~<span class="token punctuation">]</span>$ <span class="token function">nc</span> devmail <span class="token number">25</span> <span class="token operator"><<</span><span class="token string">EOM<br />EHLO STINGER<br />MAIL FROM:auser@example.com<br />RCPT TO:someone@example.com<br />DATA<br />Subject:Testing an email on the new environment<br /><br />This is a test of our postfix configuration.<br />.<br />QUIT<br />EOM</span><br /><span class="token comment">#</span><br /><span class="token number">220</span> devmail.example.com ESMTP Postfix<br /><span class="token number">250</span>-devmail.example.com<br /><span class="token number">250</span>-PIPELINING<br /><span class="token number">250</span>-SIZE <span class="token number">10240000</span><br /><span class="token number">250</span>-VRFY<br /><span class="token number">250</span>-ETRN<br /><span class="token number">250</span>-STARTTLS<br /><span class="token number">250</span>-ENHANCEDSTATUSCODES<br /><span class="token number">250</span>-8BITMIME<br /><span class="token number">250</span>-DSN<br /><span class="token number">250</span> SMTPUTF8<br /><span class="token number">250</span> <span class="token number">2.1</span>.0 Ok<br /><span class="token number">250</span> <span class="token number">2.1</span>.5 Ok<br /><span class="token number">354</span> End data with <span class="token operator"><</span>CR<span class="token operator">></span><span class="token operator"><</span>LF<span class="token operator">></span>.<span class="token operator"><</span>CR<span class="token operator">></span><span class="token operator"><</span>LF<span class="token operator">></span><br /><span class="token number">250</span> <span class="token number">2.0</span>.0 Ok: queued as EF2DD1D5C89<br /><span class="token number">221</span> <span class="token number">2.0</span>.0 Bye</code></pre>
<p>Furthermore we can verify that relaying to another email server fails:</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>ray@stinger ~<span class="token punctuation">]</span>$ <span class="token function">nc</span> devmail <span class="token number">25</span> <span class="token operator"><<</span><span class="token string">EOM<br />EHLO STINGER<br />MAIL FROM:auser@example.com<br />RCPT TO:ray@example.net<br />QUIT<br />EOM</span><br /><span class="token comment">#</span><br /><span class="token number">220</span> devmail.example.com ESMTP Postfix<br /><span class="token number">250</span>-devmail.example.com<br /><span class="token number">250</span>-PIPELINING<br /><span class="token number">250</span>-SIZE <span class="token number">10240000</span><br /><span class="token number">250</span>-VRFY<br /><span class="token number">250</span>-ETRN<br /><span class="token number">250</span>-STARTTLS<br /><span class="token number">250</span>-ENHANCEDSTATUSCODES<br /><span class="token number">250</span>-8BITMIME<br /><span class="token number">250</span>-DSN<br /><span class="token number">250</span> SMTPUTF8<br /><span class="token number">250</span> <span class="token number">2.1</span>.0 Ok<br /><span class="token number">554</span> <span class="token number">5.7</span>.1 <span class="token operator"><</span>ray@example.net<span class="token operator">></span>: Relay access denied<br /><span class="token number">221</span> <span class="token number">2.0</span>.0 Bye</code></pre>
<h2>Dovecot</h2>
<h3>Installation and Configuration</h3>
<p>I suppose you would like to be able to read the mail too? After all, we aren't all perfect coders who get it right on the first try.</p>
<p>In that case, we will set up a dovecot server to provide access to the mailbox via IMAP.</p>
<p>Note the the firewall was already configured to allow IMAP traffic above. Installing and enabling dovecot involves the usual steps:</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>ray@wxyz ~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> dnf <span class="token function">install</span> dovecot<br /><span class="token punctuation">[</span>ray@wxyz ~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> systemctl <span class="token builtin class-name">enable</span> dovecot<br />Created symlink /etc/systemd/system/multi-user.target.wants/dovecot.service → /usr/lib/systemd/system/dovecot.service.</code></pre>
<p>Next we configure authentication to use a custom password file instead of the system authentication by editing the <code>/etc/dovecot/conf.d/10-auth.conf</code> file. The resulting changes in diff format:</p>
<pre class="language-diff"><code class="language-diff"><span class="token coord">10a11</span><br /><span class="token inserted-arrow inserted"><span class="token prefix inserted">></span><span class="token line"> disable_plaintext_auth = no<br /></span></span><span class="token coord">122c123</span><br /><span class="token deleted-arrow deleted"><span class="token prefix deleted"><</span><span class="token line"> !include auth-system.conf.ext<br /></span></span><span class="token coord">---</span><br /><span class="token inserted-arrow inserted"><span class="token prefix inserted">></span><span class="token line"> #!include auth-system.conf.ext<br /></span></span><span class="token coord">125c126</span><br /><span class="token deleted-arrow deleted"><span class="token prefix deleted"><</span><span class="token line"> #!include auth-passwdfile.conf.ext<br /></span></span><span class="token coord">---</span><br /><span class="token inserted-arrow inserted"><span class="token prefix inserted">></span><span class="token line"> !include auth-passwdfile.conf.ext</span></span></code></pre>
<p>Next we tell dovecot where to find our mailbox files by editing <code>/etc/dovecot/conf.d/10-mail.conf</code>, by setting the <code>mail_location</code> parameter:</p>
<pre class="language-diff"><code class="language-diff"><span class="token coord">30c30</span><br /><span class="token deleted-arrow deleted"><span class="token prefix deleted"><</span><span class="token line"> #mail_location =<br /></span></span><span class="token coord">---</span><br /><span class="token inserted-arrow inserted"><span class="token prefix inserted">></span><span class="token line"> mail_location = maildir:/var/mail/vhosts/%d/%n/Maildir</span></span></code></pre>
<p>Next we edit the <code>/etc/dovecot/conf.d/auth-passwdfile.conf.ext</code> file. This was the configuration we activated when editing <code>10-auth.conf</code> and will contain the details of our custom authentication. The resulting file is small enough to reproduce here (with commented-out lines omitted):</p>
<pre class="language-none"><code class="language-none">passdb {<br /> driver = passwd-file<br /> args = /etc/dovecot/%d-passdb<br />}<br /><br />userdb {<br /> driver = static<br /> args = uid=vmail gid=vmail home=/var/mail/vhosts/%d/%n<br />}</code></pre>
<p>We configure dovecot to use the password file in the pattern <code>/etc/dovecot/%d-passwd</code>. The <code>%d</code> here is a placeholder for the domain, <em>example.com</em>, so out password file will be <code>/etc/dovecot/example.com-passdb</code>.</p>
<p>Since we are not all that concerned about security for our fake email, we can create the password file in plaintext:</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>ray@wxyz dovecot<span class="token punctuation">]</span>$ <span class="token function">sudo</span> <span class="token function">cat</span> <span class="token operator">></span>example.com-passdb <span class="token operator"><<</span><span class="token string">EOF<br />all@example.com:{PLAIN}password<br />EOF</span></code></pre>
<p>Now we can start the dovecot server:</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>ray@wxyz dovecot<span class="token punctuation">]</span>$ <span class="token function">sudo</span> systemctl start dovecot</code></pre>
<p>Finally, we set up a cron job to delete old mail older than a week old on a weekly basis:</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>ray@wxyz dovecot<span class="token punctuation">]</span>$ <span class="token function">sudo</span> <span class="token function">cat</span> <span class="token operator">></span>/etc/cron.weekly/delete-old-mail <span class="token operator"><<</span><span class="token string">EOF<br />#!/bin/bash<br />threshold=<span class="token variable"><span class="token variable">$(</span> <span class="token function">date</span> --date<span class="token operator">=</span><span class="token string">'last week'</span> <span class="token string">'+%d-%b-%Y'</span> <span class="token variable">)</span></span><br />doveadm expunge -u all@example.com mailbox INBOX SENTBEFORE <span class="token variable">${threshold}</span><br />doveadm purge -u all@example.com<br />EOF</span><br /><span class="token punctuation">[</span>ray@wxyz dovecot<span class="token punctuation">]</span>$ <span class="token function">sudo</span> <span class="token function">chmod</span> <span class="token number">744</span> /etc/cron.weekly/delete-old-mail</code></pre>
<h3>Testing via OpenSSL</h3>
<p>Since dovecot will use the STARTTLS protocol by default, we can't just connect to it via netcat like postfix. We need to use openssl, which supports encrypted traffic and specifically the STARTTLS protocol.</p>
<p>Below is an example session showing how to use openssl to make the connection. The commands entered interactively all start with a<em>n</em>, where <em>n</em> is a number. I've edited down some of the output from the server.</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>ray@stinger ~<span class="token punctuation">]</span>$ openssl s_client -connect devmail:143 -starttls imap<br />CONNECTED<span class="token punctuation">(</span>00000003<span class="token punctuation">)</span><br /><span class="token assign-left variable">depth</span><span class="token operator">=</span><span class="token number">0</span> OU <span class="token operator">=</span> IMAP server, CN <span class="token operator">=</span> imap.example.com, emailAddress <span class="token operator">=</span> postmaster@example.com<br />verify error:num<span class="token operator">=</span><span class="token number">18</span>:self signed certificate<br />verify return:1<br /><span class="token assign-left variable">depth</span><span class="token operator">=</span><span class="token number">0</span> OU <span class="token operator">=</span> IMAP server, CN <span class="token operator">=</span> imap.example.com, emailAddress <span class="token operator">=</span> postmaster@example.com<br />verify return:1<br />---<br />Certificate chain<br /> <span class="token number">0</span> s:/OU<span class="token operator">=</span>IMAP server/CN<span class="token operator">=</span>imap.example.com/emailAddress<span class="token operator">=</span>postmaster@example.com<br /> i:/OU<span class="token operator">=</span>IMAP server/CN<span class="token operator">=</span>imap.example.com/emailAddress<span class="token operator">=</span>postmaster@example.com<br />---<br />Server certificate<br />-----BEGIN CERTIFICATE-----<br />MIIEUzCCArugAwIBAgIUJLx3lRtnTxo0WoCjsTsRhEQklZMwDQYJKoZIhvcNAQEL<br /><span class="token assign-left variable">duHQueW0oQ</span><span class="token operator">==</span><br />-----END CERTIFICATE-----<br /><span class="token assign-left variable">subject</span><span class="token operator">=</span>/OU<span class="token operator">=</span>IMAP server/CN<span class="token operator">=</span>imap.example.com/emailAddress<span class="token operator">=</span>postmaster@example.com<br /><span class="token assign-left variable">issuer</span><span class="token operator">=</span>/OU<span class="token operator">=</span>IMAP server/CN<span class="token operator">=</span>imap.example.com/emailAddress<span class="token operator">=</span>postmaster@example.com<br />---<br />No client certificate CA names sent<br />Peer signing digest: SHA512<br />Server Temp Key: ECDH, P-256, <span class="token number">256</span> bits<br />---<br />SSL handshake has <span class="token builtin class-name">read</span> <span class="token number">2208</span> bytes and written <span class="token number">441</span> bytes<br />---<br />New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-GCM-SHA384<br />Server public key is <span class="token number">3072</span> bit<br />Secure Renegotiation IS supported<br />Compression: NONE<br />Expansion: NONE<br />No ALPN negotiated<br />SSL-Session:<br /> Protocol <span class="token builtin class-name">:</span> TLSv1.2<br /> Cipher <span class="token builtin class-name">:</span> ECDHE-RSA-AES256-GCM-SHA384<br /> Session-ID: 37D8B6A46719F7C0D16D122C8E6<br /> Session-ID-ctx:<br /> Master-Key: 2E69D75A3D0B2A6CB4AECCAA5A3<br /> Key-Arg <span class="token builtin class-name">:</span> None<br /> Krb5 Principal: None<br /> PSK identity: None<br /> PSK identity hint: None<br /> TLS session ticket lifetime hint: <span class="token number">7200</span> <span class="token punctuation">(</span>seconds<span class="token punctuation">)</span><br /> TLS session ticket:<br /> 0000 - <span class="token number">95</span> 4e 5a b2 c3 a7 <span class="token number">89</span> 8b-42 <span class="token number">49</span> 07 2b bb 6f <span class="token number">57</span> 6c .NZ<span class="token punctuation">..</span><span class="token punctuation">..</span>.BI.+.oWl<br /> 0010 - ef <span class="token number">69</span> 9b <span class="token number">92</span> 2f f3 aa 5d-a7 <span class="token number">51</span> 6d <span class="token number">77</span> 2c b4 4c <span class="token number">19</span> .i<span class="token punctuation">..</span>/<span class="token punctuation">..</span><span class="token punctuation">]</span>.Qmw,.L.<br /> 0020 - 03 <span class="token function">dd</span> e8 5f 5e <span class="token number">23</span> 6b dd-dd <span class="token number">41</span> <span class="token function">df</span> <span class="token number">48</span> <span class="token number">65</span> c8 d0 <span class="token number">50</span> <span class="token punctuation">..</span>._^<span class="token comment">#k..A.He..P</span><br /> 0030 - d3 e5 f6 cc <span class="token number">72</span> 05 cf 6f-c3 b0 f3 <span class="token number">11</span> <span class="token number">83</span> ff eb <span class="token number">74</span> <span class="token punctuation">..</span><span class="token punctuation">..</span>r<span class="token punctuation">..</span>o<span class="token punctuation">..</span><span class="token punctuation">..</span><span class="token punctuation">..</span>.t<br /><br /> Start Time: <span class="token number">1594842880</span><br /> Timeout <span class="token builtin class-name">:</span> <span class="token number">300</span> <span class="token punctuation">(</span>sec<span class="token punctuation">)</span><br /> Verify <span class="token builtin class-name">return</span> code: <span class="token number">18</span> <span class="token punctuation">(</span>self signed certificate<span class="token punctuation">)</span><br />---<br /><span class="token builtin class-name">.</span> OK Pre-login capabilities listed, post-login capabilities have more.<br /><br />a1 LOGIN all@example.com p<br />* CAPABILITY IMAP4rev1 SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT<br /><span class="token assign-left variable">SORT</span><span class="token operator">=</span><span class="token environment constant">DISPLAY</span> <span class="token assign-left variable">THREAD</span><span class="token operator">=</span>REFERENCES <span class="token assign-left variable">THREAD</span><span class="token operator">=</span>REFS <span class="token assign-left variable">THREAD</span><span class="token operator">=</span>ORDEREDSUBJECT<br />MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS<br />LIST-EXTENDED <span class="token assign-left variable">I18NLEVEL</span><span class="token operator">=</span><span class="token number">1</span> CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES<br />WITHIN <span class="token assign-left variable">CONTEXT</span><span class="token operator">=</span>SEARCH LIST-STATUS BINARY MOVE <span class="token assign-left variable">SNIPPET</span><span class="token operator">=</span>FUZZY<br /><span class="token assign-left variable">PREVIEW</span><span class="token operator">=</span>FUZZY LITERAL+ NOTIFY SPECIAL-USE<br />a1 OK Logged <span class="token keyword">in</span><br /><br />a2 LIST <span class="token string">""</span> <span class="token string">"*"</span><br />* LIST <span class="token punctuation">(</span><span class="token punctuation">\</span>HasNoChildren<span class="token punctuation">)</span> <span class="token string">"."</span> INBOX<br />a2 OK List completed <span class="token punctuation">(</span><span class="token number">0.001</span> + <span class="token number">0.000</span> secs<span class="token punctuation">)</span>.<br /><br />a3 EXAMINE INBOX<br />* FLAGS <span class="token punctuation">(</span><span class="token punctuation">\</span>Answered <span class="token punctuation">\</span>Flagged <span class="token punctuation">\</span>Deleted <span class="token punctuation">\</span>Seen <span class="token punctuation">\</span>Draft<span class="token punctuation">)</span><br />* OK <span class="token punctuation">[</span>PERMANENTFLAGS <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span> Read-only mailbox.<br />* <span class="token number">1</span> EXISTS<br />* <span class="token number">1</span> RECENT<br />* OK <span class="token punctuation">[</span>UNSEEN <span class="token number">1</span><span class="token punctuation">]</span> First unseen.<br />* OK <span class="token punctuation">[</span>UIDVALIDITY <span class="token number">1594842093</span><span class="token punctuation">]</span> UIDs valid<br />* OK <span class="token punctuation">[</span>UIDNEXT <span class="token number">2</span><span class="token punctuation">]</span> Predicted next <span class="token environment constant">UID</span><br />a3 OK <span class="token punctuation">[</span>READ-ONLY<span class="token punctuation">]</span> Examine completed <span class="token punctuation">(</span><span class="token number">0.002</span> + <span class="token number">0.000</span> + <span class="token number">0.001</span> secs<span class="token punctuation">)</span>.<br /><br />a4 FETCH <span class="token number">1</span> BODY<span class="token punctuation">[</span><span class="token punctuation">]</span><br />* <span class="token number">1</span> FETCH <span class="token punctuation">(</span>BODY<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span><span class="token number">405</span><span class="token punctuation">}</span><br />Return-Path: <span class="token operator"><</span>auser@example.com<span class="token operator">></span><br />X-Original-To: someone@example.com<br />Delivered-To: someone@example.com<br />Received: from STINGER <span class="token punctuation">(</span>unknown <span class="token punctuation">[</span><span class="token number">10.0</span>.0.200<span class="token punctuation">]</span><span class="token punctuation">)</span><br /> by devmail.prosoft.prosoftcm.com <span class="token punctuation">(</span>Postfix<span class="token punctuation">)</span> with ESMTP <span class="token function">id</span> EF2DD1D5C89<br /> <span class="token keyword">for</span> <span class="token operator"><</span>someone@example.com<span class="token operator">></span><span class="token punctuation">;</span> Wed, <span class="token number">15</span> Jul <span class="token number">2020</span> <span class="token number">15</span>:03:53 -0400 <span class="token punctuation">(</span>EDT<span class="token punctuation">)</span><br />Subject:Testing an email on the new environment<br /><br />This is a <span class="token builtin class-name">test</span> of our postfix configuration.<br /><span class="token punctuation">)</span><br />a4 OK Fetch completed <span class="token punctuation">(</span><span class="token number">0.002</span> + <span class="token number">0.000</span> + <span class="token number">0.001</span> secs<span class="token punctuation">)</span>.<br /><br />a5 LOGOUT<br />* BYE Logging out<br />a5 OK Logout completed <span class="token punctuation">(</span><span class="token number">0.001</span> + <span class="token number">0.000</span> secs<span class="token punctuation">)</span>.<br />closed</code></pre>
<p>Our command line test is successful. It should be possible to configure an IMAP client like Thunderbird to read the mailbox. Keep in mind many IMAP clients try to make configuration easy by using DNS settings which (presumably) won't exist for your development mail server.</p>
HTTPS for WildFly 202020-08-09T16:00:00Zhttp://labnotes.decampo.org/2020/08/09/wildfly-20-https.html<p>The following documents installing an actual certificate in on WildFly 20, although these steps have been tested on WildFly 19 as well.</p>
<p>In order to configure WildFly to use HTTPS with an actual certificate, we start by generating a certificate request:</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>wildfly@wxyz wildfly<span class="token punctuation">]</span>$ <span class="token function">mkdir</span> httpscert<br /><span class="token punctuation">[</span>wildfly@wxyz wildfly<span class="token punctuation">]</span>$ <span class="token builtin class-name">cd</span> httpscert<br /><span class="token punctuation">[</span>wildfly@wxyz httpscert<span class="token punctuation">]</span>$ <span class="token assign-left variable">server</span><span class="token operator">=</span>full.name.of.the.server<br /><span class="token punctuation">[</span>wildfly@wxyz httpscert<span class="token punctuation">]</span>$ keytool -alias tomcat -keyalg RSA -keystore https.keystore -genkey<br /><span class="token punctuation">[</span>wildfly@wxyz httpscert<span class="token punctuation">]</span>$ keytool -alias tomcat -keyalg RSA -keystore https.keystore -certreq -file <span class="token variable">${server}</span>.csr</code></pre>
<p>Note that the <code>keytool</code> utility comes with the Java distribution.</p>
<p>Now submit the request to the certificate authority and get the signed certificate back. You may get some intermediate certificates along with the signed certificate, these will need to be imported into your keystore.</p>
<p>If for some reason the root certificate is not recognized, it will need to be imported as well. In my case I was using my company certificate authority so I imported the root certificate.</p>
<pre class="language-shell"><code class="language-shell"><span class="token comment"># Import the root certificate, if necessary:</span><br /><span class="token punctuation">[</span>wildfly@wxyz httpscert<span class="token punctuation">]</span>$ keytool -alias root -keystore https.keystore -import -file ca.cert.pem<br /><span class="token comment"># Import any intermediates (use a different alias for each):</span><br /><span class="token punctuation">[</span>wildfly@wxyz httpscert<span class="token punctuation">]</span>$ keytool -alias intermediate -keystore https.keystore -import -file ca-chain.cert.pem<br /><span class="token comment"># NOTE: In my case ca-chain.cert.pem has multiple certificates, keytool takes the first one</span><br /><span class="token comment"># Import the actual certificate</span><br /><span class="token punctuation">[</span>wildfly@wxyz httpscert<span class="token punctuation">]</span>$ keytool -alias tomcat -keystore https.keystore -import -file <span class="token variable">${server}</span>.cert.pem<br /><span class="token comment"># Put the keystore in the right place</span><br /><span class="token punctuation">[</span>wildfly@wxyz httpscert<span class="token punctuation">]</span>$ <span class="token function">mv</span> https.keystore <span class="token punctuation">..</span>/wildfly/standalone/configuration/</code></pre>
<p>Next we configure WildFly as in <a href="https://docs.wildfly.org/20/WildFly_Elytron_Security.html#configure-ssltls">https://docs.wildfly.org/20/WildFly_Elytron_Security.html#configure-ssltls</a>.</p>
<p>Create a <code>configure-https.txt</code> file of commands for the WildFly command line tool:</p>
<pre class="language-none"><code class="language-none">connect<br />batch<br />/subsystem=elytron/key-store=https-key-store:add(path=https.keystore,relative-to=jboss.server.config.dir,credential-reference={clear-text=yourpassword},type=JKS)<br />/subsystem=elytron/key-manager=https-key-manager:add(key-store=https-key-store,credential-reference={clear-text=itsdifferentnow})<br />/subsystem=elytron/server-ssl-context=https-ssl-context:add(key-manager=https-key-manager,protocols=["TLSv1.2"])<br />/subsystem=undertow/server=default-server/https-listener=https:undefine-attribute(name=security-realm)<br />/subsystem=undertow/server=default-server/https-listener=https:write-attribute(name=ssl-context,value=https-ssl-context)<br />run-batch<br />reload<br />exit</code></pre>
<p>Note you'll need to substitute in your actual keystore password in the above.</p>
<p>Then we run the commands:</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>wildfly@wxyz httpscert<span class="token punctuation">]</span>$ <span class="token builtin class-name">cd</span> <span class="token punctuation">..</span><br /><span class="token punctuation">[</span>wildfly@wxyz wildfly<span class="token punctuation">]</span>$ wildfly/bin/jboss-cli.sh <span class="token operator"><</span>configure-https.txt</code></pre>
<p>WildFly should be using your new certificate to handle HTTPS requests now. Note that WildFly by default listens on 8443 for HTTPS.</p>
<p>If desired, you can forward the default HTTPS port 443 to port 8443 on CentOS or RedHat systems with the following command:</p>
<pre class="language-none"><code class="language-none">[root@wxyz ~]# firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=8443</code></pre>
Compiling Subversion Python 2 Bindings for CentOS 82020-08-02T15:00:00Zhttp://labnotes.decampo.org/2020/08/02/subversion-python2-centos8.html<p>I recently needed to install Trac 1.4 on a CentOS 8 server. Trac 1.4 still uses Python 2 and while CentOS 8 supports Python 2, Python 3 is the default. I suspect this went into the decision not to offer any Python bindings for Subversion in the CentOS 8 repositories.</p>
<p>I also checked WANDisco, which typically has advanced versions of Subversion packaged for CentOS, but they did not have the Python bindings for Python 2, only Python 3.</p>
<p>As a result, I was forced to compile the bindings myself.</p>
<p>I started with a new CentOS 8 VM created just for this purpose, selecting the "Development Tools" and "RPM Development Tools" software groups for installation.</p>
<p>From there we create a user named <code>mockbuild</code> which will perform the actual build. This username is referenced in the source RPMs for CentOS 8 and prevents spurious error messages. Plus it's a throwaway instance so it doesn't really matter.</p>
<pre class="language-none"><code class="language-none">[root@localhost ~]# useradd -G wheel mockbuild<br />[root@localhost ~]# passwd mockbuild<br />[root@localhost ~]# dnf install yum-utils</code></pre>
<p>We installed <code>yum-utils</code> as well to get <code>yumdownloader</code>. This tool will help us obtain the source RPMs.</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>mockbuild@localhost ~<span class="token punctuation">]</span>$ yumdownloader --source subversion<br /><span class="token punctuation">[</span>mockbuild@localhost ~<span class="token punctuation">]</span>$ <span class="token function">rpm</span> -ivh subversion-1.10.2-1.module_el8.0.0+45+75bba4f4.src.rpm<br />Updating / installing<span class="token punctuation">..</span>.<br /> <span class="token number">1</span>:subversion-1.10.2-1.module_el8.0.<span class="token comment">################################# [100%]</span></code></pre>
<p>We are also going to need Python 2:</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>mockbuild@localhost ~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> dnf <span class="token function">install</span> python2 python2-devel<br /><span class="token punctuation">[</span>mockbuild@localhost ~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> alternatives --set python /usr/bin/python2</code></pre>
<p>The next step is to install the dependencies. The <code>yum-builddep</code> tool can help us here:</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>mockbuild@localhost ~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> dnf config-manager --set-enabled PowerTools<br /><span class="token punctuation">[</span>mockbuild@localhost ~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> yum-builddep subversion<br />Package perl-generators-1.10-9.el8.noarch is already installed.<br />Package libtool-2.4.6-25.el8.x86_64 is already installed.<br />Package autoconf-2.69-27.el8.noarch is already installed.<br />Package gettext-0.19.8.1-17.el8.x86_64 is already installed.<br />Package systemd-239-30.el8_2.x86_64 is already installed.<br />Package zip-3.0-23.el8.x86_64 is already installed.<br />Package unzip-6.0-43.el8.x86_64 is already installed.<br />Package which-2.21-12.el8.x86_64 is already installed.<br />No matching package to install: <span class="token string">'utf8proc-devel'</span><br />No matching package to install: <span class="token string">'libserf-devel >= 1.3.0'</span><br />Not all dependencies satisfied<br />Error: Some packages could not be found.</code></pre>
<p>For the dependencies which were not found, we'll need to download and compile them ourselves. (In general, however, double check that the packages are not in a disabled repository like PowerTools.)</p>
<p>We'll start with <code>utf8proc-devel</code>:</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>mockbuild@localhost ~<span class="token punctuation">]</span>$ yumdownloader --source utf8proc<br /><span class="token punctuation">[</span>mockbuild@localhost ~<span class="token punctuation">]</span>$ <span class="token function">rpm</span> -ivh utf8proc-2.1.1-4.module_el8.0.0+45+75bba4f4.src.rpm<br /><span class="token punctuation">[</span>mockbuild@localhost ~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> yum-builddep utf8proc<br />Package gcc-8.3.1-5.el8.0.2.x86_64 is already installed.<br />Dependencies resolved.<br />Nothing to do.<br />Complete<span class="token operator">!</span><br /><span class="token punctuation">[</span>mockbuild@localhost ~<span class="token punctuation">]</span>$ <span class="token builtin class-name">cd</span> rpmbuild/SPECS/<br /><span class="token punctuation">[</span>mockbuild@localhost SPECS<span class="token punctuation">]</span>$ rpmbuild -ba utf8proc.spec<br /><span class="token comment"># Output elided for brevity</span><br /><span class="token punctuation">[</span>mockbuild@localhost SPECS<span class="token punctuation">]</span>$ <span class="token builtin class-name">cd</span> <span class="token punctuation">..</span>/RPMS/x86_64/<br /><span class="token punctuation">[</span>mockbuild@localhost x86_64<span class="token punctuation">]</span>$ <span class="token function">sudo</span> <span class="token function">rpm</span> -ivh utf8proc-2.1.1-4.el8.x86_64.rpm utf8proc-devel-2.1.1-4.el8.x86_64.rpm<br /><span class="token punctuation">[</span>sudo<span class="token punctuation">]</span> password <span class="token keyword">for</span> mockbuild:<br />Verifying<span class="token punctuation">..</span>. <span class="token comment">################################# [100%]</span><br />Preparing<span class="token punctuation">..</span>. <span class="token comment">################################# [100%]</span><br />Updating / installing<span class="token punctuation">..</span>.<br /> <span class="token number">1</span>:utf8proc-2.1.1-4.el8 <span class="token comment">################################# [ 50%]</span><br /> <span class="token number">2</span>:utf8proc-devel-2.1.1-4.el8 <span class="token comment">################################# [100%]</span></code></pre>
<p>Next the <code>libserf-devel</code> library:</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>mockbuild@localhost x86_64<span class="token punctuation">]</span>$ <span class="token builtin class-name">cd</span><br /><span class="token punctuation">[</span>mockbuild@localhost ~<span class="token punctuation">]</span>$ yumdownloader --source libserf<br /><span class="token punctuation">[</span>mockbuild@localhost ~<span class="token punctuation">]</span>$ <span class="token function">rpm</span> -ivh libserf-1.3.9-8.module_el8.0.0+45+75bba4f4.src.rpm<br />Updating / installing<span class="token punctuation">..</span>.<br /> <span class="token number">1</span>:libserf-1.3.9-8.module_el8.0.0+45<span class="token comment">################################# [100%]</span><br /><span class="token punctuation">[</span>mockbuild@localhost ~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> yum-builddep libserf<br /><span class="token comment"># Eliding 24 packages installed</span><br /><span class="token punctuation">[</span>mockbuild@localhost ~<span class="token punctuation">]</span>$ <span class="token builtin class-name">cd</span> rpmbuild/SPECS/<br /><span class="token punctuation">[</span>mockbuild@localhost SPECS<span class="token punctuation">]</span>$ rpmbuild -ba libserf.spec<br /><span class="token comment"># Eliding output for brevity</span><br /><span class="token punctuation">[</span>mockbuild@localhost SPECS<span class="token punctuation">]</span>$ <span class="token builtin class-name">cd</span> <span class="token punctuation">..</span>/RPMS/x86_64/<br /><span class="token punctuation">[</span>mockbuild@localhost x86_64<span class="token punctuation">]</span>$ <span class="token function">sudo</span> <span class="token function">rpm</span> -ivh libserf-1.3.9-8.el8.x86_64.rpm libserf-devel-1.3.9-8.el8.x86_64.rpm<br />Verifying<span class="token punctuation">..</span>. <span class="token comment">################################# [100%]</span><br />Preparing<span class="token punctuation">..</span>. <span class="token comment">################################# [100%]</span><br />Updating / installing<span class="token punctuation">..</span>.<br /> <span class="token number">1</span>:libserf-1.3.9-8.el8 <span class="token comment">################################# [ 50%]</span><br /> <span class="token number">2</span>:libserf-devel-1.3.9-8.el8 <span class="token comment">################################# [100%]</span></code></pre>
<p>Ok, we are getting good at this. Let's see how we are with the dependencies for <code>subversion</code>:</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>mockbuild@localhost ~<span class="token punctuation">]</span>$ <span class="token function">sudo</span> yum-builddep subversion<br />Error:<br /> Problem: conflicting requests<br /> - package junit-1:4.12-9.module_el8.0.0+30+832da3a1.noarch is filtered out by modular filtering<br /><span class="token punctuation">(</span>try to <span class="token function">add</span> <span class="token string">'--skip-broken'</span> to skip uninstallable packages or <span class="token string">'--nobest'</span> to use not only best candidate packages<span class="token punctuation">)</span></code></pre>
<p>We have a package that is available somewhere but is being filtered. Let's track it down:</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>mockbuild@localhost SPECS<span class="token punctuation">]</span>$ dnf module provides junit<br />Last metadata expiration check: <span class="token number">0</span>:37:17 ago on Wed <span class="token number">15</span> Jul <span class="token number">2020</span> 06:31:36 AM EDT.<br />junit-1:4.12-9.module_el8.0.0+30+832da3a1.noarch<br />Module <span class="token builtin class-name">:</span> javapackages-tools:201801:8000020190628172923:b07bea58:x86_64<br />Profiles <span class="token builtin class-name">:</span><br />Repo <span class="token builtin class-name">:</span> PowerTools<br />Summary <span class="token builtin class-name">:</span> Tools and macros <span class="token keyword">for</span> Java packaging support<br /><span class="token punctuation">[</span>mockbuild@localhost SPECS<span class="token punctuation">]</span>$ dnf module list javapackages-tools<br />Last metadata expiration check: <span class="token number">0</span>:39:15 ago on Wed <span class="token number">15</span> Jul <span class="token number">2020</span> 06:31:36 AM EDT.<br />CentOS-8 - PowerTools<br />Name Stream Profiles Summary<br />javapackages-tools <span class="token number">201801</span> common Tools and macros <span class="token keyword">for</span> Java packaging support<br /><br />Hint: <span class="token punctuation">[</span>d<span class="token punctuation">]</span>efault, <span class="token punctuation">[</span>e<span class="token punctuation">]</span>nabled, <span class="token punctuation">[</span>x<span class="token punctuation">]</span>disabled, <span class="token punctuation">[</span>i<span class="token punctuation">]</span>nstalled<br /><span class="token punctuation">[</span>mockbuild@localhost SPECS<span class="token punctuation">]</span>$ <span class="token function">sudo</span> dnf module <span class="token function">install</span> javapackages-tools:201801/common</code></pre>
<p>With that last dependency is resolved, <code>yum-builddep subversion</code> finishes successfully and we are ready to build <code>subversion</code>.</p>
<p>Except that we have a small diversion in the form of a cautionary tale of a developer who failed to thoroughly read the documentation beforing diving in. The source RPM has installed the files in the <code>rpmbuild/</code> directory. The <code>rpmbuild/SPECS/subversion.spec</code> file contains the details about how to build the software and the RPMs. At the top of this file are a number of directives configuring the build:</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>mockbuild@localhost ~<span class="token punctuation">]</span>$ <span class="token function">head</span> rpmbuild/SPECS/subversion.spec<br /><span class="token comment"># set to zero to avoid running test suite</span><br /><br />%bcond_without kwallet<br />%bcond_without python2<br />%bcond_with python3<br />%bcond_without bdb<br />%bcond_without tests<br />%bcond_without pyswig<br /><br />%ifarch %<span class="token punctuation">{</span>power64<span class="token punctuation">}</span> s390x</code></pre>
<p>Originally I naively tried editing the <code>subversion.spec</code> file e.g. changing <code>%bcond_without python2</code> to <code>%bcond_with python2</code>. Unfortunately (for me), the <code>%bcond</code> directives work the opposite of the way my intuition thought. So I was disabling Python 2 when I thought I was enabling it. See <a href="http://rpm.org/user_doc/conditional_builds.html">http://rpm.org/user_doc/conditional_builds.html</a> for details.</p>
<p>Furthermore, the <code>%bcond</code> directives are designed to create command line switches. So there is no need to edit the <code>subversion.spec</code> file at all.</p>
<p>(The worst part was that the build succeeded but took hours because I had't disabled the unit tests. And I didn't end up with the Python 2 bindings.)</p>
<p>So it turns out the spec file is configured to use Python 2 already. We are going to disable the tests and the <code>kwallet</code> integration via command line switches. So the correct command is:</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>mockbuild@localhost SPECS<span class="token punctuation">]</span>$ rpmbuild -ba subversion.spec --without kwallet --without tests</code></pre>
<p>Finally we tar up the resulting RPMs for installation on the target server:</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span>mockbuild@localhost rpmbuild<span class="token punctuation">]</span>$ <span class="token builtin class-name">cd</span> RPMS/<br /><span class="token punctuation">[</span>mockbuild@localhost RPMS<span class="token punctuation">]</span>$ <span class="token function">tar</span> cvf svn-1.10.2.tar *<br />noarch/<br />noarch/subversion-javahl-1.10.2-1.el8.noarch.rpm<br />x86_64/<br />x86_64/utf8proc-2.1.1-4.el8.x86_64.rpm<br />x86_64/utf8proc-devel-2.1.1-4.el8.x86_64.rpm<br />x86_64/utf8proc-debugsource-2.1.1-4.el8.x86_64.rpm<br />x86_64/utf8proc-debuginfo-2.1.1-4.el8.x86_64.rpm<br />x86_64/libserf-1.3.9-8.el8.x86_64.rpm<br />x86_64/libserf-devel-1.3.9-8.el8.x86_64.rpm<br />x86_64/libserf-debugsource-1.3.9-8.el8.x86_64.rpm<br />x86_64/libserf-debuginfo-1.3.9-8.el8.x86_64.rpm<br />x86_64/subversion-1.10.2-1.el8.x86_64.rpm<br />x86_64/subversion-libs-1.10.2-1.el8.x86_64.rpm<br />x86_64/python2-subversion-1.10.2-1.el8.x86_64.rpm<br />x86_64/subversion-devel-1.10.2-1.el8.x86_64.rpm<br />x86_64/subversion-gnome-1.10.2-1.el8.x86_64.rpm<br />x86_64/mod_dav_svn-1.10.2-1.el8.x86_64.rpm<br />x86_64/subversion-perl-1.10.2-1.el8.x86_64.rpm<br />x86_64/subversion-ruby-1.10.2-1.el8.x86_64.rpm<br />x86_64/subversion-tools-1.10.2-1.el8.x86_64.rpm<br />x86_64/subversion-debugsource-1.10.2-1.el8.x86_64.rpm<br />x86_64/subversion-debuginfo-1.10.2-1.el8.x86_64.rpm<br />x86_64/subversion-libs-debuginfo-1.10.2-1.el8.x86_64.rpm<br />x86_64/python2-subversion-debuginfo-1.10.2-1.el8.x86_64.rpm<br />x86_64/subversion-devel-debuginfo-1.10.2-1.el8.x86_64.rpm<br />x86_64/subversion-gnome-debuginfo-1.10.2-1.el8.x86_64.rpm<br />x86_64/mod_dav_svn-debuginfo-1.10.2-1.el8.x86_64.rpm<br />x86_64/subversion-perl-debuginfo-1.10.2-1.el8.x86_64.rpm<br />x86_64/subversion-ruby-debuginfo-1.10.2-1.el8.x86_64.rpm<br />x86_64/subversion-tools-debuginfo-1.10.2-1.el8.x86_64.rpm</code></pre>
<p>Now, on the target server, we install the version of Subversion we compiled. We do this because the Python bindings and Subversion need to be the same version. Then we prevent <code>dnf</code> from updating the <code>subversion</code> package and creating an issue.</p>
<pre class="language-none"><code class="language-none">[root@target x86_64]# rpm -ivh subversion-1.10.2-1.el8.x86_64.rpm \<br />subversion-libs-1.10.2-1.el8.x86_64.rpm \<br />utf8proc-2.1.1-4.el8.x86_64.rpm \<br />mod_dav_svn-1.10.2-1.el8.x86_64.rpm \<br />python2-subversion-1.10.2-1.el8.x86_64.rpm<br />Verifying... ################################# [100%]<br />Preparing... ################################# [100%]<br />Updating / installing...<br /> 1:utf8proc-2.1.1-4.el8 ################################# [ 20%]<br /> 2:subversion-libs-1.10.2-1.el8 ################################# [ 40%]<br /> 3:subversion-1.10.2-1.el8 ################################# [ 60%]<br /> 4:mod_dav_svn-1.10.2-1.el8 ################################# [ 80%]<br /> 5:python2-subversion-1.10.2-1.el8 ################################# [100%]<br />[root@target dnf]# cd /etc/dnf<br />[root@target dnf]# cp dnf.conf dnf.conf.in<br />[root@target dnf]# vi dnf.conf<br />[root@target dnf]# diff dnf.conf.in dnf.conf<br />6a7<br />> exclude=subversion subversion-libs mod_dav_svn</code></pre>
Mocking Static Methods in Mockito2020-07-31T13:00:00Zhttp://labnotes.decampo.org/2020/07/31/mockito-static-methods.html<p>Mockito has been able to mock static methods since version 3.4.0 but the documentation doesn't explain how to mock methods that take arguments.</p>
<p>E.g. to mock the JavaMail Transport class:</p>
<pre class="language-java"><code class="language-java"> messageCaptor <span class="token operator">=</span> <span class="token class-name">ArgumentCaptor</span><span class="token punctuation">.</span><span class="token function">forClass</span><span class="token punctuation">(</span><span class="token class-name">Message</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">try</span> <span class="token punctuation">(</span><span class="token class-name">MockedStatic</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">Transport</span><span class="token punctuation">></span></span> transport <span class="token operator">=</span> <span class="token function">mockStatic</span><span class="token punctuation">(</span><span class="token class-name">Transport</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> <span class="token comment">// mailer.send invokes Transport</span><br /> mailer<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>from<span class="token punctuation">,</span> <span class="token keyword">to</span><span class="token punctuation">,</span> subject<span class="token punctuation">,</span> body<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> transport<span class="token punctuation">.</span><span class="token function">verify</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">Transport</span><span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>messageCaptor<span class="token punctuation">.</span><span class="token function">capture</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token comment">// Now we can see what mailer passed to Transport.send()</span><br /> <span class="token keyword">final</span> <span class="token class-name">Message</span> message <span class="token operator">=</span> messageCaptor<span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span></code></pre>
<p>or if we want to specify behavior:</p>
<pre class="language-java"><code class="language-java"> <span class="token keyword">try</span> <span class="token punctuation">(</span><span class="token class-name">MockedStatic</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">Transport</span><span class="token punctuation">></span></span> transport <span class="token operator">=</span> <span class="token function">mockStatic</span><span class="token punctuation">(</span><span class="token class-name">Transport</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">{</span><br /> transport<span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">Transport</span><span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token function">any</span><span class="token punctuation">(</span><span class="token class-name">Message</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">thenThrow</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">MessagingException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> mailer<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>from<span class="token punctuation">,</span> <span class="token keyword">to</span><span class="token punctuation">,</span> subject<span class="token punctuation">,</span> body<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span></code></pre>
Convert Hyper-V disk to KVM2020-07-11T19:00:00Zhttp://labnotes.decampo.org/2020/07/11/convert-hyper-v-to-kvm.html<p>When converting a Hyper-V VM to KVM (as used in CentOS or RHEL) it is not enough
to simply convert the disk from vhd to qcow2 using <code>qemu</code>. Doing so will result
in errors concerning <code>dracut</code> timing out. Instead one must convert the guest OS
as well by using <code>virt-v2v</code>.</p>
<p>Sometimes you will run into the following error when attempting to convert the
disk:</p>
<pre class="language-shell"><code class="language-shell">$ <span class="token function">sudo</span> virt-v2v -i disk /var/images/virdisk.vhd -o <span class="token builtin class-name">local</span> -of qcow2 -os /var/lib/libvirt/images/<br /><br />virt-v2v: error: libguestfs error: part_get_parttype: <span class="token function">parted</span> exited with<br />status <span class="token number">1</span>: Error: Can't have a partition outside the disk<span class="token operator">!</span></code></pre>
<p>In this case for some reason Hyper-V created the partitions on the disk with
geometry outside the "physical" geometry of the disk. To repair this we need
to convert the disk to RAW format in order to operate on the partition table.
Be warned that the RAW disk image will be as big as the logical size of the
VHD disk.</p>
<p>Convert to RAW format:</p>
<pre class="language-shell"><code class="language-shell">$ qemu-img convert virdisk.vhd -O raw virdisk.raw</code></pre>
<p>Issue the command <code>fdisk virdisk.raw</code> to operate on the partition table
with fdisk. Use <code>p</code> to print the table and <code>v</code> command to verify the issue:</p>
<pre class="language-shell"><code class="language-shell">$ <span class="token function">fdisk</span> virdisk.raw<br />Command <span class="token punctuation">(</span>m <span class="token keyword">for</span> <span class="token builtin class-name">help</span><span class="token punctuation">)</span>: p<br />Disk virdisk.raw: <span class="token number">64</span> GiB, <span class="token number">68718428160</span> bytes, <span class="token number">134215680</span> sectors<br />Units: sectors of <span class="token number">1</span> * <span class="token number">512</span> <span class="token operator">=</span> <span class="token number">512</span> bytes<br />Sector size <span class="token punctuation">(</span>logical/physical<span class="token punctuation">)</span>: <span class="token number">512</span> bytes / <span class="token number">512</span> bytes<br />I/O size <span class="token punctuation">(</span>minimum/optimal<span class="token punctuation">)</span>: <span class="token number">512</span> bytes / <span class="token number">512</span> bytes<br />Disklabel type: dos<br />Disk identifier: 0x000484cd<br /><br />Device Boot Start End Sectors Size Id Type<br />virdisk.raw1 * <span class="token number">2048</span> <span class="token number">2099199</span> <span class="token number">2097152</span> 1G <span class="token number">83</span> Linux<br />virdisk.raw2 <span class="token number">2099200</span> <span class="token number">134217727</span> <span class="token number">132118528</span> 63G 8e Linux LVM<br /><br />Command <span class="token punctuation">(</span>m <span class="token keyword">for</span> <span class="token builtin class-name">help</span><span class="token punctuation">)</span>: <span class="token function">v</span><br />Total allocated sectors <span class="token number">134217728</span> greater than the maximum <span class="token number">134215680</span>.</code></pre>
<p>Now this part is "dangerous" or would be if you weren't already operating on a
copy of the virtual disk. Delete the problematic partition and recreate it with
the same starting point but with an end point that is proper. In my case this
did not destroy any data, your mileage may vary.</p>
<pre class="language-shell"><code class="language-shell">Command <span class="token punctuation">(</span>m <span class="token keyword">for</span> <span class="token builtin class-name">help</span><span class="token punctuation">)</span>: d<br />Partition number <span class="token punctuation">(</span><span class="token number">1,2</span>, default <span class="token number">2</span><span class="token punctuation">)</span>: <span class="token number">2</span><br /><br />Partition <span class="token number">2</span> has been deleted.<br /><br />Command <span class="token punctuation">(</span>m <span class="token keyword">for</span> <span class="token builtin class-name">help</span><span class="token punctuation">)</span>: n<br />Partition <span class="token builtin class-name">type</span><br /> p primary <span class="token punctuation">(</span><span class="token number">1</span> primary, <span class="token number">0</span> extended, <span class="token number">3</span> <span class="token function">free</span><span class="token punctuation">)</span><br /> e extended <span class="token punctuation">(</span>container <span class="token keyword">for</span> logical partitions<span class="token punctuation">)</span><br />Select <span class="token punctuation">(</span>default p<span class="token punctuation">)</span>: p<br />Partition number <span class="token punctuation">(</span><span class="token number">2</span>-4, default <span class="token number">2</span><span class="token punctuation">)</span>: <span class="token number">2</span><br />First sector <span class="token punctuation">(</span><span class="token number">2099200</span>-134215679, default <span class="token number">2099200</span><span class="token punctuation">)</span>:<br />Last sector, +sectors or +size<span class="token punctuation">{</span>K,M,G,T,P<span class="token punctuation">}</span> <span class="token punctuation">(</span><span class="token number">2099200</span>-134215679, default <span class="token number">134215679</span><span class="token punctuation">)</span>:<br /><br />Created a new partition <span class="token number">2</span> of <span class="token builtin class-name">type</span> <span class="token string">'Linux'</span> and of size <span class="token number">63</span> GiB.<br />Partition <span class="token comment">#2 contains a LVM2_member signature.</span><br /><br />Do you want to remove the signature? <span class="token punctuation">[</span>Y<span class="token punctuation">]</span>es/<span class="token punctuation">[</span>N<span class="token punctuation">]</span>o: N</code></pre>
<p>Finally use <code>w</code> to write the new partition table.</p>
<p>Now convert the raw image:</p>
<pre class="language-shell"><code class="language-shell">$ <span class="token function">sudo</span> virt-v2v -i disk /var/images/virdisk.raw -o <span class="token builtin class-name">local</span> -of qcow2 -os /var/lib/libvirt/images/<br /><span class="token punctuation">[</span> <span class="token number">0.0</span><span class="token punctuation">]</span> Opening the <span class="token builtin class-name">source</span> -i disk /var/images/virdisk.raw<br /><span class="token punctuation">[</span> <span class="token number">0.0</span><span class="token punctuation">]</span> Creating an overlay to protect the <span class="token builtin class-name">source</span> from being modified<br /><span class="token punctuation">[</span> <span class="token number">0.2</span><span class="token punctuation">]</span> Initializing the target -o <span class="token builtin class-name">local</span> -os /var/lib/libvirt/images/<br /><span class="token punctuation">[</span> <span class="token number">0.2</span><span class="token punctuation">]</span> Opening the overlay<br /><span class="token punctuation">[</span> <span class="token number">5.1</span><span class="token punctuation">]</span> Inspecting the overlay<br /><span class="token punctuation">[</span> <span class="token number">28.7</span><span class="token punctuation">]</span> Checking <span class="token keyword">for</span> sufficient <span class="token function">free</span> disk space <span class="token keyword">in</span> the guest<br /><span class="token punctuation">[</span> <span class="token number">28.7</span><span class="token punctuation">]</span> Estimating space required on target <span class="token keyword">for</span> each disk<br /><span class="token punctuation">[</span> <span class="token number">28.7</span><span class="token punctuation">]</span> Converting CentOS Linux release <span class="token number">7.8</span>.2003 <span class="token punctuation">(</span>Core<span class="token punctuation">)</span> to run on KVM<br />virt-v2v: This guest has virtio drivers installed.<br /><span class="token punctuation">[</span> <span class="token number">112.8</span><span class="token punctuation">]</span> Mapping filesystem data to avoid copying unused and blank areas<br /><span class="token punctuation">[</span> <span class="token number">114.0</span><span class="token punctuation">]</span> Closing the overlay<br /><span class="token punctuation">[</span> <span class="token number">114.3</span><span class="token punctuation">]</span> Checking <span class="token keyword">if</span> the guest needs BIOS or UEFI to boot<br /><span class="token punctuation">[</span> <span class="token number">114.3</span><span class="token punctuation">]</span> Assigning disks to buses<br /><span class="token punctuation">[</span> <span class="token number">114.3</span><span class="token punctuation">]</span> Copying disk <span class="token number">1</span>/1 to /var/lib/libvirt/images/virdisk-sda <span class="token punctuation">(</span>qcow2<span class="token punctuation">)</span><br /> <span class="token punctuation">(</span><span class="token number">100.00</span>/100%<span class="token punctuation">)</span><br /><span class="token punctuation">[</span> <span class="token number">117.4</span><span class="token punctuation">]</span> Creating output metadata<br /><span class="token punctuation">[</span> <span class="token number">117.4</span><span class="token punctuation">]</span> Finishing off<br />$ <span class="token function">sudo</span> <span class="token function">ls</span> -l /var/lib/libvirt/images/<br />total <span class="token number">2280904</span><br />-rw-r--r--. <span class="token number">1</span> root root <span class="token number">2335703040</span> Jun <span class="token number">15</span> <span class="token number">14</span>:34 virdisk-sda<br />-rw-r--r--. <span class="token number">1</span> root root <span class="token number">1270</span> Jun <span class="token number">15</span> <span class="token number">14</span>:34 virdisk.xml</code></pre>
<p>Congratulations, the disk is converted.</p>
<p>If you see the following error:</p>
<pre class="language-shell"><code class="language-shell"><span class="token punctuation">[</span> <span class="token number">113.7</span><span class="token punctuation">]</span> Copying disk <span class="token number">1</span>/1 to /var/lib/libvirt/images/virdisk-sda <span class="token punctuation">(</span>qcow2<span class="token punctuation">)</span><br />virt-v2v: error: libguestfs error: qemu-img:<br />/var/lib/libvirt/images/virdisk-sda: qemu-img exited with error status <span class="token number">1</span>.<br />To see full error messages you may need to <span class="token builtin class-name">enable</span> debugging.<br />Do:<br /> <span class="token builtin class-name">export</span> <span class="token assign-left variable">LIBGUESTFS_DEBUG</span><span class="token operator">=</span><span class="token number">1</span> <span class="token assign-left variable">LIBGUESTFS_TRACE</span><span class="token operator">=</span><span class="token number">1</span><br />and run the <span class="token builtin class-name">command</span> again. For further information, read:<br /> http://libguestfs.org/guestfs-faq.1.html<span class="token comment">#debugging-libguestfs</span><br />You can also run <span class="token string">'libguestfs-test-tool'</span> and post the *complete* output<br />into a bug report or message to the libguestfs mailing list.<br /><br />If reporting bugs, run virt-v2v with debugging enabled and include the<br />complete output:<br /><br /> virt-v2v -v -x <span class="token punctuation">[</span><span class="token punctuation">..</span>.<span class="token punctuation">]</span></code></pre>
<p>It is because the user you are running as does not have permission to write to
the target directory, in my case <code>/var/lib/libvirt/images</code>.</p>
<p>Finally, once the VM is booted, we should repair the filesystem in case it still
thinks it has the old size. To repair filesystem, go into rescue mode, unmount,
run fsck/xfs_repair, remount and exit:</p>
<pre class="language-none"><code class="language-none"># systemctl rescue<br /># umount /dev/mapper/centos_virdisk-home<br /># fsck /dev/mapper/centos_virdisk-home<br /># xfs_repair /dev/mapper/centos_virdisk-home<br /># mount /dev/mapper/centos_virdisk-home<br /># exit</code></pre>
SLF4J issue with WildFly2020-07-03T18:00:00Zhttp://labnotes.decampo.org/2020/07/03/wildfly-slf4j-classcastexception.html<p>If you are getting the following error on WildFly or JBoss:</p>
<blockquote>
<p>Caused by: java.lang.ClassCastException: class org.slf4j.impl.Slf4jLoggerFactory cannot be cast to class ch.qos.logback.classic.LoggerContext (org.slf4j.impl.Slf4jLoggerFactory is in unnamed module of loader 'org.slf4j.impl@1.0.4.GA' @604f2c4f; ch.qos.logback.classic.LoggerContext is in unnamed module of loader 'deployment.idp.war' @4206bf50)"</p>
</blockquote>
<p>Then add a <code>jboss-deployment-structure.xml</code> file to the <code>WEB-INF/</code> directory of your war archive (or the <code>META-INF/</code> in the case of an ear or ejb):</p>
<pre class="language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>jboss-deployment-structure</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>deployment</span><span class="token punctuation">></span></span><br /> <span class="token comment"><!-- Exclude server libraries which conflict with ours --></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>exclusions</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>module</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>org.slf4j<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>module</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>org.slf4j.impl<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>exclusions</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>deployment</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>jboss-deployment-structure</span><span class="token punctuation">></span></span></code></pre>
<p>This will prevent WildFly from using the its versions of the SLF4J libraries for the deployed artifact allowing it to use whatever had been packaged instead.</p>
<p>Note that I encountered this issue when deploying Shibboleth IdP 4.0 on WildFly.</p>
Fixing my Borked Ruby Environment2020-07-02T14:00:00Zhttp://labnotes.decampo.org/2020/07/02/fixing-jekyll-environment.html<p>So after a couple of updates and who knows what else, I found myself with an environment (Fedora 32 if you were wondering) that would no longer run jekyll. This really caused by blogging level to stop completely since I couldn't preview any of the posts. As the potential posts piled up in my drafts folder, I eventually carved out some time to take care of this.</p>
<p>The error I was getting when running jekyll (actually <code>bundle exec jekyll serve</code>) was downright perplexing. It would complain that a shared library for one of the dependent gems was missing. The error message gave the full path to the file. The perplexing thing is that the file existed and my user had permissions to read it.</p>
<p>The library belonged to the commonmark gem. I tried reinstalling it via <code>bundle</code> and via <code>gem</code> both as a regular user and as root. No luck, it refused to find the shared library that was right where it claimed to be looking for it.</p>
<p>So I figured I'd burn the whole thing down and try again. First thing was to
uninstall all gems using <code>gem</code>. User-level gems were not much of a problem, I just used <code>gem uninstall -aIx</code>. For system-level gems I had to repeatedly alternate between these commands until there were no more gems remaining:</p>
<pre class="language-shell"><code class="language-shell"><span class="token function">sudo</span> gem uninstall -aIx<br /><span class="token function">sudo</span> gem uninstall -i /usr/local/share/gems -aIx<br /><span class="token function">sudo</span> gem uninstall -i /usr/share/gems -aIx</code></pre>
<p>With all the gems annihilated from the system, time to reinstall ruby:</p>
<pre class="language-shell"><code class="language-shell"><span class="token function">sudo</span> dnf reinstall ruby libyaml rubygem-psych rubygems rubygem-openssl <span class="token punctuation">\</span><br /> rubygem-bigdecimal rubygem-io-console rubygem-irb rubygem-json <span class="token punctuation">\</span><br /> rubygem-psych rubygem-rdoc</code></pre>
<p>At this point I also made a directory for user-level gems and set <code>$GEM_HOME</code> in my <code>.bashrc</code> but unfortunately there's a bug which prevents gem from respecting that setting. I did eventually use the directory for the install target for bundle however.</p>
<p>Now we install bundler and use it to install jekyll:</p>
<pre class="language-shell"><code class="language-shell">gem <span class="token function">install</span> bundler<br /><span class="token function">rm</span> -rf .bundle<br />bundle config <span class="token builtin class-name">set</span> path <span class="token string">'/home/dev/gems'</span><br /><span class="token comment"># Edit Gemfile to have the correct version of jekyll that github-pages expects</span><br /><span class="token function">vi</span> Gemfile<br />bundle <span class="token function">install</span><br /><span class="token comment"># Verify it works</span><br />bundle <span class="token builtin class-name">exec</span> jekyll serve<br /><span class="token comment"># commit the Gemfile changes</span><br /><span class="token function">git</span> <span class="token function">add</span> Gemfile Gemfile.lock<br /><span class="token function">git</span> commit<br /><span class="token function">git</span> push</code></pre>
<p>Now that my environment works again I hope to be blogging more.</p>
Configuring Wildfly for JWT Authentication2019-03-17T12:00:00Zhttp://labnotes.decampo.org/2019/03/17/wildfly-jwt-authentication.html<p>In this post we describe how to configure WildFly to access JSON Web Tokens (JWT) via the Authentication header using the Bearer schema. This allows you to install stateless authenticated REST services on WildFly.</p>
<p><strong>NOTE:</strong> Another way to accomplish this is to use the KeyCloak server and install its components in your WildFly server. For large deployments that is the better approach. This approach is more suitable to a small deployment where KeyCloak would be overkill.</p>
<p>We adapt the instructions and code from <a href="https://github.com/wildfly/quickstart/tree/master/jaxrs-jwt">https://github.com/wildfly/quickstart/tree/master/jaxrs-jwt</a>. This adaptation does not use a keystore but simply a pre-generated public/private key pair. This reduces the complexity and positions our server better for cloud deployment (as their are no external files to access).</p>
<h2>Generate the keys</h2>
<p>First we generate a public/private key pair. Note that you will be able to generate different key pairs for different environments (e.g. development, test, production).</p>
<pre class="language-bash"><code class="language-bash"><span class="token comment"># Use an empty passphrase when prompted</span><br />$ ssh-keygen -t rsa -b <span class="token number">4096</span> -m PEM -f jwtRS256.key<br />$ openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub<br /><span class="token comment"># To see the private key:</span><br />$ <span class="token function">more</span> jwtRS256.key<br /><span class="token comment"># To see the public key:</span><br />$ <span class="token function">more</span> jwtRS256.key.pub</code></pre>
<h2>Configure Wildfly</h2>
<p>Here we configure Wildfly to enforce the presence of a bearer token for secure application resources. Start the WildFly admin command line interface:</p>
<pre class="language-bash"><code class="language-bash"><span class="token builtin class-name">cd</span> <span class="token variable">$WILDFLY_HOME</span>/bin<br />./jboss-cli.sh --connect</code></pre>
<p>The remaining commands in this section are input for the WildFly admin CLI.</p>
<p>Add a new token security realm to elytron for authentication using JWTs, using the public key that was generated. Replace the fake key below with the key from your <code>jwtRS256.key.pub</code> file:</p>
<pre class="language-none"><code class="language-none"> /subsystem=elytron/token-realm=jwt-realm:add( \<br /> jwt={ \<br /> issuer=["my-application-issuer"], \<br /> audience=["my-application-audience"], \<br /> public-key="-----BEGIN PUBLIC KEY-----<br /> AXaRqQL18r/NiUV7vpQyLvqjC34pLYF3l3mrpfeIG1bXATsqVmJkNhAkkIyjLcTA<br /> qB5a+lpWb08GtpkcLX7G2+s7Js05CngGv4wGHmp9yiO9z5nMIcQXQXtT41Qn6716<br /> DGbDiTBQ+xycByEXuM6hU25rTnlWfGCRigm0zSjg6716Qr4zsYT7NyQWb+K7ntvd<br /> cuYjOfSbhY0imX6TYU8Edv4YOJe2pBeteHV1UHYcwMBGjt661yWWhx6fwJQMIA+v<br /> rKh58z7Pi5mqr0rFTX9zDK1h79vygXNTAlZcRubVjEpa8fCgvYMrbqq1CC12j07d<br /> dHbCWv3cjoVcnmW4g6k4M6xLx6kpKcBbCDiaEalJ2o872GNMXqJYuFw7YmmQaskw<br /> sUZTJjhu7BgofU3/t01VAWe0ye2s9H0LzUuWNUx1YcKgpi0efGUB/2rmejfFTUr/<br /> 1pbTTXTqxiuB3Jnt+dhA/XJX11ALo27Ngzto0nkC/2s0csGq7uqJ4Dt2bGBvs1jB<br /> DWo76frVXuVNJqJACuZ1eL2JKt12vu7c2an++UooHZDDcRGkbivf4wBycxJmdKpE<br /> nN0SQ9j42ldOVz708CGgbXTJCcaZ6gH0Hbm1d6v+vpZESmaKjUtvFI/gnFGCqqWM<br /> MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxjYdscscXjbDb/kfnfXc<br /> 7ZFoJOusLdXxfouD8WGy5oMCAwEAAQ==<br /> -----END PUBLIC KEY-----" \<br /> },principal-claim="sub")</code></pre>
<p>Add a new security domain, which uses the jwt security realm:</p>
<pre class="language-none"><code class="language-none"> /subsystem=elytron/security-domain=jwt-domain:add( \<br /> realms=[{ \<br /> realm=jwt-realm, \<br /> role-decoder=groups-to-roles \<br /> }],permission-mapper=default-permission-mapper, \<br /> default-realm=jwt-realm)</code></pre>
<p>Create http authentication factory that uses BEARER_TOKEN authentication:</p>
<pre class="language-none"><code class="language-none"> /subsystem=elytron/http-authentication-factory=jwt-http-authentication:add( \<br /> security-domain=jwt-domain, \<br /> http-server-mechanism-factory=global, \<br /> mechanism-configurations=[{ \<br /> mechanism-name="BEARER_TOKEN", \<br /> mechanism-realm-configurations=[{ \<br /> realm-name="jwt-realm" \<br /> }]}])</code></pre>
<p>Configure Undertow to use our http authentication factory for authentication:</p>
<pre class="language-none"><code class="language-none"> /subsystem=undertow/application-security-domain=other:add( \<br /> http-authentication-factory=jwt-http-authentication)</code></pre>
<h2>Configure the Web Application</h2>
<p>A few entries in the <code>web.xml</code> for your web application are required.</p>
<p>First, we put the private key (from your <code>jwtRS256.key</code> file) in an environment entry. Your private key will be much longer than the fake one I have used here:</p>
<pre class="language-xml"><code class="language-xml"> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>env-entry</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>description</span><span class="token punctuation">></span></span>Private key for signing JWTs<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>description</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>env-entry-name</span><span class="token punctuation">></span></span>jwtPrivateKey<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>env-entry-name</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>env-entry-type</span><span class="token punctuation">></span></span>java.lang.String<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>env-entry-type</span><span class="token punctuation">></span></span><br /> <span class="token comment"><!-- The development key, use an overlay in production --></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>env-entry-value</span><span class="token punctuation">></span></span><br />-----BEGIN RSA PRIVATE KEY-----<br />B73cQ1t2nx+0v6Fz7NcfhEbejyk02Kf+NJbKZPuZfnZlHSqH9xecxN2g1OYZtd0n<br />+Gg93QKCAQBd45hMMNohn6d+KkXRnNRkQLzVCQPMCHNAJL6773QPjeSskvvlXVOR<br />E2uxFupdP6qMV/oMHlm+XT6b/AeWma1o3oa6zvoEXx+dx+WTXjTHZEvTguZEcv7w<br />6l1T3bul5ujmK3DbHjfX77V3tDLYN0xQj6KZazRf9MQsZoC5xWKDLPGdfogS+1db<br />sr6Tq725Op4wRRwPDEYZthonddVyuMWrDElkC3HrK+31X+4wuP10gPdiayq2O3p8<br />NyCaimTtIELvLa32hJxsGQe8juTZ15quCmAu4tffmRdX3z1zFXoI6ObkM6hDhGWw<br />myHW4Qzjk6vijIYbeu7n5Tr77w8VSuh9AoIBAFhbLDJPmnC8Tp/BS5U2ntaxS8Qb<br />+icGXNguaHaw9niUCflHeWqaHYDNxGchnlFoEo5WxWuw0hC/peB2bjFLVFYif/mV<br />xUZfLBvKOUbeCor/m+jDoT19AQFpLu/at5Z82nLKyH3CqS4MO9VDs3PPZF6cbV89<br />fC2JD/k0MXuCRPa9t22v4BYBxG1AzcvTr/ly7pxNX8hoEHkNjx3e9ZrjyWa3pWFo<br />is4WSrogQVJHmCAceaTnUrSbfZ3SfAcElA/Qf94HQNrqIT5WetFy6INDdHD8WS61<br />wJMTAe2BezELmGR2NyBQp4IZrBEEtH03dBX9H61lm9raBId4Cc84C/mThes=<br />-----END RSA PRIVATE KEY-----<br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>env-entry-value</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>env-entry</span><span class="token punctuation">></span></span></code></pre>
<p>Next we standard <code>security-constraint</code> and <code>login-config</code> entries. Replace the <code>url-pattern</code> and <code>role-name</code> with appropriate values for your application. The <code>url-pattern</code> determines which REST URLs are protected. The <code>role-name</code> enforces a role for the user (you must have at least one role). There is a lot of flexibity available here that we will not cover. Also replace the <code>realm-name</code> and <code>web-resource-name</code> with appropriate values, although these are not referenced elsewhere.</p>
<pre class="language-xml"><code class="language-xml"> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>security-constraint</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>web-resource-collection</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>web-resource-name</span><span class="token punctuation">></span></span>Secure REST URLs<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>web-resource-name</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>url-pattern</span><span class="token punctuation">></span></span>/admin/*<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>url-pattern</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>web-resource-collection</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>auth-constraint</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>role-name</span><span class="token punctuation">></span></span>administrator<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>role-name</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>auth-constraint</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>security-constraint</span><span class="token punctuation">></span></span><br /><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>login-config</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>auth-method</span><span class="token punctuation">></span></span>BEARER_TOKEN<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>auth-method</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>realm-name</span><span class="token punctuation">></span></span>ACME Corp.<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>realm-name</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>login-config</span><span class="token punctuation">></span></span></code></pre>
<h2>Authenticating Users</h2>
<p>In this section we present some Java snippets to demonstrate how to create and sign a JWT once your user is authenticated. The code here uses Context and Dependency Injection (CDI), it is assumed you are familiar with it.</p>
<p>First, a simple CDI bean to read the private key from the JNDI environment:</p>
<pre class="language-java"><code class="language-java"><span class="token annotation punctuation">@Dependent</span><br /><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">JwtKey</span> <span class="token punctuation">{</span><br /> <span class="token annotation punctuation">@Resource</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"jwtPrivateKey"</span><span class="token punctuation">)</span><br /> <span class="token keyword">private</span> <span class="token class-name">String</span> privateKeyAsString<span class="token punctuation">;</span><br /><br /> <span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">getPrivateKeyAsString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> privateKeyAsString<span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /><span class="token punctuation">}</span></code></pre>
<p>Next we make a producer method for the <code>PrivateKey</code> instance:</p>
<pre class="language-java"><code class="language-java"> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> RSA_STRIP_REGEX <span class="token operator">=</span><br /> <span class="token string">"BEGIN RSA PRIVATE KEY|END RSA PRIVATE KEY|-|\\s"</span><span class="token punctuation">;</span><br /><br /> <span class="token annotation punctuation">@Produces</span><br /> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token class-name">PrivateKey</span> <span class="token function">producePrivateKey</span><span class="token punctuation">(</span><span class="token keyword">final</span> <span class="token class-name">JwtKey</span> jwtKey<span class="token punctuation">)</span><br /> <span class="token keyword">throws</span> <span class="token class-name">KeyStoreException</span><span class="token punctuation">,</span> <span class="token class-name">IOException</span><span class="token punctuation">,</span> <span class="token class-name">GeneralSecurityException</span> <span class="token punctuation">{</span><br /> <span class="token keyword">final</span> <span class="token class-name">String</span> keyString <span class="token operator">=</span> jwtKey<span class="token punctuation">.</span><span class="token function">getPrivateKeyAsString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">final</span> <span class="token class-name">String</span> base64Key <span class="token operator">=</span> keyString<span class="token punctuation">.</span><span class="token function">replaceAll</span><span class="token punctuation">(</span>RSA_STRIP_REGEX<span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">final</span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> keyBytes <span class="token operator">=</span> <span class="token class-name">Base64</span><span class="token punctuation">.</span><span class="token function">getDecoder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">decode</span><span class="token punctuation">(</span>base64Key<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">final</span> <span class="token class-name">PKCS8EncodedKeySpec</span> keySpec <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">PKCS8EncodedKeySpec</span><span class="token punctuation">(</span>keyBytes<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">final</span> <span class="token class-name">KeyFactory</span> keyFactory <span class="token operator">=</span> <span class="token class-name">KeyFactory</span><span class="token punctuation">.</span><span class="token function">getInstance</span><span class="token punctuation">(</span><span class="token string">"RSA"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> keyFactory<span class="token punctuation">.</span><span class="token function">generatePrivate</span><span class="token punctuation">(</span>keySpec<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span></code></pre>
<p>Now the code to create the token using the <a href="https://connect2id.com/products/nimbus-jose-jwt">Nimbus + JOSE library</a>:</p>
<pre class="language-java"><code class="language-java"> <span class="token annotation punctuation">@Inject</span><br /> <span class="token keyword">private</span> <span class="token class-name">PrivateKey</span> privateKey<span class="token punctuation">;</span><br /><br /> <span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">createJwt</span><span class="token punctuation">(</span><br /> <span class="token keyword">final</span> <span class="token class-name">String</span> subject<span class="token punctuation">,</span><br /> <span class="token keyword">final</span> <span class="token class-name">Collection</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">String</span><span class="token punctuation">></span></span> roles<span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token class-name">JOSEException</span> <span class="token punctuation">{</span><br /><br /> <span class="token keyword">final</span> <span class="token class-name">String</span> claims <span class="token operator">=</span> <span class="token class-name">Json</span><span class="token punctuation">.</span><span class="token function">createObjectBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"sub"</span><span class="token punctuation">,</span> subject<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"iss"</span><span class="token punctuation">,</span> ISSUER<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"aud"</span><span class="token punctuation">,</span> AUDIENCE<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"groups"</span><span class="token punctuation">,</span> <span class="token class-name">Json</span><span class="token punctuation">.</span><span class="token function">createArrayBuilder</span><span class="token punctuation">(</span>roles<span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"exp"</span><span class="token punctuation">,</span> <span class="token class-name">System</span><span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">/</span><span class="token number">1000</span> <span class="token operator">+</span> EXPIRATION<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">final</span> <span class="token class-name">JWSHeader</span> header <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">JWSHeader<span class="token punctuation">.</span>Builder</span><span class="token punctuation">(</span><span class="token class-name">JWSAlgorithm</span><span class="token punctuation">.</span>RS256<span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">type</span><span class="token punctuation">(</span><span class="token class-name">JOSEObjectType</span><span class="token punctuation">.</span>JWT<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">final</span> <span class="token class-name">JWSObject</span> jws <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">JWSObject</span><span class="token punctuation">(</span>header<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">Payload</span><span class="token punctuation">(</span>claims<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> jws<span class="token punctuation">.</span><span class="token function">sign</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">RSASSASigner</span><span class="token punctuation">(</span>privateKey<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> jws<span class="token punctuation">.</span><span class="token function">serialize</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span></code></pre>
<p>Finally we define a REST endpoint to send the token to the user:</p>
<pre class="language-java"><code class="language-java"> <span class="token annotation punctuation">@POST</span><br /> <span class="token annotation punctuation">@Path</span><span class="token punctuation">(</span><span class="token string">"/token"</span><span class="token punctuation">)</span><br /> <span class="token annotation punctuation">@Consumes</span><span class="token punctuation">(</span><span class="token class-name">MediaType</span><span class="token punctuation">.</span>APPLICATION_FORM_URLENCODED<span class="token punctuation">)</span><br /> <span class="token keyword">public</span> <span class="token class-name">Response</span> <span class="token function">token</span><span class="token punctuation">(</span><br /> <span class="token annotation punctuation">@FormParam</span><span class="token punctuation">(</span><span class="token string">"username"</span><span class="token punctuation">)</span> <span class="token keyword">final</span> <span class="token class-name">String</span> username<span class="token punctuation">,</span><br /> <span class="token annotation punctuation">@FormParam</span><span class="token punctuation">(</span><span class="token string">"password"</span><span class="token punctuation">)</span> <span class="token keyword">final</span> <span class="token class-name">String</span> password<span class="token punctuation">)</span> <span class="token punctuation">{</span><br /><br /> <span class="token keyword">final</span> <span class="token class-name">User</span> user <span class="token operator">=</span> <span class="token function">authenticate</span><span class="token punctuation">(</span>username<span class="token punctuation">,</span> password<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">if</span> <span class="token punctuation">(</span>user <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><br /> <span class="token keyword">return</span> <span class="token class-name">Response</span><span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token class-name">Response<span class="token punctuation">.</span>Status</span><span class="token punctuation">.</span>UNAUTHORIZED<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span><br /> <span class="token keyword">final</span> <span class="token class-name">List</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">String</span><span class="token punctuation">></span></span> roles <span class="token operator">=</span> <span class="token function">getRolesForUser</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span><br /><br /> <span class="token keyword">final</span> <span class="token class-name">String</span> token <span class="token operator">=</span> jwtCreator<span class="token punctuation">.</span><span class="token function">createJwt</span><span class="token punctuation">(</span>username<span class="token punctuation">,</span> roles<span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">final</span> <span class="token class-name">JsonObject</span> response <span class="token operator">=</span> <span class="token class-name">Json</span><span class="token punctuation">.</span><span class="token function">createObjectBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"user"</span><span class="token punctuation">,</span> user<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"roles"</span><span class="token punctuation">,</span> <span class="token class-name">Json</span><span class="token punctuation">.</span><span class="token function">createArrayBuilder</span><span class="token punctuation">(</span>roles<span class="token punctuation">)</span><span class="token punctuation">)</span><br /> <span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"token"</span><span class="token punctuation">,</span> token<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token keyword">return</span> <span class="token class-name">Response</span><span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>response<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><br /> <span class="token punctuation">}</span></code></pre>
<h2>Client-side</h2>
<p>Just a few notes about the client-side. You can authenticate the user by POSTing to the <code>token</code> endpoint and get back the token in the response. Save the token in whatever mechanism is appropriate for your application.</p>
<p>Whenever accessing a protected URL, include the token using the standard <code>Authentication</code> header using the <code>Bearer</code> schema (see <a href="https://jwt.io/introduction/">https://jwt.io/introduction/</a> for more details). If the token expires or is otherwise invalid, WildFly will return a <code>403 Forbidden</code> response. At that point you can re-prompt the user for credentials and obtain a new token (or take whatever action is appropriate for your application).</p>
<h2>Configure Wildfly overlay</h2>
<p>As a final note, you may be concerned that the private key has been built into the war archive. There is no need to have multiple builds for multiple environments however. By using the <a href="http://docs.wildfly.org/13/Admin_Guide.html#Deployment_Overlays">WildFly overlay functionality</a>, combined with the override ability via <code>jboss-web.xml</code> we can replace the development private key with keys for each environment.</p>
<p>First, create <code>/path/to/overlay/jboss-web.xml</code> file containing the actual private key (obviously use a path that makes sense for your environment):</p>
<pre class="language-xml"><code class="language-xml"><span class="token prolog"><?xml version="1.0" encoding="UTF-8"?></span><br /><span class="token doctype"><span class="token punctuation"><!</span><span class="token doctype-tag">DOCTYPE</span> <span class="token name">jboss-web</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>jboss-web</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>env-entry</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>description</span><span class="token punctuation">></span></span>Private key for signing JWTs<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>description</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>env-entry-name</span><span class="token punctuation">></span></span>jwtPrivateKey<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>env-entry-name</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>env-entry-type</span><span class="token punctuation">></span></span>java.lang.String<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>env-entry-type</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>env-entry-value</span><span class="token punctuation">></span></span><br />-----BEGIN RSA PRIVATE KEY-----<br />gXNTAlZcRubVjEpa8fCgvYMrbqq1CC12j07dDWo76frVXuVNJqJACuZ1eL2JKt12<br />vu7c2an++UooHZDDcRGkbivf4wBycxJmdKpEdHbCWv3cjoVcnmW4g6k4M6xLx6kp<br />KcBbCDiaEalJ2o872GNMXqJYuFw7YmmQaskwqB5a+lpWb08GtpkcLX7G2+s7Js05<br />CngGv4wGHmp9yiO9z5nMIcQXQXtT41Qn6716sUZTJjhu7BgofU3/t01VAWe0ye2s<br />9H0LzUuWNUx1YcKgpi0efGUB/2rmejfFTUr/1pbTTXTqxiuB3Jnt+dhA/XJX11AL<br />o27Ngzto0nkC/2s0csGq7uqJ4Dt2bGBvs1jBnN0SQ9j42ldOVz708CGgbXTJCcaZ<br />-----END RSA PRIVATE KEY-----<br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>env-entry-value</span><span class="token punctuation">></span></span><br /> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>env-entry</span><span class="token punctuation">></span></span><br /><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>jboss-web</span><span class="token punctuation">></span></span></code></pre>
<p>Then use the command line tool to associate the overlay to the deployment, replacing the <code>name</code>, the path to the overlay and the <code>deployments</code>:</p>
<pre class="language-bash"><code class="language-bash">$ <span class="token builtin class-name">cd</span> <span class="token variable">$WILDFLY_HOME</span>/bin<br />$ ./jboss-cli.sh --connect<br />deployment-overlay <span class="token function">add</span> <span class="token punctuation">\</span><br /> --name<span class="token operator">=</span>myOverlay <span class="token punctuation">\</span><br /> --content<span class="token operator">=</span>/WEB-INF/web.xml<span class="token operator">=</span>/path/to/overlay/jboss-web.xml <span class="token punctuation">\</span><br /> --deployments<span class="token operator">=</span>my-war-file.war <span class="token punctuation">\</span><br /> --redeploy-affected</code></pre>
<h2>Resources</h2>
<ul>
<li><a href="https://jwt.io/introduction/">https://jwt.io/introduction/</a></li>
<li><a href="https://github.com/wildfly/quickstart/tree/master/jaxrs-jwt">https://github.com/wildfly/quickstart/tree/master/jaxrs-jwt</a></li>
<li><a href="http://docs.wildfly.org/13/Admin_Guide.html#Deployment_Overlays">http://docs.wildfly.org/13/Admin_Guide.html#Deployment_Overlays</a></li>
<li><a href="https://connect2id.com/products/nimbus-jose-jwt">https://connect2id.com/products/nimbus-jose-jwt</a></li>
</ul>