<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Jeffrey Bostoen</title>
	<atom:link href="https://jeffreybostoen.be/feed/" rel="self" type="application/rss+xml" />
	<link>https://jeffreybostoen.be</link>
	<description>Freelance iTop consultant and developer - IT Consulting - Official iTop Partner</description>
	<lastBuildDate>Fri, 20 Mar 2026 14:46:28 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://jeffreybostoen.be/wp-content/uploads/2023/01/cropped-android-chrome-512x512-1-32x32.png</url>
	<title>Jeffrey Bostoen</title>
	<link>https://jeffreybostoen.be</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>BitWarden 2026 as SSH agent for Visual Studio and Windows Subsystem for Linux (WSL)</title>
		<link>https://jeffreybostoen.be/bitwarden-2026-as-ssh-agent-for-visual-studio/</link>
		
		<dc:creator><![CDATA[Jeffrey Bostoen]]></dc:creator>
		<pubDate>Tue, 17 Mar 2026 20:24:15 +0000</pubDate>
				<category><![CDATA[Security]]></category>
		<category><![CDATA[bitwarden]]></category>
		<category><![CDATA[openssh]]></category>
		<category><![CDATA[security]]></category>
		<category><![CDATA[ssh]]></category>
		<category><![CDATA[visual studio]]></category>
		<guid isPermaLink="false">https://jeffreybostoen.be/?p=1455</guid>

					<description><![CDATA[I&#8217;m writing this in March 2026. The procedure already changed a couple of times because of BitWarden upgrades. Goal My objective is to be able to use the SSH keys stored in BitWarden in Visual Studio, and more in particular in combination with the SSH FS &#8211; Visual Studio Marketplace extension. This SSH FS extension [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>I&#8217;m writing this in March 2026. The procedure already changed a couple of times because of BitWarden upgrades.</p>



<h2 class="wp-block-heading">Goal</h2>



<p>My objective is to be able to use the SSH keys stored in BitWarden in Visual Studio, and more in particular in combination with the <a href="https://marketplace.visualstudio.com/items?itemName=Kelvin.vscode-sshfs">SSH FS &#8211; Visual Studio Marketplace</a> extension.</p>



<p>This SSH FS extension allows users to connect to a remote server using the SSH protocol, and to mount folders and write to remote files.</p>



<h2 class="wp-block-heading">Prerequisite</h2>



<p>Current Microsoft Windows versions come with their own OpenSSH service.</p>



<p>We want to use the standard OpenSSH pipe, and make Bitwarden prompt to authorize the SSH keys stored in BitWarden.</p>



<p>To accomplish this, we must disable the OpenSSH service. Otherwise, BitWarden can not take over.</p>



<pre class="wp-block-code"><code>Stop-Service ssh-agent -Force
Set-Service -Name ssh-agent -StartupType Disabled</code></pre>



<p>Now, confirm that <strong>no</strong> pipes are shown anymore. The following command should not list anything related to openssh-ssh-agent anymore.</p>



<pre class="wp-block-code"><code>&#91;System.IO.Directory]::GetFiles("\\.\pipe\") | Where-Object { $_ -like "*ssh*" }</code></pre>



<h2 class="wp-block-heading">Enabling the BitWarden SSH agent</h2>



<ol class="wp-block-list">
<li>Start BitWarden. If it was already running, restart it. It may be necessary to do this explicitly as an administrator. </li>



<li>Go to Settings &gt; SSH Agent.</li>



<li>Ensure Enable SSH agent is checked.</li>
</ol>



<p>Check if this shows the pipe:</p>



<pre class="wp-block-code"><code>&#91;System.IO.Directory]::GetFiles("\\.\pipe\") | Where-Object { $_ -like "*ssh*" }</code></pre>



<p>The output should be this:</p>



<pre class="wp-block-code"><code>\\.\pipe\openssh-ssh-agent</code></pre>



<p>If you try the following command, it should now list the SSH keys that you have available in BitWarden.</p>



<pre class="wp-block-code"><code>ssh-add -l</code></pre>



<p></p>



<h2 class="wp-block-heading">Configuring SSH FS in VS Code</h2>



<p>(Re)start Visual Studio. When you go to SSH FS and edit a configuration for a remote server, focus on this:</p>



<ul class="wp-block-list">
<li>Don&#8217;t set a private key; don&#8217;t point to a private key file.</li>



<li>Look for the <strong>Agent</strong> setting and make sure it points to<strong> \\.\pipe\openssh-ssh-agent</strong></li>
</ul>



<p>This should be enough.</p>



<p>As soon as you want to connect to this remote server within VS Code, you should see BitWarden requesting your authorization.</p>



<h2 class="wp-block-heading">Using the SSH agent in WSL</h2>



<p>If you install for example an Ubuntu distribution and use bash, you may be inclined to try this simple listing of SSH keys &#8211; and see it fails.</p>



<pre class="wp-block-code"><code>jbostoen@ltJeffrey2024:/mnt/c/Users/jbost$ ssh-add -l
Could not open a connection to your authentication agent.</code></pre>



<p>The secret sauce is that you can actually use <strong>ssh.exe </strong>and <strong>ssh-add.exe</strong> &#8211; Which work!</p>



<pre class="wp-block-code"><code>jbostoen@ltJeffrey2024:/mnt/c/Users/jbost$ ssh-add.exe -l
256 SHA256:xxx somename (ED25519)</code></pre>



<p>If need, you can alias those commands.</p>



<pre class="wp-block-code"><code>nano ~/.bashrc</code></pre>



<p><br>Add this alias at the bottom:</p>



<pre class="wp-block-code"><code>alias ssh='ssh.exe'
alias ssh-add='ssh-add.exe'</code></pre>



<h2 class="wp-block-heading">SSH in Windows</h2>



<p>To faciliate your life, you can create this kind of <strong>config</strong> file ( <strong>%userprofile%\.ssh\config</strong> ):</p>



<pre class="wp-block-code"><code># On Windows, use forward slashes for the identity agent.

Host jump
    HostName xxx.be
	Port 1234
    User root
	IdentityAgent //./pipe/openssh-ssh-agent

Host someremotehost
    HostName 1.2.3.4
    Port 5678
    User jeffrey
    ProxyJump jump
    IdentityAgent //./pipe/openssh-ssh-agent
	</code></pre>



<p>After that, you can shorten your command syntax a lot to:</p>



<pre class="wp-block-code"><code>ssh -J jump someremotehost</code></pre>



<p></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Keycloak: WebAuthn</title>
		<link>https://jeffreybostoen.be/keycloak-webauthn/</link>
		
		<dc:creator><![CDATA[Jeffrey Bostoen]]></dc:creator>
		<pubDate>Mon, 25 Mar 2024 14:28:21 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Keycloak]]></category>
		<category><![CDATA[Single Sign-On]]></category>
		<category><![CDATA[SSO]]></category>
		<guid isPermaLink="false">https://jeffreybostoen.be/?p=1121</guid>

					<description><![CDATA[Personally, I investigated how to set up Keycloak and make it possible to use Windows Hello to authenticate using a passkey. Mind that to simplify things, I just created a realm where only this kind of authentication is supported. Flows Policies Failed to register your Passkey. invalid cert path Solution: In my case, it was [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Personally, I investigated how to set up Keycloak and make it possible to use Windows Hello to authenticate using a passkey.</p>



<p>Mind that to simplify things, I just created a realm where only this kind of authentication is supported.</p>



<p><strong>Flows</strong></p>



<ul class="wp-block-list">
<li>Copy &#8220;browser flow&#8221;
<ul class="wp-block-list">
<li>Remove OTP, password field</li>



<li>Add webauthn passwordless</li>
</ul>
</li>



<li>Copy &#8220;registration flow&#8221;
<ul class="wp-block-list">
<li>Only &#8220;Registration user Profile Creation&#8221; is needed.</li>
</ul>
</li>
</ul>



<p></p>



<p><strong>Policies</strong></p>



<ul class="wp-block-list">
<li>Webauthn passwordless</li>
</ul>



<p></p>



<p></p>



<p>Failed to register your Passkey. invalid cert path</p>



<p>Solution: In my case, it was because I had set <strong>Attestation conveyance preference</strong> to Direct. Leaving it to <strong>Not specified</strong> made everything work like a charm.</p>



<p>Another suggestion on the Internet is to try out enabling different signature algorithms, such as RS256 for Yubikey.</p>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="619" height="407" src="https://jeffreybostoen.be/wp-content/uploads/2024/03/image.png" alt="" class="wp-image-1122" srcset="https://jeffreybostoen.be/wp-content/uploads/2024/03/image.png 619w, https://jeffreybostoen.be/wp-content/uploads/2024/03/image-300x197.png 300w" sizes="(max-width: 619px) 100vw, 619px" /></figure>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Self accounting: Accountable vs. Dexxter vs. CoManage.</title>
		<link>https://jeffreybostoen.be/accountancy-accountable-vs-dexxter-vs-comanage/</link>
		
		<dc:creator><![CDATA[Jeffrey Bostoen]]></dc:creator>
		<pubDate>Sat, 16 Mar 2024 13:10:20 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[YO25]]></category>
		<guid isPermaLink="false">https://jeffreybostoen.be/?p=1111</guid>

					<description><![CDATA[Note: Originally written on the 13th of March, 2024. It has been updated to reflect some changes.Consider that features may have changed in the meantime.For instance, Dexxter now does have OCR, supports PEPPOL, and can read your bank account. Dexxter has been acquired by a bigger company, giving them more resources. Since this article was [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="has-vivid-green-cyan-color has-text-color has-link-color wp-elements-08955587016544d4a6839a78f2b95310"><strong>Note: Originally written on the 13th of March, 2024. It has been updated to reflect some changes.</strong><br><strong>Consider that features may have changed in the meantime.</strong><br><strong>For instance, Dexxter now does have OCR, supports PEPPOL, and can read your bank account.</strong> <br><strong>Dexxter has been acquired by a bigger company, giving them more resources.</strong></p>



<p><em>Since this article was written, Dexxter actually did implement OCR, for instance.</em></p>



<p>As a one man business, a side business actually, I had difficulties managing the complexity of an accountant in Belgium.</p>



<p>While originally my profits were small and I was looking to cut down on costs, I realized I did need assistance with over-complicated finance stuff. Doing international business, both in Europe and other regions, makes it more complicated too. And I learned there are for example different ways to write things off (1st of January vs. actual purchase date) in my case; and even the online tools I evaluated do it (by default) opposite from each other.</p>



<p>But even finding an accountant who was willing to take me on as a customer, proved to be a challenge. Out of all the ones I contacted, I got to visit one &#8211; as a courtesy since I went to school with the guy.</p>



<p>I initially meant to investigate the two main (promoted) ones: <strong>Accountable </strong>and <strong><a href="https://dexxter.cello.so/V0r2cNLMrcE">Dexxter</a></strong>. While doing a typical web search, I was presented with a comparison matrix by a third player, CoManage. It wasn&#8217;t entirely accurate though; it listed API as a feature for Accountable.</p>



<p>I spend around an equal time playing around with Accountable and Dexxter. I may have missed some features.</p>



<p>There are actually two things which I had in mind as a &#8220;MUST&#8221;.</p>



<ul class="wp-block-list">
<li><strong>API</strong>. I want to be able to integrate my existing applications (which include contact data) and just synchronize this info with the accountancy tool. Spoiler alert: both solutions failed.</li>



<li><strong>Export of all data.</strong> I want to be able to make an offline backup of my data at any given time. Too many applications have changed over time, or have been discontinued, or prices went way up. I want to have the freedom to leave, with all my data, at any given point.</li>
</ul>



<p></p>



<h2 class="wp-block-heading">Features</h2>



<h3 class="wp-block-heading">Accountable</h3>



<p>Pro:</p>



<ul class="wp-block-list">
<li>When importing invoices (from third parties), there is an OCR feature which tries to prefill as much as possible. Seems to be quite capable, real time-saver!</li>



<li>A great feature I didn&#8217;t consider: bank integration. They can show an overview of the transactions, and they can then be linked to invoices.</li>



<li>Very good wizards and pro-active suggestions.</li>



<li>Integration with Mollie.</li>



<li>Nice timeline and reports.</li>



<li>Large knowledge base.</li>
</ul>



<p>Cons:</p>



<ul class="wp-block-list">
<li>No API (  Note: in the comparison matrix by CoManage, it seems it was incorrectly stated there was one ).</li>



<li>Investments: linear write off by default (so set on 1st of January, which is allowed for my VAT status, and may be beneficial at some point but it&#8217;s not what I&#8217;m looking to do).</li>
</ul>



<p>Unknown:</p>



<ul class="wp-block-list">
<li>They&#8217;re starting to experiment with AI. Seems to be a chatbot based on their KBs.</li>



<li>Not enough time to play around with PEPPOL, seemed to be supported.</li>
</ul>



<p>User base: Unknown. On Google, they have a 4.7 score based on 34 reviews.</p>



<h3 class="wp-block-heading">Dexxter</h3>



<p>Sign up <a href="https://dexxter.cello.so/V0r2cNLMrcE">here</a> now! No additional cost for you; but &#8211; full disclosure &#8211; I get a small cut .</p>



<p><strong>Pro:</strong></p>



<ul class="wp-block-list">
<li>Some <strong>limited</strong> multi-lingual support to create quotes and invoices. Templates are a bit limited though.</li>



<li>You can maintain a list of your own products.</li>



<li>Integration with Mollie.</li>



<li>Link your bank account.</li>



<li>PEPPOL support.</li>



<li>Feels very snappy.</li>



<li>Very good wizards and pro-active suggestions.</li>



<li>Investments: pro rata write off by default (so from purchase date onwards).</li>



<li>Nice timeline and reports.</li>



<li>Quite a few exports; but the main one to export all your input is missing documents you upload (e.g. PDF invoices) and only contained CSV files &#8230;</li>



<li>Small community.</li>



<li>Large knowledge base. Also, the KB is nicely integrated and seems to suggest actually relevant FAQs on each section.</li>



<li>Ability to enter the expected frequency of certain costs (+ alerts about invoices which may be missing)</li>



<li>Only lets you go back 1 year since the time of creating your profile. So, if you&#8217;re in 2024, you can&#8217;t add invoices to year 2022.</li>
</ul>



<p><strong>Cons:</strong></p>



<ul class="wp-block-list">
<li>There was no easy way to retrieve any uploaded documents.</li>



<li>API has been requested at least since 2021, but is not available.</li>
</ul>



<p>User base: Their website currently states <strong>10,000+</strong> (which is a growing number the last couple of years). On Google, it scores 4.9 out of 210 reviews. Surprisingly enough, the lowest rating is 4/5.</p>



<h3 class="wp-block-heading">CoManage</h3>



<p>I honestly gave up shortly after signing in. There was a very short welcome video. I played around with the layout, and it was way too busy for me. This did not give me a feel of simplicity &#8211; the key thing I&#8217;m looking for.</p>



<p></p>



<h2 class="wp-block-heading">Pricing</h2>



<p><strong>Accountable </strong>comes in 3 flavors: free (very limited), small and pro. I check nearly all of the boxes of &#8220;small&#8221;, but I need an IC-listing (with my Europese customers). That&#8217;s a &#8220;pro&#8221; feature. Pay per month, or per year (cheaper).</p>



<p><strong>Dexxter</strong> also seems to have different packages (VAT status), but the price seems to be the same for all. Hint: On their own website, they host pages with some partner info. For many of those partners, promo codes are available. Surprisingly enough, they&#8217;re different. When writing this article (April 2024), 25% off was the best one I found: <a href="https://dexxter.be/youngones/">Dexxter via YoungOnes &#8211; Dexxter</a> </p>



<p>Dexxter is slightly cheaper than the &#8220;small&#8221; package from Accountable.</p>



<h2 class="wp-block-heading">Conclusion</h2>



<p class="has-vivid-green-cyan-color has-text-color has-link-color wp-elements-4ba90e874ed744b02aa230b680cd5a5c"><strong>TLDR: Choose Dexxter!</strong></p>



<p>In both cases, it&#8217;s a shame they do not have an API at this point. This is a huge disappointment. If either one of them would have had this feature, it would most definitely have made the difference.</p>



<p>Both Accountable and Dexxter look very <strong>modern</strong>. Accountable has a cleaner lay-out (which I prefer), but feels slightly less snappy &#8211; however, it doesn&#8217;t feel sluggish at all.</p>



<p>With Accountable, I couldn&#8217;t choose the start date of my investment; despite a KB article mentioning it could be changed after contacting support (who put me on the wrong track first).</p>



<p></p>



<p><strong>What makes Accountable the better choice?</strong></p>



<ul class="wp-block-list">
<li>Accountable somehow managed to alert me better about my requirement to submit an IC listing of my European customers.</li>



<li>They also allow you to export all your data.</li>



<li>Their suggestions to categorize costs, are more intelligent-guess based in combination with wizards.</li>
</ul>



<p><strong>What makes Dexxter the better choice?</strong></p>



<ul class="wp-block-list">
<li>I really miss product management in Accountable. It&#8217;s easy to re-select items when creating invoices.</li>



<li>The Mollie integration is something I keep in mind for the future. As I have many international customers, Mollie would allow them to pay through a vast majority of services (of course, Mollie is an extra cost). It&#8217;s also a payment solution I have considered in the past for a webshop.</li>



<li>Just a personal preference: pro rata write off (investment).</li>



<li>Pricing = best value!</li>



<li>Support was faster, and more personal.</li>
</ul>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>openssl: self-signed certificate creation</title>
		<link>https://jeffreybostoen.be/openssl-self-signed-certificate-creation/</link>
		
		<dc:creator><![CDATA[Jeffrey Bostoen]]></dc:creator>
		<pubDate>Thu, 14 Mar 2024 15:09:04 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[certificate]]></category>
		<category><![CDATA[openssl]]></category>
		<category><![CDATA[SSL]]></category>
		<guid isPermaLink="false">https://jeffreybostoen.be/?p=1118</guid>

					<description><![CDATA[]]></description>
										<content:encoded><![CDATA[
<pre class="wp-block-code"><code>root@jb-ubuntu-01:/etc/keycloakcerts# vim ext.conf 

&#91;req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no

&#91;req_distinguished_name]
C = BE
ST = xxx
L = 
O = My Company
OU = My Department
CN = keycloak

&#91;v3_req]
subjectAltName = @alt_names
keyUsage = keyUsage = digitalSignature, keyCertSign, cRLSign


&#91;alt_names]
DNS.1 = localhost
DNS.2 = keycloak.mydomain.com
IP.1 = 10.1.10.41


root@jb-ubuntu-01:/etc/keycloakcerts# openssl req -x509 -nodes -days 360 -newkey rsa:4096 -keyout mycert.key -out mycert.pem -config ext.conf
</code></pre>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Kerberos authentication against Apache2</title>
		<link>https://jeffreybostoen.be/kerberos-authentication-against-apache2/</link>
		
		<dc:creator><![CDATA[Jeffrey Bostoen]]></dc:creator>
		<pubDate>Sun, 11 Feb 2024 20:47:41 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[GSSAPI]]></category>
		<category><![CDATA[itop]]></category>
		<category><![CDATA[Kerberos]]></category>
		<category><![CDATA[keytab]]></category>
		<category><![CDATA[kinit]]></category>
		<category><![CDATA[krb5]]></category>
		<category><![CDATA[ktpass]]></category>
		<category><![CDATA[Single Sign-On]]></category>
		<category><![CDATA[SSO]]></category>
		<guid isPermaLink="false">https://jeffreybostoen.be/?p=1084</guid>

					<description><![CDATA[It&#8217;s possible to require Kerberos authentication before allowing access to a web application, let&#8217;s say served by an Apache2 web server. In practice, for one of my favorite web applications (iTop); it means internal domain users could be automatically authenticated. They would not need to provide their credentials and there&#8217;s no need to configure an [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>It&#8217;s possible to require <strong>Kerberos </strong>authentication before allowing access to a web application, let&#8217;s say served by an <strong>Apache2 </strong>web server.</p>



<p>In practice, for one of my favorite web applications (iTop); it means <strong>internal domain users</strong> could be <strong>automatically authenticated</strong>. They would not need to provide their credentials and there&#8217;s no need to configure an external Identity Provider using SAML or OpenID.</p>



<p>I&#8217;ve initially first set it up for a WordPress plugin called &#8220;Next Active Directory integration&#8221;, to allow single-sign on to the WordPress. Later on, I also configured iTop to work this way.</p>



<h2 class="wp-block-heading">Prerequisites</h2>



<p>For this post, we make the following assumptions about the infrastructure:</p>



<ul class="wp-block-list">
<li>There is an <strong>Active Directory</strong> in place in a typical <strong>Microsoft Windows</strong> enterprise environment.</li>



<li>There is an <strong>Apache2</strong> web server &#8211; using a <strong>Debian-based</strong> operating system (I&#8217;m personally using <strong>Ubuntu</strong>).<br>This webserver can connect to the primary domain controller of the Active Directory environment. <br>This domain controller should allow <strong>inbound</strong> connections coming from the webserver on both <strong>UDP and TCP port 88.</strong></li>
</ul>



<p>Note that I try to follow best-practices and security standards which are in place when writing this post, in February 2024. If you come here after several years, the instructions or recommendations may be slightly different.</p>



<h2 class="wp-block-heading">Reference</h2>



<p>In this post, we&#8217;ll be using:</p>



<figure class="wp-block-table"><table><tbody><tr><td>YOURDOMAIN.ORG</td><td>This is the Fully Qualified Domain Name (FQDN) of your Windows domain.</td></tr><tr><td>webserver.yourdomain.org</td><td>This is the name of your webserver. Note: We did not domain-join this webserver in any specific way.</td></tr><tr><td>krb5.webserver01</td><td>The user name of the Active Directory user account that will be used for the service.</td></tr><tr><td>DontJustCopyThis*1234</td><td>The password of the Active Directory user account that will be used for the service.</td></tr></tbody></table></figure>



<h2 class="wp-block-heading">Active Directory configuration</h2>



<p>Let&#8217;s prepare the Active Directory side. What we need here, is a <strong>regular</strong> user account. This user does not need to belong to any privileged groups. In fact, I recommend against it. Ideally, this should be a <strong>dedicated</strong> account.</p>



<ol class="wp-block-list">
<li>Open Active Directory Users and Computers.</li>



<li>Create a new user. Give it a decent name ( e.g. krb5.webserver01 ) and secure password.<br>In the rest of this post, we&#8217;ll be using <strong>krb5.webserver01</strong> and <strong>DontJustCopyThis*1234 </strong>as password.</li>



<li>Set the password to never expire.</li>



<li>In the properties, under &#8220;Account&#8221;, check these boxes:
<ul class="wp-block-list">
<li>User cannot change password.</li>



<li>Password never expires.</li>



<li>This account supports Kerberos AES 256 bit encryption. (Scroll down a bit, it&#8217;s also in the list of Account options).</li>
</ul>
</li>
</ol>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="616" height="835" src="https://jeffreybostoen.be/wp-content/uploads/2024/02/image.png" alt="" class="wp-image-1089" style="width:375px;height:auto" srcset="https://jeffreybostoen.be/wp-content/uploads/2024/02/image.png 616w, https://jeffreybostoen.be/wp-content/uploads/2024/02/image-221x300.png 221w" sizes="(max-width: 616px) 100vw, 616px" /></figure>



<p>We&#8217;ll also need to generate a so-called <strong>keytab</strong> using the <strong>ktpass</strong> command. There are some variants of this instruction on the Internet, with some for example allowing all cryptography algorithms. I want to keep it as secure as possible, and explicitly want to restrict it to only support AES-256. This may lead to an additional challenge which gets covered below.</p>



<p>I suggest executing this in an <strong>elevated (Run as administrator) command prompt</strong> on a domain controller:</p>



<pre class="wp-block-code"><code>ktpass -princ HTTP/webserver.yourdomain.org@YOURDOMAIN.ORG -mapuser krb5.webserver01@YOURDOMAIN.ORG -pass DontJustCopyThis*1234 -ptype KRB5_NT_PRINCIPAL -kvno 0 -crypto AES256-SHA1 -out krb5.webserver01.keytab</code></pre>



<p><strong>Q: What is a keytab file?</strong><br>A: A keytab is a file containing pairs of Kerberos principals and encrypted keys that are derived from the Kerberos password. You can use this file to log on to Kerberos without being prompted for a password.</p>



<p><strong>Q: Can I use a &#8220;service account&#8221; for this?</strong><br>A:  I looked into <strong>service accounts</strong> (introduced in Microsoft Windows Server 2016),<strong> </strong> but that was a dead end for me.</p>



<p>You can verify if the proper SPN (Service Principal Name) is set. In Active Directory Users and Computers, under View, make sure Advanced features is checked.</p>



<p>Then, when viewing the properties of the user account, you should see the HTTP/webserver.yourdomain.org@YOURDOMAIN.ORG value in the <strong>Attribute Editor</strong> tab for <strong>servicePrincipalName</strong>.</p>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="616" height="833" src="https://jeffreybostoen.be/wp-content/uploads/2024/02/image-1.png" alt="" class="wp-image-1090" style="width:369px;height:auto" srcset="https://jeffreybostoen.be/wp-content/uploads/2024/02/image-1.png 616w, https://jeffreybostoen.be/wp-content/uploads/2024/02/image-1-222x300.png 222w" sizes="(max-width: 616px) 100vw, 616px" /></figure>



<h2 class="wp-block-heading">Kerberos configuration (webserver)</h2>



<p>Time to configure the server.</p>



<pre class="wp-block-code"><code>apt install krb5-user</code></pre>



<p>Now, take your favorite text editor to change the configuration file <strong>/etc/krb5.conf </strong>.<br>Under the <strong>[realms]</strong> section, you&#8217;ll want to add your own Active Directory domain in uppercase.</p>



<pre class="wp-block-code"><code>&#91;realms]
YOURDOMAIN.ORG = {

	# Specify at least one Kerberos Domain Controller (KDC).
	# Usually, your primary domain controller acts as a Kerberos Domain Controller.
	# If you want to specify additional KDCs, just enter one line like this for each KDC:
	kdc = 10.1.10.1
	
	# Admin server. In most setups, just point to the primary Domain Controller.
	admin_server = 10.1.10.1

	# AES256 made things a bit more tricky. To avoid trying deprecated cryptography such as RC4-HMAC and only use AES256:
	default_tkt_enctypes = aes256-cts-hmac-sha1-96 rc4-hmac
	default_tgs_enctypes = aes256-cts-hmac-sha1-96  rc4-hmac
	permitted_enctypes = aes256-cts-hmac-sha1-96 rc4-hmac
}
</code></pre>



<p>Let&#8217;s put this configuration to the test. On the Linux webserver, you can now try the command below. Note that this is <strong>NOT</strong> our krb5.webserver01 user; but just another regular user account!</p>



<pre class="wp-block-code"><code>kinit your-user@YOURDOMAIN.ORG</code></pre>



<p>You won&#8217;t see much if everything goes right. If something goes wrong, you&#8217;d see an error message.<br>For example:</p>



<ul class="wp-block-list">
<li><strong>kinit: Client &#8216;nonexistinguser@YOURDOMAIN.ORG&#8217; not found in Kerberos database while getting initial credentials</strong>. &#8211; After entering an incorrect username.</li>



<li><strong>kinit: Password incorrect while getting initial credentials.</strong> &#8211; After entering an incorrect password.</li>
</ul>



<p>To truly validate, run:</p>



<pre class="wp-block-code"><code>klist</code></pre>



<p>This should output info about the ticket cache such as the default principal, two timestamps to indicate during which period the ticket will be valid, and a service principal such as <strong>krbtgt/YOURDOMAIN.ORG@YOURDOMAIN.ORG</strong> .</p>



<p><strong>Q: What is this krbtgt string?</strong><br>A: KRB = Kerberos, TGT = Ticket Granting Ticket. KRBTGT is a default account that exists in all Windows domains. It is meant to act as a service account (specifically for the KDC = Key Distribution Center) for domain controllers.</p>



<p>To clear the credentials, run:</p>



<pre class="wp-block-code"><code>kdestroy</code></pre>



<h2 class="wp-block-heading">Apache2 configuration (webserver)</h2>



<p>We&#8217;ll assume you already have your Apache2 up and running; with SSL enabled.</p>



<p>We&#8217;ll be using this module: <a href="https://github.com/gssapi/mod_auth_gssapi">GitHub &#8211; gssapi/mod_auth_gssapi: GSSAPI Negotiate module for Apache</a> . <br>Let&#8217;s first get this concept working. I do recommend reading some of the documentation after we manage to pull this off, as it also contains some other options (I recommend reading up on GssapiUseSessions ).</p>



<p>Anyhow, we&#8217;ll need to install this module:</p>



<pre class="wp-block-code"><code>apt install libapache2-mod-auth-gssapi</code></pre>



<p>Now, for the sake of my implementation, I personally had a virtual host configured on port 443. The server name (host header) was webserver.yourdomain.org .</p>



<p>I put these settings in my location block (I only had one):</p>



<pre class="wp-block-code"><code>&lt;Location/>

	AuthType GSSAPI
	AuthName "Kerberos Authentication"

	# The keytab file that was generated before.
	GssapiCredStore keytab:/privkeytab/krb5.webserver01.keytab
	GssapiAllowedMech krb5
	GssapiBasicAuth Off
	
	# Play around with this setting as needed.
	# "On" means the remote user would be identified as "someuser", with "Off" it would be "someuser@YOURDOMAIN.ORG".
	GssapiLocalName Off

	# Users should not be able to transmit sensitive data over non-SSL/TLS connections.
	GssapiSSLonly On

	# Only accept users who are able to authenticate.
	require valid-user

&lt;/Location></code></pre>



<p>As you see, we need to transfer the krb5.webserver01.keytab file that we generated on the Windows domain controller to our Linux webserver.</p>



<p>In this example, I&#8217;ve transferred it to the <strong>/privkeytab</strong> folder. Now, make sure the user account linked to Apache 2.4 (often <strong>www-data</strong>) has permissions to use this:</p>



<pre class="wp-block-code"><code>chown -R www-data:www-data /privkeytab</code></pre>



<p>Additionally, don&#8217;t forget to at least reload the Apache configuration. Although, if possible, I always perform a restart instead for this.</p>



<pre class="wp-block-code"><code>service apache2 restart &amp;&amp; service apache2 status</code></pre>



<p>Hint: If you have PHP installed, just make a simple PHP file which can echo our &#8220;remote user&#8221; variable later on. <strong>$_SERVER[&#8216;REMOTE_USER&#8217;]</strong> is the variable where the web application will be able to grab the username from.</p>



<pre class="wp-block-code"><code>&lt;?php
	echo 'Identified remote user: "'.$_SERVER&#91;'REMOTE_USER'].'"';</code></pre>



<h2 class="wp-block-heading">Browser configuration</h2>



<p>Finally, the browser configuration. It&#8217;s commonly referred to as <strong>SPNEGO</strong> (Simple and Protected GSSAPI Negotiation Mechanism).</p>



<p>Below I&#8217;ll describe the steps to enable it on an individual machines. If you&#8217;re a system administrator; consider using <strong>group policies</strong> to deploy these settings to all your domain users and machines.</p>



<p>If you want to perform a quick test, navigate to the PHP file you created earlier. You should <strong>NOT</strong> be prompted for authentication. You should be automatically signed in. Your username should automatically be shown.</p>



<h3 class="wp-block-heading">Microsoft Edge and Google Chrome</h3>



<p>Open good old <strong>iexplore.exe </strong>. </p>



<ol class="wp-block-list">
<li>Go to Tools > Internet Options.</li>



<li>In the <strong>Security</strong> tab, select <strong>Local Intranet</strong>.
<ol class="wp-block-list">
<li>Click <strong>[Sites].</strong> </li>



<li>Add: <strong>https://webserver01.yourdomain.org</strong> (or https://*.yourdomain.org if you want to be more lenient ).</li>



<li>Close the window.</li>



<li>Click <strong>[Custom level&#8230;]</strong>.</li>



<li>Scroll to the bottom, find <strong>User Authentication > Logon</strong>.</li>



<li>Select <strong>Automatic logon with current user name and password.</strong></li>
</ol>
</li>



<li>Save and close all.<strong> </strong></li>
</ol>



<h3 class="wp-block-heading">Mozilla Firefox</h3>



<ol class="wp-block-list">
<li>In the address bar, type <strong>about:config</strong> and press [Enter].</li>



<li>In the filter/search field, enter <strong>negotiate.</strong></li>



<li>Find the setting named <strong>network.negotiate-auth.trusted-uris</strong>.</li>



<li>Make sure it includes https://webserver01.yourdomain.org and save.</li>



<li>Restart Mozilla Firefox.</li>
</ol>



<p></p>



<h2 class="wp-block-heading">iTop configuration</h2>



<p>As a bonus, I&#8217;ll show you how to configure iTop to accept these kind of logons. If you want additional security, you can still check my &#8220;pro&#8221; extensions and use the Multi-Factor Authentication.</p>



<p>We&#8217;ll be configuring &#8220;<strong>external</strong>&#8221; authentication.</p>



<p>Open the iTop configuration. We&#8217;ll need to update two settings.</p>



<pre class="wp-block-code"><code>'allowed_login_types' =&gt; '...',</code></pre>



<p>This setting lists the allowed login modes, seperated by the pipe ( | ) character. &#8220;External&#8221; needs to be present here. By default, it&#8217;s added at the end, according to the &#8220;Configuration parameters&#8221; documentation. However, another page suggests to put it <strong>first</strong>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>In order to ensure that the external authentication is used first (preventing iTop from prompting the already authenticated user a second time), make sure that in the iTop configuration file, the order for allowed_login_types specifies “external” as the first login mode</p>
<cite><a href="https://www.itophub.io/wiki/page?id=latest:admin:user_authentication_options#logon_types">User Authentication Options [iTop Documentation] (itophub.io)</a></cite></blockquote>



<p>Do put it first!</p>



<p>The second setting is this one; which you should set to match the $_SERVER[&#8216;REMOTE_USER&#8217;] variable:</p>



<pre class="wp-block-code"><code>'ext_auth_variable' =&gt; '$_SERVER&#91;\'REMOTE_USER\']',</code></pre>



<p>Now save.</p>



<p>Make sure in iTop you do have a <strong>User account</strong> created.</p>



<p>If you&#8217;re going to use short login names (&#8220;someuser&#8221;), set GssapiLocalName in the Apache configuration to &#8220;On&#8221;.<br>Otherwise, with GssapiLocalName set to &#8220;Off&#8221;, your user accounts would be named &#8220;someuser@YOURDOMAIN.ORG&#8221; instead.</p>



<h2 class="wp-block-heading">Some common issues</h2>



<p>Often, posts don&#8217;t include how to troubleshoot. I&#8217;ll share some issues I&#8217;ve ran into myself, and some solutions. You may want to consider setting a specific <strong>error_log</strong> directive in the Apache configuration; so you can easily see the Kerberos-related issues</p>



<p><strong>Incorrect service principal name</strong></p>



<pre class="wp-block-code"><code>&#91;Sun Feb 11 13:29:16.940771 2024] &#91;auth_gssapi:error] &#91;pid 5428:tid 139654099572288] &#91;client 192.168.0.210:64532] GSS ERROR In Negotiate Auth: gss_accept_sec_context() failed: &#91;Unspecified GSS failure.  Minor code may provide more information (Request ticket server HTTP/webserver01.yourdomain.org@YOURDOMAIN.ORG not found in keytab (ticket kvno 1))]
</code></pre>



<p>Solution: Double-check whether the keytab actually does contain this entry.</p>



<p><strong>Enctype issues</strong></p>



<pre class="wp-block-code"><code>&#91;Sun Feb 11 13:42:42.501763 2024] &#91;auth_gssapi:error] &#91;pid 5852:tid 139767178040896] &#91;client 192.168.0.210:63466] GSS ERROR In Negotiate Auth: gss_accept_sec_context() failed: &#91;Unspecified GSS failure.  Minor code may provide more information (Request ticket server HTTP/webserver01.yourdomain.org@YOURDOMAIN.ORG kvno 4 found in keytab but not with enctype rc4-hmac)]</code></pre>



<p>Solution: In krb5.conf, make sure to specify the encryption types. In the keytab, only AES256 was included; so <strong>/etc/krb5.conf</strong> had to be adjusted. Otherwise, it still tried to authenticate with the insecure RC4-HMAC.</p>



<p>Afterward, I also learned that I might have seen even more info when creating a trace file I could check after running this command:</p>



<pre class="wp-block-code"><code>KRB5_TRACE=/tmp/krb5_trace.log kinit -Vkt /privkeytab/krb5.webserver01.keytab HTTP/webserver01.yourdomain.org@YOURDOMAIN.ORG</code></pre>



<p>Then, not only check the output of the above command, but also of the trace log <strong>/tmp/krb5_trace.log </strong>.</p>



<p><strong>Ticket out of date</strong></p>



<pre class="wp-block-code"><code>&#91;Sun Feb 11 13:57:52.894981 2024] &#91;auth_gssapi:error] &#91;pid 6349:tid 140421321233984] &#91;client 192.168.0.210:63692] GSS ERROR In Negotiate Auth: gss_accept_sec_context() failed: &#91;Unspecified GSS failure.  Minor code may provide more information (Request ticket server HTTP/webserver01.yourdomain.org@YOURDOMAIN.ORG kvno 4 not found in keytab; ticket is likely out of date)]</code></pre>



<p>To be honest, this one was only resolved after I rebooted the machine I used to navigate to the web application. I think I just should have purged the cache there. Running <strong>klist purge</strong> on this Windows machine may have spared me from a reboot.</p>



<p>When running <strong>klist</strong> on Microsoft Windows, you&#8217;ll also get an overview of the cached tickets. You&#8217;ll see the same <strong>krbtgt/YOURDOMAIN.ORG@YOURDOMAIN.ORG</strong> ; and also a ticket where the server is listed as <strong>HTTP/webserver01.yourdomain.org@YOURDOMAIN.ORG</strong> . It also includes some extra info such as the validity period and session key type (encryption). It also lists the KDC (Kerberos Domain Controller) that was used.</p>



<p><strong>SPNEGO cannot find mechanisms to negotiate</strong></p>



<pre class="wp-block-code"><code>Sun Feb 11 14:41:02.838029 2024] &#91;auth_gssapi:error] &#91;pid 14397] &#91;client 192.168.0.210:61579] GSS ERROR In Negotiate Auth: gss_accept_sec_context() failed: &#91;Unspecified GSS failure.  Minor code may provide more information ( SPNEGO cannot find mechanisms to negotiate)]
</code></pre>



<p>It means the SPNEGO authentication fails.</p>



<p>In my case, I was just messing around with settings in the Apache configuration that led to the error above.</p>



<p><strong>No credentials were supplied, or the credentials were unavailable or inaccessible (Unknown error)</strong></p>



<pre class="wp-block-code"><code>&#91;Sun Feb 11 20:40:55.691852 2024] &#91;auth_gssapi:error] &#91;pid 4492] &#91;client 192.168.0.210:57991] GSS ERROR In Negotiate Auth: gss_accept_sec_context() failed: &#91;No credentials were supplied, or the credentials were unavailable or inaccessible (Unknown error)]</code></pre>



<p>The above error occurred if I used Google Chrome to navigate to the web application, without it being in the list of local Intranet websites. It then shows a prompt instead to enter credentials. Trying do dismiss it and not entering credentials, leads to the above. However, when manually entering correct credentials; it won&#8217;t display this.</p>



<h2 class="wp-block-heading">Background info</h2>



<h3 class="wp-block-heading">Useful links</h3>



<ul class="wp-block-list">
<li><a href="https://en.wikipedia.org/wiki/Generic_Security_Services_Application_Program_Interface">Generic Security Services Application Program Interface &#8211; Wikipedia</a> &#8211; Info about GSSAPI.</li>



<li><a href="https://web.mit.edu/kerberos/krb5-devel/doc/basic/keytab_def.html">keytab — MIT Kerberos Documentation</a> &#8211; Info about keytab.</li>



<li><a href="https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html">krb5.conf — MIT Kerberos Documentation</a> &#8211; Info about krb5.conf and its parameters.</li>



<li><a href="https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-service-accounts">Service Accounts | Microsoft Learn</a> &#8211; Info about service accounts in the Active Direcotry.</li>



<li><a href="https://forum.kaspersky.com/topic/kwts-kerberos-ldap-sso-proxy-authentication-problems-kaspersky-web-traffic-security-37974/">KWTS Kerberos LDAP, SSO, Proxy authentication problems [Kaspersky Web Traffic Security] &#8211; Advice and solutions for Kaspersky Security for Internet Gateway &#8211; Kaspersky Support Forum</a> &#8211; Some additional help in troubleshooting.</li>
</ul>



<h3 class="wp-block-heading">Used versions</h3>



<p>While writing this tutorial, my environment looked like this:</p>



<ul class="wp-block-list">
<li>Microsoft Windows Server 2022.</li>



<li>Ubuntu 22.04 LTS</li>



<li>krb5-user : 1.19.2</li>



<li>Apache 2.4: 2.4.52</li>



<li>libapache2-mod-auth-gssapi: 1.6.3</li>
</ul>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Duo Security API &#8211; How to test</title>
		<link>https://jeffreybostoen.be/duo-security-api-how-to-test/</link>
		
		<dc:creator><![CDATA[Jeffrey Bostoen]]></dc:creator>
		<pubDate>Sat, 03 Feb 2024 10:10:16 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Cisco]]></category>
		<category><![CDATA[Duo]]></category>
		<category><![CDATA[Duo Security]]></category>
		<guid isPermaLink="false">https://jeffreybostoen.be/?p=1078</guid>

					<description><![CDATA[In February 2024, I had to troubleshoot an issue with a Python-based Duo implementation. There were two things I wanted to check manually, using cURL. Based on some googling, I came up with the following snippet to check if authenticating to the API worked. These resources were very helpful: Eventually, it worked and provided me [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>In February 2024, I had to troubleshoot an issue with a Python-based Duo implementation.</p>



<p>There were two things I wanted to check manually, using <strong>cURL</strong>.</p>



<p>Based on some googling, I came up with the following snippet to <strong>check if authenticating to the API worked</strong>.</p>



<p>These resources were very helpful:</p>



<ul class="wp-block-list">
<li><a href="https://duo.com/docs/authapi">Duo Auth API | Duo Security</a></li>



<li><a href="https://help.duo.com/s/article/1338?language=en_US">What are Duo&#8217;s API responses and error messages?</a> &#8211; When you need to start troubleshooting the authentication.</li>
</ul>



<pre class="wp-block-code"><code>

# Replace this with the variables found in the Duo Admin portal.
# These are generic settings.
integrationKey="xxx"
secretKey="xxx"
apiHostname="xxx.duosecurity.com"

# Endpoint specific.
method="GET"
apiPath="/auth/v2/check"
params="" # Just left this in here, in case parameters do need to be passed.

# Each request needs to be signed.
# On the web, there was a script which used \n for newlines.
# For some reason, it didn't work for me until I actually changed the template to what you see below.
# The template is used to generate a signature, and consists of five things:
# timestamp, method, API hostname, API path and parameters (if there are none, there must be a blank line.)
requestTemplate="$timestamp
$method
$apiHostname
$apiPath
$params"

timestamp=$(date -R)

signature=$(echo -n "$requestTemplate" | openssl sha1 -hmac "$integrationKey" | cut -d" " -f 2)
authHeader=$(echo -n "$integrationKey:$signature" | base64 -w0)

# Some HTTP headers must be set.
curl -s -H "Date: $timestamp" -H "Content-Type: application/x-www-form-urlencoded" -H "Authorization: Basic $authHeader" https://$apiHostname$apiPath</code></pre>



<p>Eventually, it worked and provided me with the output I needed.</p>



<p>Now, to know whether a user is authorized to log in, and (if so) returns the user&#8217;s available authentication factors, I wanted to check the <strong>/preauth</strong> endpoint. The script had to be adjusted a little bit; mainly in the cURL line. We specifically add the method in there (<strong>POST</strong>); and of course some data needed to be posted (<strong>params</strong>).</p>



<p> I struggled a while before realizing in this case I should <strong>NOT</strong> set the <strong>Content-Type: application/x-www-form-urlencoded</strong> header.</p>



<pre class="wp-block-code"><code>

# Replace this with the variables found in the Duo Admin portal.
# These are generic settings.
integrationKey="xxx"
secretKey="xxx"
apiHostname="xxx.duosecurity.com"

# Endpoint specific.
method="GET"
apiPath="/auth/v2/preauth"
params="username=xxx" # Alternative for this endpoint: user_id=xxx where xxx is a Duo user ID.

# Each request needs to be signed.
# On the web, there was a script which used \n for newlines.
# For some reason, it didn't work for me until I actually changed the template to what you see below.
# The template is used to generate a signature, and consists of five things:
# timestamp, method, API hostname, API path and parameters (if there are none, there must be a blank line.)
requestTemplate="$timestamp
$method
$apiHostname
$apiPath
$params"

timestamp=$(date -R)

signature=$(echo -n "$requestTemplate" | openssl sha1 -hmac "$integrationKey" | cut -d" " -f 2)
authHeader=$(echo -n "$integrationKey:$signature" | base64 -w0)

# Some HTTP headers must be set.
curl -s -d "$params" -H "Date: $timestamp" -H "Authorization: Basic $authHeader" -X "$method" https://$apiHostname$apiPath</code></pre>



<p>Since I only wanted to check those two things, I didn&#8217;t go through the effort of creating a function for this instead.</p>



<p></p>



<p></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Installing Keycloak on Ubuntu Server 22.04</title>
		<link>https://jeffreybostoen.be/installing-keycloak-on-ubuntu-server-22-04/</link>
		
		<dc:creator><![CDATA[Jeffrey Bostoen]]></dc:creator>
		<pubDate>Sun, 28 Jan 2024 17:41:13 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[ADFS]]></category>
		<category><![CDATA[Keycloak]]></category>
		<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[systemd]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<guid isPermaLink="false">https://jeffreybostoen.be/?p=1063</guid>

					<description><![CDATA[What is Keycloak? Keycloak is a versatile open source identity and access management solution. You&#8217;ll see that it is often used as an external Identity Provider (IdP). You can put Keycloak in front of your web application (for example iTop, Awingu / Parallels Secure Workspace, &#8230;), and users would authenticate to it. Once authenticated to [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">What is Keycloak?</h2>



<p>Keycloak is a versatile open source identity and access management solution. You&#8217;ll see that it is often used as an external Identity Provider (IdP).</p>



<p>You can put Keycloak in front of your web application (for example iTop, Awingu / Parallels Secure Workspace, &#8230;), and users would authenticate to it. Once authenticated to this (most commonly shared with other applications) identity provider; you&#8217;d be redirected to your web application where you&#8217;re then signed in.</p>



<p>You can put it in front of another identity provider, as I&#8217;ve documented for my employer Alludo ( <a href="https://kb.parallels.com/en/129654">Integrating a third-party identity provider in front of Microsoft ADFS with Parallels Secure Workspace</a> ). In that scenario, I used KeyCloak as a stand-in for an external Identity Provider where users would authenticate using their national identity cards. Then, the Microsoft Active Directory Federation Services (ADFS) would transform this (hash of a) national number into the user principle name (UPN) of a Microsoft Active Directory User. </p>



<p>You could also do it the other way around, and put for example Microsoft ADFS in front of KeyCloak.</p>



<h2 class="wp-block-heading">Installation on a fresh Ubuntu Server 20.04 LTS</h2>



<p>Let&#8217;s elevate first, so we can execute the entire installation with elevated rights.</p>



<pre class="wp-block-code"><code>sudo su -</code></pre>



<p>Let&#8217;s make sure we have the latest info on available packages.</p>



<pre class="wp-block-code"><code>root@ubuntu01:~# apt-get update</code></pre>



<h3 class="wp-block-heading">Prerequisite</h3>



<p>Check if Java is installed. The output should look like this:</p>



<pre class="wp-block-code"><code># Check Java version.
root@ubuntu01:~# java -version
openjdk version "11.0.21" 2023-10-17
OpenJDK Runtime Environment (build 11.0.21+9-post-Ubuntu-0ubuntu122.04)
OpenJDK 64-Bit Server VM (build 11.0.21+9-post-Ubuntu-0ubuntu122.04, mixed mode, sharing)</code></pre>



<p>If the above command didn&#8217;t output a version, it means Java may not be installed yet.</p>



<pre class="wp-block-code"><code>root@ubuntu01:~# apt-get install default-jdk -y</code></pre>



<p>If the above command didn&#8217;t output a version, it means Java may not be installed yet. Try:</p>



<h3 class="wp-block-heading">Installing Keycloak</h3>



<p>Unfortunately, it&#8217;s not as straight-forward as just downloading a package. Check the latest available link at https://www.keycloak.org. We&#8217;ll download the distribution from there, to our own <strong>/opt</strong> folder.</p>



<pre class="wp-block-code"><code>root@ubuntu01:~# cd /opt
# Download the file.
root@ubuntu01:~# wget -O keycloak.tar.gz https://github.com/keycloak/keycloak/releases/download/23.0.4/keycloak-23.0.4.tar.gz

# Create directory /opt/keycloak.
root@ubuntu01:~# mkdir keycloak

# Unpack under /opt/keycloak.
# In this example, the .tar.gz actually contained a "keycloak-23.0.4" folder.
# The --strip-components makes sure to ignore this first (1) level.
root@ubuntu01:~# tar -xzvf keycloak.tar.gz keycloak --strip-components=1
# List the files and folders in /opt/keycloak.
# In this example, the directory will contain a subdirectory with the specific version:
# /opt/keycloak/
# It should show a LICENSE.TXT, README.MD, version.txt files; and some folders: bin, conf, lib, providers, themes.
root@ubuntu01:~# ls -l /opt/keycloak

# Cleanup
root@ubuntu01:~# rm keycloak.tar.gz
</code></pre>



<h3 class="wp-block-heading">Securing Keycloak</h3>



<p>It would be a bad idea security-wise to run Keycloak as a root user. <br>As a good practice, we&#8217;ll create a dedicated group and user for Keycloak.</p>



<pre class="wp-block-code"><code># Create a group first.
root@ubuntu01:/opt# groupadd keycloak
# Create a dedicated user account.
# This user account will be part of the group keycloak.
# Its home directory will be set to /opt/keycloak.
# There will be no shell for this user.
# Its username is keycloak.
root@ubuntu01:/opt# useradd -r -g keycloak -d /opt/keycloak -s /sbin/nologin keycloak </code></pre>



<p>Additionally, let&#8217;s adjust the folder and file permissions.</p>



<pre class="wp-block-code"><code># Our Keycloak user becomes the owner of the folder /opt/keycloak and everything in it (recursively).
root@ubuntu01:/opt# chown -R keycloak: keycloak
# Set permissions.
root@ubuntu01:/opt# chmod o+x /opt/keycloak/bin </code></pre>



<h3 class="wp-block-heading">Configuring Keycloak as a service</h3>



<p>If you&#8217;re familiar with some of the more common packages such as Apache, krb5, OpenSSL, &#8230;  You&#8217;ll know that most of the time, you find configuration files under the <strong>/etc</strong> directory for these services. For Keycloak, we need to create this ourselves.</p>



<p>Turns out, my installation was so bare minimum that I still needed to install <strong>vim</strong>. This can be a bit of a complex text editor though for newcomers to Linux. You could of course use any text editor of your choice, such as <strong>nano</strong>.</p>



<pre class="wp-block-code"><code># Install vim.
root@ubuntu01:/opt# apt install vim</code></pre>



<p>The basic goal is to create a service configuration file.</p>



<pre class="wp-block-code"><code># Start editing using vim.
root@ubuntu01:/opt# vim /etc/systemd/system/keycloak.service</code></pre>



<p>The file contents should look like this:</p>



<pre class="wp-block-code"><code>&#91;Unit]
Description=The Keycloak Server
After=syslog.target network.target

# Hint: If Keycloak must be started before other services,
# specify it like this:
# Before=apache2.service

&#91;Service]
User=keycloak
Group=keycloak
LimitNOFILE=102642
PIDFile=/run/keycloak/keycloak.pid
ExecStart=/opt/keycloak/bin/kc.sh start --optimized
StandardOutput=null

&#91;Install]
WantedBy=multi-user.target

</code></pre>



<p>Just as you are following this tutorial, I was also following some pointers on the Internet. For older versions, it seems. For example, to create the service file above, it mentioned copying the configuration file from a directory which is no longer present in the Keycloak 23 package.</p>



<p>I also initially configured a legacy/deprecated directory for the PIDFile. Hence, in the output I&#8217;ve pasted below, you&#8217;ll see the warning about /var/run not being correct.</p>



<pre class="wp-block-code"><code># Reload daemon.
root@ubuntu01:/opt/keycloak# systemctl daemon-reload

# Check Keycloak status. It will likely be shown as "inactive (dead)".
root@ubuntu01:/opt/keycloak# systemctl status keycloak 
○ keycloak.service - The Keycloak Server
     Loaded: loaded (/etc/systemd/system/keycloak.service; disabled; vendor preset: enabled)
     Active: inactive (dead)

Jan 28 13:17:45 ubuntu01 systemd&#91;1]: /etc/systemd/system/keycloak.service:13: PIDFile= references a path below legacy directory /var/run/, updating /var/run/keycloak/keycloak.pid → /run/keycloak/keycloak.pid; please update the unit file accordingly.

# Enable Keycloak.
root@ubuntu01:/opt/keycloak# systemctl enable keycloak

# Let's start the Keycloak service.
root@ubuntu01:/opt/keycloak# systemctl start keycloak

# And let's confirm the status after our start attempt.
root@ubuntu01:/opt/keycloak# systemctl status keycloak 
× keycloak.service - The Keycloak Server
     Loaded: loaded (/etc/systemd/system/keycloak.service; enabled; vendor preset: enabled)
     Active: failed (Result: exit-code) since Sun 2024-01-28 13:19:44 UTC; 6s ago
    Process: 6427 ExecStart=/opt/keycloak/bin/kc.sh start --optimized (code=exited, status=1/FAILURE)
   Main PID: 6427 (code=exited, status=1/FAILURE)
        CPU: 522ms

Jan 28 13:19:43 ubuntu01 systemd&#91;1]: Started The Keycloak Server.
Jan 28 13:19:44 ubuntu01 systemd&#91;1]: keycloak.service: Main process exited, code=exited, status=1/FAILURE
Jan 28 13:19:44 ubuntu01 systemd&#91;1]: keycloak.service: Failed with result 'exit-code'.
</code></pre>



<p>Well, that doesn&#8217;t look too good. I left it in here, as others may be running into the same issue. And to show that behind every post, there is often also initial failure as well. Let&#8217;s troubleshoot this by executing the action manually.</p>



<pre class="wp-block-code"><code>root@ubuntu01:/opt/keycloak# /opt/keycloak/bin/kc.sh start --optimized 
Exception in thread "main" java.lang.UnsupportedClassVersionError: org/keycloak/quarkus/runtime/KeycloakMain has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 55.0
	at java.base/java.lang.ClassLoader.defineClass1(Native Method)
	at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1022)
	at io.quarkus.bootstrap.runner.RunnerClassLoader.loadClass(RunnerClassLoader.java:105)
	at io.quarkus.bootstrap.runner.RunnerClassLoader.loadClass(RunnerClassLoader.java:65)
	at io.quarkus.bootstrap.runner.QuarkusEntryPoint.doRun(QuarkusEntryPoint.java:60)
	at io.quarkus.bootstrap.runner.QuarkusEntryPoint.main(QuarkusEntryPoint.java:32)
</code></pre>



<p>Now this is more meaningful. We need a more up-to-date Java Runtime.</p>



<pre class="wp-block-code"><code># Install OpenJDK 21 Java Runtime Environment (JRE)
root@ubuntu01:/opt/keycloak# apt install openjdk-21-jre 

# Confirm Java version.
root@ubuntu01:/opt/keycloak# java -version
openjdk version "21.0.1" 2023-10-17
OpenJDK Runtime Environment (build 21.0.1+12-Ubuntu-222.04)
OpenJDK 64-Bit Server VM (build 21.0.1+12-Ubuntu-222.04, mixed mode, sharing)
</code></pre>



<p>I tried again, but ran into the next error.</p>



<pre class="wp-block-code"><code>root@ubuntu01:/opt/keycloak# /opt/keycloak/bin/kc.sh start --optimized 
ERROR: Unexpected error when starting the server in (production) mode
ERROR: Failed to start quarkus
ERROR: Strict hostname resolution configured but no hostname setting provided
For more details run the same command passing the '--verbose' option. Also you can use '--help' to see the details about the usage of the particular command.</code></pre>



<p>See what it tells us.</p>



<pre class="wp-block-code"><code>root@ubuntu01:/opt/keycloak# /opt/keycloak/bin/kc.sh start --help
Start the server.

Usage: 

kc.sh start &#91;OPTIONS]

Use this command to run the server in production.

Options:

-h, --help           This help message.
--help-all           This same help message but with additional options.
--import-realm       Import realms during startup by reading any realm configuration file from the
                       'data/import' directory.
--optimized          Use this option to achieve an optimal startup time if you have previously
                       built a server image using the 'build' command.

Cache:

--cache &lt;type&gt;       Defines the cache mechanism for high-availability. By default in production
                       mode, a 'ispn' cache is used to create a cluster between multiple server
                       nodes. By default in development mode, a 'local' cache disables clustering
                       and is intended for development and testing purposes. Possible values are:
                       ispn, local. Default: ispn.
--cache-config-file &lt;file&gt;
                     Defines the file from which cache configuration should be loaded from. The
                       configuration file is relative to the 'conf/' directory.
--cache-stack &lt;stack&gt;
                     Define the default stack to use for cluster communication and node discovery.
                       This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
                       Possible values are: tcp, udp, kubernetes, ec2, azure, google.

Database:

--db &lt;vendor&gt;        The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
                       mysql, oracle, postgres. Default: dev-file.
--db-driver &lt;driver&gt; The fully qualified class name of the JDBC driver. If not set, a default
                       driver is set accordingly to the chosen database.
--db-password &lt;password&gt;
                     The password of the database user.
--db-pool-initial-size &lt;size&gt;
                     The initial size of the connection pool.
--db-pool-max-size &lt;size&gt;
                     The maximum size of the connection pool. Default: 100.
--db-pool-min-size &lt;size&gt;
                     The minimal size of the connection pool.
--db-schema &lt;schema&gt; The database schema to be used.
--db-url &lt;jdbc-url&gt;  The full database JDBC URL. If not provided, a default URL is set based on the
                       selected database vendor. For instance, if using 'postgres', the default
                       JDBC URL would be 'jdbc:postgresql://localhost/keycloak'.
--db-url-database &lt;dbname&gt;
                     Sets the database name of the default JDBC URL of the chosen vendor. If the
                       `db-url` option is set, this option is ignored.
--db-url-host &lt;hostname&gt;
                     Sets the hostname of the default JDBC URL of the chosen vendor. If the
                       `db-url` option is set, this option is ignored.
--db-url-port &lt;port&gt; Sets the port of the default JDBC URL of the chosen vendor. If the `db-url`
                       option is set, this option is ignored.
--db-url-properties &lt;properties&gt;
                     Sets the properties of the default JDBC URL of the chosen vendor. Make sure to
                       set the properties accordingly to the format expected by the database
                       vendor, as well as appending the right character at the beginning of this
                       property value. If the `db-url` option is set, this option is ignored.
--db-username &lt;username&gt;
                     The username of the database user.

Transaction:

--transaction-xa-enabled &lt;true|false&gt;
                     If set to false, Keycloak uses a non-XA datasource in case the database does
                       not support XA transactions. Default: true.

Feature:

--features &lt;feature&gt; Enables a set of one or more features. Possible values are: account-api,
                       account2, account3, admin-api, admin-fine-grained-authz, admin2,
                       authorization, ciba, client-policies, client-secret-rotation,
                       declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
                       impersonation, js-adapter, kerberos, linkedin-oauth, map-storage,
                       multi-site, par, preview, recovery-codes, scripts, step-up-authentication,
                       token-exchange, transient-users, update-email, web-authn.
--features-disabled &lt;feature&gt;
                     Disables a set of one or more features. Possible values are: account-api,
                       account2, account3, admin-api, admin-fine-grained-authz, admin2,
                       authorization, ciba, client-policies, client-secret-rotation,
                       declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
                       impersonation, js-adapter, kerberos, linkedin-oauth, map-storage,
                       multi-site, par, preview, recovery-codes, scripts, step-up-authentication,
                       token-exchange, transient-users, update-email, web-authn.

Hostname:

--hostname &lt;hostname&gt;
                     Hostname for the Keycloak server.
--hostname-admin &lt;hostname&gt;
                     The hostname for accessing the administration console. Use this option if you
                       are exposing the administration console using a hostname other than the
                       value set to the 'hostname' option.
--hostname-admin-url &lt;url&gt;
                     Set the base URL for accessing the administration console, including scheme,
                       host, port and path
--hostname-debug &lt;true|false&gt;
                     Toggle the hostname debug page that is accessible at
                       /realms/master/hostname-debug Default: false.
--hostname-path &lt;path&gt;
                     This should be set if proxy uses a different context-path for Keycloak.
--hostname-port &lt;port&gt;
                     The port used by the proxy when exposing the hostname. Set this option if the
                       proxy uses a port other than the default HTTP and HTTPS ports. Default: -1.
--hostname-strict &lt;true|false&gt;
                     Disables dynamically resolving the hostname from request headers. Should
                       always be set to true in production, unless proxy verifies the Host header.
                       Default: true.
--hostname-strict-backchannel &lt;true|false&gt;
                     By default backchannel URLs are dynamically resolved from request headers to
                       allow internal and external applications. If all applications use the public
                       URL this option should be enabled. Default: false.
--hostname-url &lt;url&gt; Set the base URL for frontend URLs, including scheme, host, port and path.

HTTP/TLS:

--http-enabled &lt;true|false&gt;
                     Enables the HTTP listener. Default: false.
--http-host &lt;host&gt;   The used HTTP Host. Default: 0.0.0.0.
--http-max-queued-requests &lt;requests&gt;
                     Maximum number of queued HTTP requests. Use this to shed load in an overload
                       situation. Excess requests will return a "503 Server not Available" response.
--http-port &lt;port&gt;   The used HTTP port. Default: 8080.
--http-relative-path &lt;path&gt;
                     Set the path relative to '/' for serving resources. The path must start with a
                       '/'. Default: /.
--https-certificate-file &lt;file&gt;
                     The file path to a server certificate or certificate chain in PEM format.
--https-certificate-key-file &lt;file&gt;
                     The file path to a private key in PEM format.
--https-cipher-suites &lt;ciphers&gt;
                     The cipher suites to use. If none is given, a reasonable default is selected.
--https-client-auth &lt;auth&gt;
                     Configures the server to require/request client authentication. Possible
                       values are: none, request, required. Default: none.
--https-key-store-file &lt;file&gt;
                     The key store which holds the certificate information instead of specifying
                       separate files.
--https-key-store-password &lt;password&gt;
                     The password of the key store file. Default: password.
--https-key-store-type &lt;type&gt;
                     The type of the key store file. If not given, the type is automatically
                       detected based on the file name. If 'fips-mode' is set to 'strict' and no
                       value is set, it defaults to 'BCFKS'.
--https-port &lt;port&gt;  The used HTTPS port. Default: 8443.
--https-protocols &lt;protocols&gt;
                     The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2.
--https-trust-store-file &lt;file&gt;
                     The trust store which holds the certificate information of the certificates to
                       trust.
--https-trust-store-password &lt;password&gt;
                     The password of the trust store file.
--https-trust-store-type &lt;type&gt;
                     The type of the trust store file. If not given, the type is automatically
                       detected based on the file name. If 'fips-mode' is set to 'strict' and no
                       value is set, it defaults to 'BCFKS'.

Health:

--health-enabled &lt;true|false&gt;
                     If the server should expose health check endpoints. If enabled, health checks
                       are available at the '/health', '/health/ready' and '/health/live'
                       endpoints. Default: false.

Config:

--config-keystore &lt;config-keystore&gt;
                     Specifies a path to the KeyStore Configuration Source.
--config-keystore-password &lt;config-keystore-password&gt;
                     Specifies a password to the KeyStore Configuration Source.
--config-keystore-type &lt;config-keystore-type&gt;
                     Specifies a type of the KeyStore Configuration Source. Default: PKCS12.

Metrics:

--metrics-enabled &lt;true|false&gt;
                     If the server should expose metrics. If enabled, metrics are available at the
                       '/metrics' endpoint. Default: false.

Proxy:

--proxy &lt;mode&gt;       The proxy address forwarding mode if the server is behind a reverse proxy.
                       Possible values are: none, edge, reencrypt, passthrough. Default: none.

Vault:

--vault &lt;provider&gt;   Enables a vault provider. Possible values are: file, keystore.
--vault-dir &lt;dir&gt;    If set, secrets can be obtained by reading the content of files within the
                       given directory.
--vault-file &lt;file&gt;  Path to the keystore file.
--vault-pass &lt;pass&gt;  Password for the vault keystore.
--vault-type &lt;type&gt;  Specifies the type of the keystore file. Default: PKCS12.

Logging:

--log &lt;handler&gt;      Enable one or more log handlers in a comma-separated list. Possible values
                       are: console, file, gelf. Default: console.
--log-console-color &lt;true|false&gt;
                     Enable or disable colors when logging to console. Default: false.
--log-console-format &lt;format&gt;
                     The format of unstructured console log entries. If the format has spaces in
                       it, escape the value using "&lt;format&gt;". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} %
                       -5p &#91;%c] (%t) %s%e%n.
--log-console-output &lt;output&gt;
                     Set the log output to JSON or default (plain) unstructured logging. Possible
                       values are: default, json. Default: default.
--log-file &lt;file&gt;    Set the log file path and filename. Default: data/log/keycloak.log.
--log-file-format &lt;format&gt;
                     Set a format specific to file log entries. Default: %d{yyyy-MM-dd HH:mm:ss,
                       SSS} %-5p &#91;%c] (%t) %s%e%n.
--log-file-output &lt;output&gt;
                     Set the log output to JSON or default (plain) unstructured logging. Possible
                       values are: default, json. Default: default.
--log-gelf-facility &lt;name&gt;
                     The facility (name of the process) that sends the message. Default: keycloak.
--log-gelf-host &lt;hostname&gt;
                     Hostname of the Logstash or Graylog Host. By default UDP is used, prefix the
                       host with 'tcp:' to switch to TCP. Example: 'tcp:localhost' Default:
                       localhost.
--log-gelf-include-location &lt;true|false&gt;
                     Include source code location. Default: true.
--log-gelf-include-message-parameters &lt;true|false&gt;
                     Include message parameters from the log event. Default: true.
--log-gelf-include-stack-trace &lt;true|false&gt;
                     If set to true, occuring stack traces are included in the 'StackTrace' field
                       in the GELF output. Default: true.
--log-gelf-level &lt;level&gt;
                     The log level specifying which message levels will be logged by the GELF
                       logger. Message levels lower than this value will be discarded. Default:
                       INFO.
--log-gelf-max-message-size &lt;size&gt;
                     Maximum message size (in bytes). If the message size is exceeded, GELF will
                       submit the message in multiple chunks. Default: 8192.
--log-gelf-port &lt;port&gt;
                     The port the Logstash or Graylog Host is called on. Default: 12201.
--log-gelf-timestamp-format &lt;pattern&gt;
                     Set the format for the GELF timestamp field. Uses Java SimpleDateFormat
                       pattern. Default: yyyy-MM-dd HH:mm:ss,SSS.
--log-level &lt;category:level&gt;
                     The log level of the root category or a comma-separated list of individual
                       categories and their levels. For the root category, you don't need to
                       specify a category. Default: info.

Security:

--fips-mode &lt;mode&gt;   Sets the FIPS mode. If 'non-strict' is set, FIPS is enabled but on
                       non-approved mode. For full FIPS compliance, set 'strict' to run on approved
                       mode. This option defaults to 'disabled' when 'fips' feature is disabled,
                       which is by default. This option defaults to 'non-strict' when 'fips'
                       feature is enabled. Possible values are: non-strict, strict. Default:
                       disabled.

By default, this command tries to update the server configuration by running a
'build' before starting the server. You can disable this behavior by using the
'--optimized' option:

      $ kc.sh start '--optimized'

By doing that, the server should start faster based on any previous
configuration you have set when manually running the 'build' command.
</code></pre>



<p>Let&#8217;s try and specify a hostname.</p>



<pre class="wp-block-code"><code>root@ubuntu01:/opt/keycloak# /opt/keycloak/bin/kc.sh start --hostname=ubuntu01
2024-01-28 13:52:48,149 INFO  &#91;org.keycloak.quarkus.runtime.hostname.DefaultHostnameProvider] (main) Hostname settings: Base URL: &lt;unset&gt;, Hostname: ubuntu01, Strict HTTPS: true, Path: &lt;request&gt;, Strict BackChannel: false, Admin URL: &lt;unset&gt;, Admin: &lt;request&gt;, Port: -1, Proxied: false
2024-01-28 13:52:50,414 WARN  &#91;io.quarkus.agroal.runtime.DataSources] (main) Datasource &lt;default&gt; enables XA but transaction recovery is not enabled. Please enable transaction recovery by setting quarkus.transaction-manager.enable-recovery=true, otherwise data may be lost if the application is terminated abruptly
2024-01-28 13:52:51,729 WARN  &#91;org.infinispan.PERSISTENCE] (keycloak-cache-init) ISPN000554: jboss-marshalling is deprecated and planned for removal
2024-01-28 13:52:52,070 INFO  &#91;org.infinispan.CONTAINER] (keycloak-cache-init) ISPN000556: Starting user marshaller 'org.infinispan.jboss.marshalling.core.JBossUserMarshaller'
2024-01-28 13:52:52,328 INFO  &#91;org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000088: Unable to use any JGroups configuration mechanisms provided in properties {}. Using default JGroups configuration!
2024-01-28 13:52:52,670 INFO  &#91;org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000078: Starting JGroups channel `ISPN`
2024-01-28 13:52:52,673 INFO  &#91;org.jgroups.JChannel] (keycloak-cache-init) local_addr: f84cf898-bda1-4458-a2b9-0afa365204b2, name: ubuntu01-33680
2024-01-28 13:52:52,679 WARN  &#91;org.jgroups.protocols.UDP] (keycloak-cache-init) JGRP000015: the send buffer of socket MulticastSocket was set to 1MB, but the OS only allocated 212.99KB
2024-01-28 13:52:52,680 WARN  &#91;org.jgroups.protocols.UDP] (keycloak-cache-init) JGRP000015: the receive buffer of socket MulticastSocket was set to 20MB, but the OS only allocated 212.99KB
2024-01-28 13:52:52,681 WARN  &#91;org.jgroups.protocols.UDP] (keycloak-cache-init) JGRP000015: the send buffer of socket MulticastSocket was set to 1MB, but the OS only allocated 212.99KB
2024-01-28 13:52:52,683 WARN  &#91;org.jgroups.protocols.UDP] (keycloak-cache-init) JGRP000015: the receive buffer of socket MulticastSocket was set to 25MB, but the OS only allocated 212.99KB
2024-01-28 13:52:52,718 INFO  &#91;org.jgroups.protocols.FD_SOCK2] (keycloak-cache-init) server listening on *.23781
2024-01-28 13:52:54,730 INFO  &#91;org.jgroups.protocols.pbcast.GMS] (keycloak-cache-init) ubuntu01-33680: no members discovered after 2007 ms: creating cluster as coordinator
2024-01-28 13:52:54,737 INFO  &#91;org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000094: Received new cluster view for channel ISPN: &#91;ubuntu01-33680|0] (1) &#91;ubuntu01-33680]
2024-01-28 13:52:54,743 INFO  &#91;org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000079: Channel `ISPN` local address is `ubuntu01-33680`, physical addresses are `&#91;192.168.0.220:39317]`
2024-01-28 13:52:54,755 WARN  &#91;org.infinispan.CONFIG] (keycloak-cache-init) ISPN000569: Unable to persist Infinispan internal caches as no global state enabled
2024-01-28 13:52:55,189 INFO  &#91;org.infinispan.CLUSTER] (main) ISPN000080: Disconnecting JGroups channel `ISPN`
2024-01-28 13:52:55,284 ERROR &#91;org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler] (main) ERROR: Failed to start server in (production) mode
2024-01-28 13:52:55,284 ERROR &#91;org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler] (main) ERROR: Key material not provided to setup HTTPS. Please configure your keys/certificates or start the server in development mode.
2024-01-28 13:52:55,285 ERROR &#91;org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler] (main) For more details run the same command passing the '--verbose' option. Also you can use '--help' to see the details about the usage of the particular command.
root@ubuntu01:/opt/keycloak# /opt/keycloak/bin/kc.sh start --hostname=ubuntu01
</code></pre>



<p>Another error. Yes, I&#8217;m indeed trying to launch Keycloak without using a certificate. It&#8217;s important to note here that I&#8217;m just quickly testing, and trying to get it up and running. Before going live with a production environment, of course this would need to be addressed properly.</p>



<p>Let&#8217;s try to run it again, this time with HTTP enabled.</p>



<pre class="wp-block-code"><code>root@ubuntu01:/opt/keycloak# /opt/keycloak/bin/kc.sh start --hostname=ubuntu01 --http-enabled=true
2024-01-28 13:57:17,080 INFO  &#91;org.keycloak.quarkus.runtime.hostname.DefaultHostnameProvider] (main) Hostname settings: Base URL: &lt;unset&gt;, Hostname: ubuntu01, Strict HTTPS: true, Path: &lt;request&gt;, Strict BackChannel: false, Admin URL: &lt;unset&gt;, Admin: &lt;request&gt;, Port: -1, Proxied: false
2024-01-28 13:57:18,939 WARN  &#91;io.quarkus.agroal.runtime.DataSources] (main) Datasource &lt;default&gt; enables XA but transaction recovery is not enabled. Please enable transaction recovery by setting quarkus.transaction-manager.enable-recovery=true, otherwise data may be lost if the application is terminated abruptly
2024-01-28 13:57:20,485 WARN  &#91;org.infinispan.PERSISTENCE] (keycloak-cache-init) ISPN000554: jboss-marshalling is deprecated and planned for removal
2024-01-28 13:57:20,623 INFO  &#91;org.infinispan.CONTAINER] (keycloak-cache-init) ISPN000556: Starting user marshaller 'org.infinispan.jboss.marshalling.core.JBossUserMarshaller'
2024-01-28 13:57:20,813 INFO  &#91;org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000088: Unable to use any JGroups configuration mechanisms provided in properties {}. Using default JGroups configuration!
2024-01-28 13:57:21,215 INFO  &#91;org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000078: Starting JGroups channel `ISPN`
2024-01-28 13:57:21,228 INFO  &#91;org.jgroups.JChannel] (keycloak-cache-init) local_addr: 6e1ad1ef-2284-44ed-b58f-3f7dd14e9aa5, name: ubuntu01-34727
2024-01-28 13:57:21,241 WARN  &#91;org.jgroups.protocols.UDP] (keycloak-cache-init) JGRP000015: the send buffer of socket MulticastSocket was set to 1MB, but the OS only allocated 212.99KB
2024-01-28 13:57:21,243 WARN  &#91;org.jgroups.protocols.UDP] (keycloak-cache-init) JGRP000015: the receive buffer of socket MulticastSocket was set to 20MB, but the OS only allocated 212.99KB
2024-01-28 13:57:21,244 WARN  &#91;org.jgroups.protocols.UDP] (keycloak-cache-init) JGRP000015: the send buffer of socket MulticastSocket was set to 1MB, but the OS only allocated 212.99KB
2024-01-28 13:57:21,244 WARN  &#91;org.jgroups.protocols.UDP] (keycloak-cache-init) JGRP000015: the receive buffer of socket MulticastSocket was set to 25MB, but the OS only allocated 212.99KB
2024-01-28 13:57:21,264 INFO  &#91;org.jgroups.protocols.FD_SOCK2] (keycloak-cache-init) server listening on *.32758
2024-01-28 13:57:23,288 INFO  &#91;org.jgroups.protocols.pbcast.GMS] (keycloak-cache-init) ubuntu01-34727: no members discovered after 2004 ms: creating cluster as coordinator
2024-01-28 13:57:23,308 INFO  &#91;org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000094: Received new cluster view for channel ISPN: &#91;ubuntu01-34727|0] (1) &#91;ubuntu01-34727]
2024-01-28 13:57:23,333 INFO  &#91;org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000079: Channel `ISPN` local address is `ubuntu01-34727`, physical addresses are `&#91;192.168.0.220:48294]`
2024-01-28 13:57:23,355 WARN  &#91;org.infinispan.CONFIG] (keycloak-cache-init) ISPN000569: Unable to persist Infinispan internal caches as no global state enabled
2024-01-28 13:57:25,751 INFO  &#91;org.keycloak.quarkus.runtime.storage.legacy.liquibase.QuarkusJpaUpdaterProvider] (main) Initializing database schema. Using changelog META-INF/jpa-changelog-master.xml

UPDATE SUMMARY
Run:                        117
Previously run:               0
Filtered out:                 0
-------------------------------
Total change sets:          117

2024-01-28 13:57:31,584 INFO  &#91;org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory] (main) Node name: ubuntu01-34727, Site name: null
2024-01-28 13:57:31,711 INFO  &#91;org.keycloak.broker.provider.AbstractIdentityProviderMapper] (main) Registering class org.keycloak.broker.provider.mappersync.ConfigSyncEventListener
2024-01-28 13:57:31,788 INFO  &#91;org.keycloak.services] (main) KC-SERVICES0050: Initializing master realm
2024-01-28 13:57:34,400 INFO  &#91;io.quarkus] (main) Keycloak 23.0.4 on JVM (powered by Quarkus 3.2.9.Final) started in 19.120s. Listening on: http://0.0.0.0:8080
2024-01-28 13:57:34,401 INFO  &#91;io.quarkus] (main) Profile prod activated. 
2024-01-28 13:57:34,401 INFO  &#91;io.quarkus] (main) Installed features: &#91;agroal, cdi, hibernate-orm, jdbc-h2, jdbc-mariadb, jdbc-mssql, jdbc-mysql, jdbc-oracle, jdbc-postgresql, keycloak, logging-gelf, micrometer, narayana-jta, reactive-routes, resteasy-reactive, resteasy-reactive-jackson, smallrye-context-propagation, smallrye-health, vertx]
</code></pre>



<p>Alright, this seems to work! On this server, when navigating to http://127.0.0.1:8080/ , you should be able to see the Keycloak UI now.</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="328" src="https://jeffreybostoen.be/wp-content/uploads/2024/01/image-1-1024x328.png" alt="" class="wp-image-1070" srcset="https://jeffreybostoen.be/wp-content/uploads/2024/01/image-1-1024x328.png 1024w, https://jeffreybostoen.be/wp-content/uploads/2024/01/image-1-300x96.png 300w, https://jeffreybostoen.be/wp-content/uploads/2024/01/image-1-768x246.png 768w, https://jeffreybostoen.be/wp-content/uploads/2024/01/image-1-1536x492.png 1536w, https://jeffreybostoen.be/wp-content/uploads/2024/01/image-1.png 1837w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Explaining how to get a proper SSL certificate (not a self-signed one) will lead us too far off topic. So for the purpose of this post, I did create a self-signed SSL certificate.</p>



<pre class="wp-block-code"><code># Create directory to store the certificate and the key.
root@ubuntu01:/opt/keycloak# mkdir /opt/keycloak/certs
# Use openssl to generate a self-signed certificate.
root@ubuntu01:/opt/keycloak# openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout mycert.key -out mycert.crt </code></pre>



<p>Let&#8217;s try to run the Keycloak server with the self-signed certificate now. I&#8217;ve also specified the HTTPS port on which I expect Keycloak to listen: 8443.</p>



<pre class="wp-block-code"><code>root@ubuntu01:/opt/keycloak# /opt/keycloak/bin/kc.sh start --hostname=ubuntu01 --https-certificate-file=/opt/keycloak/cert/mycert.crt --https-certificate-key-file=/opt/keycloak/cert/mycert.key --https-port=8443</code></pre>



<p>This time, when navigating on this server to https://127.0.0.1:8443/ , I did get a certificate warning (since it&#8217;s self-signed). But it does work.</p>



<p>Final step for the service configuration: update the configuration file again to reflect this new instruction.</p>



<p>I did this:</p>



<pre class="wp-block-code"><code>root@ubuntu01:/opt/keycloak# systemctl daemon-reload
root@ubuntu01:/opt/keycloak# systemctl restart keycloak
root@ubuntu01:/opt/keycloak# systemctl status keycloak</code></pre>



<p>It worked. For a few seconds. Then it failed. To properly debug it, I changed the &#8220;StandardOutput&#8221; line from the service configuration.</p>



<pre class="wp-block-code"><code>StandardOutput=file:/var/log/keycloak/standard.log
StandardError=file:/var/log/keycloak/error.log</code></pre>



<p>Other lessons I learned: Make sure to create the log directory and set the permissions (in this case, change the owner to the keycloak user):</p>



<pre class="wp-block-code"><code># Create log directory.
root@ubuntu01:/opt/keycloak# mkdir /var/log/keycloak
# Change the owner to the keycloak user (defined in the service configuration).
root@ubuntu01:/opt/keycloak# chown -R /var/log/keycloak</code></pre>



<p>The reason it had crashed, turned out to be folder and file permissions. While configuring, I ran some commands as the root user (named &#8220;root&#8221;). As a result, under <strong>/opt/keycloak</strong>, the <strong>data</strong> and <strong>certs</strong> directories were both having the root user as an owner.</p>



<pre class="wp-block-code"><code># Set owner of folder and files of the entire Keycloak directory again to the keycloak user.
root@ubuntu01:/opt/keycloak# chown -R keycloak: /opt/keycloak</code></pre>



<p>Finally, it was up and running.</p>



<p>Bonus hint: for users, their account page (sign in) in Keycloak 23 will for example be: https://ubuntu01:8443/realms/iTop/account  . <br>So: https://{fqdn}/realms/{realmName}/account </p>



<p></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Increasing disk space on Ubuntu Server 20.04 LTS</title>
		<link>https://jeffreybostoen.be/increasing-disk-space-on-ubuntu-server-20-04-lts/</link>
		
		<dc:creator><![CDATA[Jeffrey Bostoen]]></dc:creator>
		<pubDate>Sun, 28 Jan 2024 12:40:16 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[VMware]]></category>
		<guid isPermaLink="false">https://jeffreybostoen.be/?p=1068</guid>

					<description><![CDATA[Recently I installed an Ubuntu Server 20.04 LTS in a virtual machine on VM Workstation Player 17. For some reason, while I did create a 20 GB disk, I quickly got a warning that my disk space was nearly full. Having used Ubuntu for a while, I was surprised to see my 20 GB would [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Recently I installed an Ubuntu Server 20.04 LTS in a virtual machine on VM Workstation Player 17.</p>



<p>For some reason, while I did create a 20 GB disk, I quickly got a warning that my disk space was nearly full. Having used Ubuntu for a while, I was surprised to see my 20 GB would already be full on this fresh install.</p>



<p>So I verified using the commands below. As you can see, it did detect that my physical volume was slightly short of 20 GB.</p>



<pre class="wp-block-code"><code>root@ubuntu01:/opt# pvdisplay
  --- Physical volume ---
  PV Name               /dev/sda3
  VG Name               ubuntu-vg
  PV Size               &lt;18.23 GiB / not usable 3.00 MiB
  Allocatable           yes 
  PE Size               4.00 MiB
  Total PE              4665
  Free PE               2105
  Allocated PE          2560
  PV UUID               bsAlMw-TKc1-hMOx-8tdy-fjKR-NwW6-uMG5fg
</code></pre>



<p>But my volume group was a different story. The volume group size (VG Size) was close to what the output of the physical volume told us. However, only 10 GB was allocated; and 8.22 GiB was for some reason not allocated. Probably an oversight on my side.</p>



<pre class="wp-block-code"><code>
   
root@ubuntu01:/opt# vgdisplay
  --- Volume group ---
  VG Name               ubuntu-vg
  System ID             
  Format                lvm2
  Metadata Areas        1
  Metadata Sequence No  2
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                1
  Open LV               1
  Max PV                0
  Cur PV                1
  Act PV                1
  VG Size               18.22 GiB
  PE Size               4.00 MiB
  Total PE              4665
  Alloc PE / Size       2560 / 10.00 GiB
  Free  PE / Size       2105 / 8.22 GiB
  VG UUID               sYknqJ-0ZCm-9Cqc-qXp2-yU80-IC7N-eM75Jb</code></pre>



<p>lvdisplay confirms this:</p>



<pre class="wp-block-code"><code>root@ubuntu01:/opt# lvdisplay
  --- Logical volume ---
  LV Path                /dev/ubuntu-vg/ubuntu-lv
  LV Name                ubuntu-lv
  VG Name                ubuntu-vg
  LV UUID                YAs7Vb-5luh-TBOs-IVOH-cxiT-s4zX-OhFinf
  LV Write Access        read/write
  LV Creation host, time ubuntu-server, 2024-01-28 08:44:17 +0000
  LV Status              available
  # open                 1
  LV Size                10.00 GiB
  Current LE             2560
  Segments               1
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     256
  Block device           253:0
</code></pre>



<p>Now, let&#8217;s make sure this logical volume group uses all the available space.<br>Above, we see the LV Path, which is what we need to run our next command.</p>



<pre class="wp-block-code"><code>root@ubuntu01:/opt# lvextend -l +100%FREE /dev/ubuntu-vg/ubuntu-lv 
  Size of logical volume ubuntu-vg/ubuntu-lv changed from 10.00 GiB (2560 extents) to 18.22 GiB (4665 extents).
  Logical volume ubuntu-vg/ubuntu-lv successfully resized.
</code></pre>



<p>If like me you forgot to add &#8220;-r&#8221; in the command above, check the type of file system.</p>



<pre class="wp-block-code"><code># Check sizes
root@ubuntu01:~# df -h
Filesystem                         Size  Used Avail Use% Mounted on
tmpfs                              388M  1.9M  386M   1% /run
/dev/mapper/ubuntu--vg-ubuntu--lv   18G  9.3G  7.8G  55% /
tmpfs                              1.9G     0  1.9G   0% /dev/shm
tmpfs                              5.0M  4.0K  5.0M   1% /run/lock
/dev/sda2                          1.8G  136M  1.5G   9% /boot
tmpfs                              388M  132K  388M   1% /run/user/1000

# Check file system
root@ubuntu01:~# df -T 
Filesystem                        Type  1K-blocks    Used Available Use% Mounted on
tmpfs                             tmpfs    397092    1984    395108   1% /run
/dev/mapper/ubuntu--vg-ubuntu--lv ext4   18696940 9680488   8109936  55% /
tmpfs                             tmpfs   1985444       0   1985444   0% /dev/shm
tmpfs                             tmpfs      5120       4      5116   1% /run/lock
/dev/sda2                         ext4    1790136  138800   1542076   9% /boot
tmpfs                             tmpfs    397088     140    396948   1% /run/user/1000

</code></pre>



<p>In my case, I needed <strong>/dev/mapper/ubuntu&#8211;vg-ubuntu&#8211;lv </strong>which is an ext4 file system. My final step:</p>



<pre class="wp-block-code"><code>root@ubuntu01:~# resize2fs /dev/mapper/ubuntu--vg-ubuntu--lv</code></pre>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>A.I. vs. automation of workflows in customer support (using iTop)</title>
		<link>https://jeffreybostoen.be/ai-vs-automation-of-workflows-in-helpdesk/</link>
		
		<dc:creator><![CDATA[Jeffrey Bostoen]]></dc:creator>
		<pubDate>Mon, 08 Jan 2024 09:56:00 +0000</pubDate>
				<category><![CDATA[iTop]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[customer support]]></category>
		<category><![CDATA[itop]]></category>
		<guid isPermaLink="false">https://jeffreybostoen.be/?p=1026</guid>

					<description><![CDATA[A.I. (artificial intelligence) is the buzzword nowadays, especially in sales pitches. Expectations are high. However, it&#8217;s crucial to keep in mind what A.I. means. A simple definition from Wikipedia: Artificial intelligence (AI) is the intelligence of machines or software, as opposed to the intelligence of humans or animals. Now, when researching how A.I. can be [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p></p>



<div class="wp-block-group is-layout-constrained wp-block-group-is-layout-constrained">
<p><strong>A.I. (artificial intelligence)</strong> is the buzzword nowadays, especially in sales pitches. Expectations are high.</p>



<p>However, it&#8217;s crucial to keep in mind what A.I. means. A simple definition from Wikipedia: <em>Artificial intelligence</em> (<em>AI</em>) is the intelligence of machines or software, as opposed to the intelligence of humans or animals. <sup data-fn="221475a6-5ffe-49eb-a7a7-4a59e6f85e05" class="fn"><a href="#221475a6-5ffe-49eb-a7a7-4a59e6f85e05" id="221475a6-5ffe-49eb-a7a7-4a59e6f85e05-link">1</a></sup></p>



<p>Now, when researching how A.I. can be leveraged in customer support, it often boils down to<strong> improving customer satisfaction </strong>or <strong>cost efficiency</strong> in various manners. In a traditional service desk tool, you&#8217;ll quickly notice most of the topics are not as complicated as you&#8217;d think.</p>



<p>A.I. is much more generic than the term <strong>machine learning</strong>. (<em> Wikipedia: Machine learning (ML) is a field of study in artificial intelligence concerned with the development and study of statistical algorithms that can learn from data and generalize to unseen data, and thus perform tasks without explicit instructions. </em>) <sup data-fn="159df89c-c13c-4102-9633-b37c8804ce65" class="fn"><a href="#159df89c-c13c-4102-9633-b37c8804ce65" id="159df89c-c13c-4102-9633-b37c8804ce65-link">2</a></sup> .</p>



<p>You&#8217;ll notice that a lot of ideas are actually still very much rule-based; rather than having some form of self-learning and self-improving neural network.</p>



<p>To put it very simple: With machine learning, the system learns and trains itself from historical data.</p>



<p>So what are the main ideas when it comes to A.I.? What&#8217;s currently possible? How can these tools lead to more satisfied customers? How can they assist support agents? Is there a risk (mostly first line) support agents will be replaced? What are the pitfalls? And, in our case, how does it apply to iTop?</p>



<p>Below you&#8217;ll find a summary of the most common concepts; their benefits; and their pitfalls.</p>



<p>Note that sometimes the goal is &#8220;reduce workload&#8221;. This is a very generic phrase. It could indeed mean that a business would need fewer people. It could also mean the staff has more opportunities to invest time in other facets of their job.</p>
</div>



<div class="wp-block-group is-layout-constrained wp-block-group-is-layout-constrained">
<h2 class="wp-block-heading">Smart suggestions and recommendations</h2>



<p><strong>What:</strong> Customers and staff could get recommendations on how to solve issues. They can be provided with a limited and accurate answer; to make sure they don&#8217;t need to go through an extensive admin manual or knowledge base.</p>



<p><strong>Benefits:</strong></p>



<ul class="wp-block-list">
<li>For customers: Instant accurate answers.</li>



<li>For staff: Less work. Tickets will be deflected.</li>
</ul>



<p><strong>Goals:</strong></p>



<ul class="wp-block-list">
<li>Improve customer satisfaction. </li>



<li>Reduce work load.</li>
</ul>



<p><strong>Risks:</strong></p>



<ul class="wp-block-list">
<li>Low: Customer frustration.</li>
</ul>



<p><strong>Challenge:</strong></p>



<p>The suggestions should be &#8220;smart&#8221;. The system should ideally only show recommendations which actually matter. For example, if a customer is specifically asking about product X, they should not be presented with knowledge base articles relating to product Y.</p>



<p>The list of suggestions should also be short and relevant. The suggestions should not lead to more confusion.</p>



<p>Smart suggestions could be offered in many ways:</p>



<ul class="wp-block-list" id="block-17eec2b5-6af7-417c-8dca-48b43341f4b4">
<li>Customers creating a ticket through a support portal, can be shown suggestions right away. This works for the classic creation of a ticket; but also with chatbots.</li>



<li>Customers creating a ticket through different channels, even e-mail, could receive these suggestions automatically at first.</li>



<li>Within the back-end of a service desk, recommendations could also be shown to the agent who handles the support case. From the recommendations, the agent can then select which ones truly matter for the customer.</li>
</ul>



<p></p>



<p><strong>Options in iTop:</strong></p>



<ul class="wp-block-list">
<li>Pro extension: <a href="https://jeffreybostoen.be/itop-extension-suggested-faqs/">Suggested Articles (FAQs)</a></li>
</ul>
</div>



<div class="wp-block-group is-layout-constrained wp-block-group-is-layout-constrained">
<h2 class="wp-block-heading">Automated ticket triaging</h2>



<p><strong>What:</strong> A.I. can already do initial processing of a ticket. The urgency or complexity can be determined, categorization can already be performed (e.g. &#8220;software&#8221; or &#8220;hardware&#8221; issue), &#8230;</p>



<p>The most simple form is to do this rule-based. More advanced forms consist of machine learning, where the system learns for example how urgent a ticket really was.</p>



<p>After categorization, it can also assign the proper teams or agents.</p>



<p><strong>Benefits:</strong></p>



<ul class="wp-block-list">
<li>For customers: Better response times.</li>



<li>For staff: 
<ul class="wp-block-list">
<li>Less work. </li>



<li>Consistency. A system should also be more consistent in its categorization than different people doing the categorization.</li>
</ul>
</li>
</ul>



<p><strong>Goals:</strong></p>



<ul class="wp-block-list">
<li>Improve customer satisfaction. </li>



<li>Reduce work load.</li>
</ul>



<p><strong>Risks:</strong></p>



<ul class="wp-block-list">
<li>Medium: Customer frustration.</li>



<li>Medium: Staff frustration.</li>
</ul>



<p><strong>Options in iTop:</strong></p>



<ul class="wp-block-list">
<li>Pro extension: <a href="https://jeffreybostoen.be/itop-extension-auto-assigner/" data-type="page" data-id="563">Auto Assigner</a></li>
</ul>
</div>



<div class="wp-block-group is-layout-constrained wp-block-group-is-layout-constrained">
<h2 class="wp-block-heading">Agent routing</h2>



<p><strong>What:</strong> A.I. can assign teams and agents to tickets. The system can route tickets to the best suited team or person. This could be done based on categorization (see above), but it could also factor in different factors such as work load, availability (holidays, schedules, time zone, &#8230;), expertise, spoken languages, other skills, &#8230;</p>



<p>The most simple form is to do this rule-based. More advanced forms consist of machine learning, where the system learns for example which agent handled similar cases in the most efficient manner.</p>



<p><strong>Goals:</strong></p>



<ul class="wp-block-list">
<li>Improve customer satisfaction. </li>



<li>Reduce work load.</li>
</ul>



<p><strong>Benefits:</strong></p>



<ul class="wp-block-list">
<li>For customers: Better response times.</li>



<li>For staff: Better work load management.</li>
</ul>



<p><strong>Risks:</strong></p>



<ul class="wp-block-list">
<li>Low: Customer frustration (Ending up with the wrong team/agent. But those can still manually correctly re-assign it.)</li>



<li>Low: Staff frustration.</li>
</ul>



<p><strong>Options in iTop:</strong></p>



<ul class="wp-block-list">
<li>Pro extension: <a href="https://jeffreybostoen.be/itop-extension-auto-assigner/" data-type="page" data-id="563">Auto Assigner</a></li>
</ul>
</div>



<div class="wp-block-group is-layout-constrained wp-block-group-is-layout-constrained">
<h2 class="wp-block-heading">Chatbots / virtual assistants</h2>



<p>The difference between a chatbot and a virtual assistant, is that a virtual assistant can perform more complex actions and try to offer a more personalized form of assistance. For convenience, I&#8217;m only using the term &#8220;chatbot&#8221; in this section.</p>



<p><strong>What:</strong> Customers can chat with a system to get replies to their questions. During this flow, additional information can be automatically obtained already; and if needed, it can be passed on to a human who will take over the chat conversation or who will reach out later in an alternative manner.</p>



<p><strong>Benefits:</strong> </p>



<ul class="wp-block-list">
<li>For customers: Instant replies, 24/7. This is something smaller or local business usually can&#8217;t offer; as it requires working in shifts.</li>



<li>For staff: 
<ul class="wp-block-list">
<li>Very basic questions get deflected.</li>



<li>Additional info can already be collected, such as for example log files, specific contact info, initial troubleshooting steps, &#8230;</li>
</ul>
</li>
</ul>



<p><strong>Goals:</strong></p>



<ul class="wp-block-list">
<li> Improve customer satisfaction. </li>



<li>Reduce work load.</li>
</ul>



<p><strong>Risks:</strong> </p>



<ul class="wp-block-list">
<li>High: Customer frustration.</li>



<li>Medium: Customer alienation. Depending on the services you offer, people appreciate two things: fast responses/solutions; and a personal experience &#8211; which becomes even more important when there is no quick fix.</li>
</ul>



<p><strong>Challenge:</strong><br>The chatbot must be mature enough to provide accurate replies. The system may face challenges in understanding what the customer means. It may fail to comprehend the context, the actual language, cultural nuances, &#8230;</p>



<p>Context is crucial when you want to provide customers a decent level of support. As a support agent, you might have a better grasp of what the customer struggled with in the past, what their environment looks like, what sensitivities there may be</p>



<p>Surely you&#8217;ve experienced this form of online chat, where you enter a question and the chatbot asks &#8220;did you mean X?&#8221; &#8211; which is not what you meant at all. An inadequate chatbot can already lead to more customer frustration. Customers may give up already and be very displeased. But most likely, they&#8217;ll end up with a (human) support agent. While their original issue might already have caused for some friction, their frustration might have become worse. This would lead to an immediate impact on the first interactions with the support agent who takes over at some point.</p>



<p>So the question could be: How many wrong guesses (if any) are acceptable?</p>



<p>Nowadays, depending on the type of customer service, customers may also be looking for a <strong>much more personalized</strong> experience.</p>



<p><strong>Options in iTop:</strong> None natively available.</p>
</div>



<div class="wp-block-group is-layout-constrained wp-block-group-is-layout-constrained">
<h2 class="wp-block-heading">Language translation (machine translation)</h2>



<p><strong>What:</strong> We all know Google Translate or Deepl nowadays. This is only important for organizations who offer services to customers who speak different languages.</p>



<p>But A.I. could assist with language translation in both directions: the agent gets a translation from the customer&#8217;s inquiry; and the customer receives a translated version of the agent&#8217;s response.</p>



<p><strong>Goals:</strong></p>



<ul class="wp-block-list">
<li>Improve customer satisfaction.</li>
</ul>



<p><strong>Benefits:</strong></p>



<ul class="wp-block-list">
<li>For customers and staff: They understand each other.</li>



<li>For customers: A more personalized experience.</li>
</ul>



<p><strong>Risks:</strong></p>



<ul class="wp-block-list">
<li>Medium: Customer frustration.</li>



<li>Medium: Staff frustration.</li>
</ul>



<p><strong>Challenge:</strong></p>



<p>Translations may be incorrect. The more context there is, the better modern translation systems are. Imagine an organization which uses one service desk tool for all their departments. A ticket saying nothing more than &#8220;The window is stuck&#8221;: is this a computer issue; or is there actually a window in a building which can&#8217;t be opened?</p>



<p>Also, some parts should perhaps not be translated at all; or could be translated incorrectly. Imagine you are a vendor of software, and have localized versions of it for your end users. Will the translation algorithm used by the service desk provide the same output? If for example the agent sends a reply to a customer: Go to Settings &gt; Federation; will it translate this as &#8216;Instellingen &gt; Federatie&#8217; or &#8216;Instellingen &gt; Vereniging&#8217; in Dutch (which may be different from what your localized software looks like).</p>



<p><strong>Options in iTop:</strong> None natively available. If you&#8217;re interested in having this developed, please reach out.</p>
</div>



<div class="wp-block-group is-layout-constrained wp-block-group-is-layout-constrained">
<h2 class="wp-block-heading">Language suggestions</h2>



<p><strong>What:</strong> There are also systems available now which can adjust the tone of a message. The tone may depend a lot on the customer you&#8217;re writing to, or on the organization&#8217;s policy. Common techniques are rephrasing, elaborating, shortening, translating / synonyms, and grammar correction.</p>



<p>Do you want to send short bullet point responses to customers, or lengthy paragraphs? Are you going for an informal or formal style?</p>



<p>It will also help your agents (or systems) to send responses with less spelling or grammar mistakes.</p>



<p><strong>Goals:</strong></p>



<ul class="wp-block-list">
<li>Improve customer experience.</li>



<li>Improve brand experience.</li>
</ul>



<p><strong>Benefits:</strong></p>



<ul class="wp-block-list">
<li>Offer a <strong>personalized experience</strong> to the customer (for instance: a lengthy formal reply vs. short informal reply).</li>



<li>Offer a <strong>unified</strong> <strong>brand experience</strong> to the customer, where all communication sent by staff is in a unified tone.</li>
</ul>



<p><strong>Risks:</strong></p>



<ul class="wp-block-list">
<li>Low. Customer frustration. (Approached in the wrong personalized or brand style, but most people are quite easy-going here.).</li>
</ul>



<p><strong>Options in iTop:</strong> None natively available. If you&#8217;re interested in having this developed, please reach out.</p>
</div>



<div class="wp-block-group is-layout-constrained wp-block-group-is-layout-constrained">
<h2 class="wp-block-heading">Quick summary</h2>



<p><strong>What: </strong>The A.I. system summarizes the entire conversation and actions which were taken.</p>



<p><strong>Benefits:</strong></p>



<ul class="wp-block-list">
<li>For customers: Better response times.</li>



<li>For staff: Better overview.</li>
</ul>



<p><strong>Goals:</strong></p>



<ul class="wp-block-list">
<li>Improve customer satisfaction. </li>



<li>Reduce work load.</li>
</ul>



<p><strong>Risks:</strong></p>



<ul class="wp-block-list">
<li>Medium: Customer frustration.</li>



<li>Medium: Staff frustration.</li>
</ul>



<p><strong>Challenge:</strong></p>



<p>The summary should be accurate. It should contain all the really relevant information. Agents will be frustrated if crucial info is missing in this summary (as the goal of a summary is to avoid needing to go through an entire history). Customers will become frustrated if agents ask for information again which they already provided, but wasn&#8217;t part of the summary.</p>



<p><strong>Options in iTop:</strong> None natively available. If you&#8217;re interested in having this developed, please reach out.</p>
</div>



<div class="wp-block-group is-layout-constrained wp-block-group-is-layout-constrained">
<h2 class="wp-block-heading">Predictive analysis</h2>



<p><strong>What:</strong> A.I. can identify common issues. We already discussed how it could also offer suggestions. This reduces the need for manual intervention. It can also help customers solve potential issues before there&#8217;s any form of escalation.</p>



<p>In some situations, it may also be possible to use historical data to predict when there will be more new cases. As a human, we may have some experience (new software releases, holiday periods, knowing which regions your customers are mostly situated in, &#8230;). A.I. can be a tool to support this; or to make predictions based on some event or pattern that you didn&#8217;t even consider yet.</p>



<p><strong>Benefits:</strong></p>



<ul class="wp-block-list">
<li>For customers: Less resources lost with issues.</li>



<li>For staff: Better planning.</li>
</ul>



<p><strong>Goals:</strong></p>



<ul class="wp-block-list">
<li>Improve customer satisfaction. </li>



<li>Reduce work load.</li>
</ul>



<p><strong>Risks:</strong></p>



<ul class="wp-block-list">
<li>Low: Customer frustration.</li>



<li>Low: Staff frustration.</li>
</ul>



<p><strong>Options in iTop:</strong> None natively available. If you&#8217;re interested in having this developed, please reach out.<br><br>What is available though, is the <a href="https://jeffreybostoen.be/itop-extension-report-generator/">Report Generator</a> . Reports can be created from historical data, for example to see during which hours or in which months or on which days most cases were raised.</p>
</div>



<div class="wp-block-group is-layout-constrained wp-block-group-is-layout-constrained">
<h2 class="wp-block-heading">Sentiment analysis</h2>



<p><strong>What:</strong> The sentiment or emotional tone used by a customer, can be derived from their request. Based on indicators such as the use of certain words or phrases or emojis, it&#8217;s possible to determine whether a customer had a positive or negative feeling while writing certain responses.</p>



<p>Like the above, it could be a very simple mechanism; or it could be powered by a machine learning algorithm.</p>



<p><strong>Goals:</strong></p>



<ul class="wp-block-list">
<li>Improve customer satisfaction.</li>
</ul>



<p><strong>Benefits:</strong></p>



<ul class="wp-block-list">
<li>For customers and staff: The customer experience can be influenced by this in many ways (see the challenge description below).</li>
</ul>



<p><strong>Risks:</strong></p>



<ul class="wp-block-list">
<li>High: Customer frustration.</li>



<li>High: Staff frustration.</li>
</ul>



<p><strong>Challenge:</strong></p>



<p>Just like humans, also more advanced A.I. systems may struggle to correctly identify a sentiment. Especially when sarcasm is involved. For example, what if a simple system analyzed these sentences: &#8220;The customer support is really great <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f644.png" alt="🙄" class="wp-smiley" style="height: 1em; max-height: 1em;" />&#8221;, &#8220;Thank you very much for wasting my time&#8221;. Or what if it does not consider the context, such as in &#8220;Your competitor had really terrible support compared to you guys&#8221;.</p>



<p>Assuming the agent A.I. system correctly identifies the sentiment, what actions will be taken? </p>



<p>Will a more personalized support be offered? And what does it mean? Will a human agent reach out, or will the A.I. system or employee adjust their tone?</p>



<p>Will the case be handled with priority? If so, you could stimulate customers to <strong>act</strong> upset to jump to the front of the line. This could lead to a lot of frustration within a support team, as the customer may keep up this act and be difficult to co-operate with for the agent. What if a really nice customer actually has an urgent issue going on but isn&#8217;t as pressing, while this one gets priority instead?</p>



<p>In my personal experience, this could be helpful information; but a real person should verify how relevant and accurate this analysis is. It&#8217;s best value is probably in following up relationships with customers outside of support cases.</p>



<p><strong>Options in iTop:</strong> None natively available. If you&#8217;re interested in having this developed, please reach out.</p>
</div>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-wide"/>


<ol class="wp-block-footnotes"><li id="221475a6-5ffe-49eb-a7a7-4a59e6f85e05"><a href="https://en.wikipedia.org/wiki/Artificial_intelligence">Artificial intelligence &#8211; Wikipedia</a> <a href="#221475a6-5ffe-49eb-a7a7-4a59e6f85e05-link" aria-label="Jump to footnote reference 1"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/21a9.png" alt="↩" class="wp-smiley" style="height: 1em; max-height: 1em;" />︎</a></li><li id="159df89c-c13c-4102-9633-b37c8804ce65"><a href="https://en.wikipedia.org/wiki/Machine_learning">Machine learning &#8211; Wikipedia</a> <a href="#159df89c-c13c-4102-9633-b37c8804ce65-link" aria-label="Jump to footnote reference 2"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/21a9.png" alt="↩" class="wp-smiley" style="height: 1em; max-height: 1em;" />︎</a></li></ol>]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Generating custom subCA certificate on a non-domain joined Microsoft Windows Server</title>
		<link>https://jeffreybostoen.be/generating-custom-subca-certificate-on-a-non-domain-joined-microsoft-windows-server/</link>
		
		<dc:creator><![CDATA[Jeffrey Bostoen]]></dc:creator>
		<pubDate>Sun, 07 Jan 2024 18:31:45 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://jeffreybostoen.be/?p=878</guid>

					<description><![CDATA[In some scenarios, you want to generate a custom subCA certificate which can actually issue (smart card) certificates for users in a domain. It gets more complex however when the Certificate Authority is a Microsoft Windows Server which is not joined to the (same) Microsoft Windows domain. You might see similar behavior: The workaround is [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>In some scenarios, you want to generate a custom subCA certificate which can actually issue (smart card) certificates for users in a domain. It gets more complex however when the Certificate Authority is a Microsoft Windows Server which is not joined to the (same) Microsoft Windows domain.</p>



<p>You might see similar behavior:</p>



<ul class="wp-block-list">
<li>While other export options are available, the option to export as Personal Information Exchange &#8211; PKCS #12 (.pfx) is greyed out. (Machine was not domain joined).</li>



<li>In the Certificate Manager in Microsoft Windows, there is no small &#8220;key&#8221; visible in the certificate&#8217;s icon.</li>
</ul>



<p></p>



<p>The workaround is to create a  .inf file for the certificate request. The instructions below were tested on a Microsoft Windows Server 2022 DataCenter at the time of writing.</p>



<p><strong>Inf file:</strong></p>



<pre class="wp-block-code"><code>&#91;Version]

Signature="$Windows NT$"

&#91;NewRequest]

Subject = "CN=SomeSubCA"
Exportable = True
MachineKeySet = True
KeyLength = 4096
KeyUsage = "CERT_KEY_CERT_SIGN_KEY_USAGE | CERT_DIGITAL_SIGNATURE_KEY_USAGE | CERT_CRL_SIGN_KEY_USAGE"
KeyUsageProperty = "NCRYPT_ALLOW_SIGNING_FLAG"

&#91;Extensions]

2.5.29.15 = AwIBhg==
2.5.29.19 = "{text}ca=1&amp;pathlength=0"
Critical = 2.5.29.15</code></pre>



<p></p>



<p><br>The next issue you might run into, is that the generated certificate can not be exported due to &#8220;old key usage&#8221;.</p>



<p><strong>Now, to make sure &#8220;KeyUsage&#8221; can be marked as critical, enable this flag:</strong></p>



<pre class="wp-block-code"><code><code>PS C:\users\jeffrey_bostoen\Desktop> &amp; certutil.exe -setreg policy\EditFlags <strong>+</strong>EDITF_ADDOLDKEYUSAGE</code></code></pre>



<p></p>



<p><strong>To restore the original value:</strong><br></p>



<pre class="wp-block-code"><code><code>PS C:\users\jeffrey_bostoen\Desktop&gt; &amp; certutil.exe -setreg policy\EditFlags -EDITF_ADDOLDKEYUSAGE</code></code></pre>



<p><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Forget &#8220;+&#8221; in front of EDITF_ADDOLDKEYUSAGE and the updated value will just contain one single flag, as you can see below.</strong></p>



<pre class="wp-block-code"><code>PS C:\users\jeffrey_bostoen\Desktop> &amp; certutil.exe -setreg policy\EditFlags EDITF_ADDOLDKEYUSAGE
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration\JB-WIN-STANDALO-CA\PolicyModules\CertificateAuthority_MicrosoftDefault.Policy\EditFlags:

Old Value:
  EditFlags REG_DWORD = 83ee (33774)
    EDITF_REQUESTEXTENSIONLIST -- 2
    EDITF_DISABLEEXTENSIONLIST -- 4
    EDITF_ADDOLDKEYUSAGE -- 8
    EDITF_ATTRIBUTEENDDATE -- 20 (32)
    EDITF_BASICCONSTRAINTSCRITICAL -- 40 (64)
    EDITF_BASICCONSTRAINTSCA -- 80 (128)
    EDITF_ENABLEAKIKEYID -- 100 (256)
    EDITF_ATTRIBUTECA -- 200 (512)
    EDITF_ATTRIBUTEEKU -- 8000 (32768)

New Value:
  EditFlags REG_DWORD = 8
    EDITF_ADDOLDKEYUSAGE -- 8
CertUtil: -setreg command completed successfully.
The CertSvc service may need to be restarted for changes to take effect.</code></pre>



<p>Afterwards, reset the defaults:</p>



<pre class="wp-block-code"><code><code>PS C:\users\jeffrey_bostoen\Desktop> &amp; certutil.exe -setreg policy\EditFlags 83ee
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration\JB-WIN-STANDALO-CA\PolicyModules\CertificateAuthority_MicrosoftDefault.Policy\EditFlags:</code>
Old Value:
  EditFlags REG_DWORD = 0
CertUtil: -setreg command FAILED: 0x8007000d (WIN32: 13 ERROR_INVALID_DATA)
CertUtil: The data is invalid.

PS C:\users\jeffrey_bostoen\Desktop> &amp; certutil.exe -setreg policy\EditFlags 0x83ee
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration\JB-WIN-STANDALO-CA\PolicyModules\CertificateAuthority_MicrosoftDefault.Policy\EditFlags:

Old Value:
  EditFlags REG_DWORD = 0

New Value:
  EditFlags REG_DWORD = 83ee (33774)
    EDITF_REQUESTEXTENSIONLIST -- 2
    EDITF_DISABLEEXTENSIONLIST -- 4
    EDITF_ADDOLDKEYUSAGE -- 8
    EDITF_ATTRIBUTEENDDATE -- 20 (32)
    EDITF_BASICCONSTRAINTSCRITICAL -- 40 (64)
    EDITF_BASICCONSTRAINTSCA -- 80 (128)
    EDITF_ENABLEAKIKEYID -- 100 (256)
    EDITF_ATTRIBUTECA -- 200 (512)
    EDITF_ATTRIBUTEEKU -- 8000 (32768)
CertUtil: -setreg command completed successfully.
The CertSvc service may need to be restarted for changes to take effect.




</code></pre>



<p>For troubleshooting purposes, this is what I&#8217;m looking at to know that it was enabled:</p>



<p><em>EditFlags&nbsp;&nbsp;&nbsp; REG_DWORD = 83ee (33774)</em></p>



<p>33774 indicates that all of the values listed underneath were enabled, including this line:</p>



<p><em>EDITF_ADDOLDKEYUSAGE &#8212; 8</em></p>



<p>Source:&nbsp;<a href="https://social.technet.microsoft.com/Forums/windowsserver/en-US/8f089f6c-8667-4835-a660-0b2a00b647a2/how-to-make-key-extension-critical-in-adcs-issued-ca-certificates">How to make key extension critical in ADCS issued CA certificates (microsoft.com)</a></p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
