Jekyll2023-09-25T21:54:45+02:00https://blog.benstein.nl/feed.xmlChristian’s BlogStories of /dev/null
Christian BensteinPutting low profile back in to ec11 encoders2023-08-18T00:00:00+02:002023-08-18T00:00:00+02:00https://blog.benstein.nl/posts/snipping-ec11s-for-that-low-low<p>One of the greatest joys of having a 3d printer is finding problems (that are not really problems) and fixing them by prototyping your own solution. Designing a model, printing it and testing it through multiple iterations can quickly swallow up a weekend’s worth of project allowance, but the results are worth it. Or at least, that’s what I tell myself when I forget to do the dishes because I’m recalibrating the printing bed for the third time today.</p>
<p>Earlier this year, I built a Lily 58 from <a href="https://splitkb.com">SplitKB</a> → <a href="https://splitkb.com/collections/keyboard-kits/products/aurora-lily58">Autota Lily58</a>. I built it with choc switches to keep it low profile, and I wanted to include 2 encoders on this board that still had push functionality. For this reason, I opted for EC11 encoders instead of the more low-profile EC12 encoders. The only main downside of this is that even the shortest <a href="/assets/images/ec11encoder/high_boii.jpeg">EC11 encoder sticks out like a sore thumb</a> and adding a big knob on top of it only made it worse.</p>
<p>Time for a cut!</p>
<h1 id="designing-the-knob">Designing the knob</h1>
<p>I started by learning how to create a knob in Fusion 360. I had never created anything from scratch before, so I was lucky that there was an <a href="https://www.youtube.com/watch?v=L-BHRdHFmdg">easy-to-follow tutorial on YouTube</a>. I also learned how to <a href="https://www.youtube.com/watch?v=CXqHa3tIPiA">add a knurling pattern to the knob</a>. With this, I created the first version of my knob:</p>
<p><img src="/assets/images/ec11encoder/first_design.png" alt="First design of knob" />
<em>Get it here https://thangs.com/mythangs/file/920412</em></p>
<p>But this was not the Low Low I was hoping for. I previously looked for hours on AliExpress for low profile encoders, but the lowest I could find was still around 10mm. So! Back to designing. I started out by designing a different version of the knob. Some open, some with dimples:</p>
<p><img src="/assets/images/ec11encoder/prototypes.jpeg" alt="Prototypes" />
<em>Prototypes of knobs</em></p>
<p>Eventually I created a open version of the knop. With the idea of gluing a cover on top. But then I realized that was stupid, so I redesigned it. The nice thing about the open version is that it can be used as a cutting helper.</p>
<h1 id="installing-and-trimming">Installing and trimming</h1>
<ol>
<li>Print the knob(s), get the model at <a href="https://thangs.com/mythangs/file/920416?source=All+Files&activeBottomTab=files">Thangs</a></li>
<li>Print the cutting help model. It’s called “Rotary Encoder Low Profile - Base.stl” and is part of the model at <a href="https://thangs.com/mythangs/file/920416?source=All+Files&activeBottomTab=files">Thangs</a></li>
<li>Install the helper <a href="/assets/images/ec11encoder/cuttting_helper.jpeg">Helper Installed</a></li>
<li>Snip a de snip (be carefull, this part can fly away. Cut it in a bag or wear glasses when trimming the encoder) <a href="/assets/images/ec11encoder/snipping.jpeg">Snipping</a></li>
<li>Carefully remove the cutting help from the stem by wiggeliging it loose</li>
<li>File down the cut <a href="/assets/images/ec11encoder/filling_down.jpeg">File</a></li>
<li>Install the new knob. This might take a few tries but with some patiance it should fit snug on the encoder. <a href="/assets/images/ec11encoder/low_encoder_view.jpeg">Installing knob</a></li>
</ol>
<h1 id="results">Results</h1>
<p><img src="/assets/images/ec11encoder/low_encoder.jpeg" alt="Results" /></p>
<p>I’m really happy with how this turned out. My keyboard is “more low profile” now, and it’s easier to transport without the knob sticking out. This project also gave me some 3D modeling experience, of which I had none. Onward and upward!</p>Christian BensteinOne of the greatest joys of having a 3d printer is finding problems (that are not really problems) and fixing them by prototyping your own solution. Designing a model, printing it and testing it through multiple iterations can quickly swallow up a weekend’s worth of project allowance, but the results are worth it. Or at least, that’s what I tell myself when I forget to do the dishes because I’m recalibrating the printing bed for the third time today. Earlier this year, I built a Lily 58 from SplitKB → Autota Lily58. I built it with choc switches to keep it low profile, and I wanted to include 2 encoders on this board that still had push functionality. For this reason, I opted for EC11 encoders instead of the more low-profile EC12 encoders. The only main downside of this is that even the shortest EC11 encoder sticks out like a sore thumb and adding a big knob on top of it only made it worse. Time for a cut! Designing the knob I started by learning how to create a knob in Fusion 360. I had never created anything from scratch before, so I was lucky that there was an easy-to-follow tutorial on YouTube. I also learned how to add a knurling pattern to the knob. With this, I created the first version of my knob: Get it here https://thangs.com/mythangs/file/920412 But this was not the Low Low I was hoping for. I previously looked for hours on AliExpress for low profile encoders, but the lowest I could find was still around 10mm. So! Back to designing. I started out by designing a different version of the knob. Some open, some with dimples: Prototypes of knobs Eventually I created a open version of the knop. With the idea of gluing a cover on top. But then I realized that was stupid, so I redesigned it. The nice thing about the open version is that it can be used as a cutting helper. Installing and trimming Print the knob(s), get the model at Thangs Print the cutting help model. It’s called “Rotary Encoder Low Profile - Base.stl” and is part of the model at Thangs Install the helper Helper Installed Snip a de snip (be carefull, this part can fly away. Cut it in a bag or wear glasses when trimming the encoder) Snipping Carefully remove the cutting help from the stem by wiggeliging it loose File down the cut File Install the new knob. This might take a few tries but with some patiance it should fit snug on the encoder. Installing knob Results I’m really happy with how this turned out. My keyboard is “more low profile” now, and it’s easier to transport without the knob sticking out. This project also gave me some 3D modeling experience, of which I had none. Onward and upward!Using inline Datview with Obsidian2023-06-26T00:00:00+02:002023-06-26T00:00:00+02:00https://blog.benstein.nl/posts/using-dataview-inline-with-obsidian<p>I have been using <a href="https://obsidian.md">Obsidian</a> for close to three (3) years now. Like many other users I was drawn to Obsidian for its simple core (everything is a Markdown file <sup id="fnref:Markdown" role="doc-noteref"><a href="#fn:Markdown" class="footnote" rel="footnote">1</a></sup>), it’s extreme extensibility and it’s extreme speed. I use obsidian to store information, concept ideas and even write this blog post you’re reading</p>
<p><img src="/assets/images/20230626162254.png" alt="" />
<em>Writing about Obsidian using Obsidian</em></p>
<p>In this post I’m sharing some in-line examples of one of the most popular Obsidian plugin’s called <a href="https://github.com/blacksmithgu/obsidian-dataview">Dataview</a>. Dataview allow you to Treat your Obsidian Vault as a database which you can query from. Provides a JavaScript API and pipeline-based query language for filtering, sorting, and extracting data from Markdown pages.</p>
<p>A popular use for Dataview is to generate tables of pages. You can see a lot of examples of this on the <a href="https://blacksmithgu.github.io/obsidian-dataview/">Dataview overview page</a>. In this blog, we will be looking at inline queries. By this, I mean query’s that go in-between text to render counts or other things. I had great difficulty finding examples of this at first, and when I got it working I vowed to share some examples for anyone out there. But first! The why</p>
<h1 id="why-in-line-queries">Why in-line queries</h1>
<p>I use in-line queries to get count’s of pages or links. I use this to show me how many Items I have left in my inbox folder, to count the number of files I have containing the tag <code class="language-plaintext highlighter-rouge">#meeting</code> and to count how many books I read.</p>
<h1 id="setting-up">Setting up</h1>
<p>The things you will need to do are:</p>
<ol>
<li>Install Obsidian</li>
<li>Install the Dataview plugin</li>
<li>Enable Inline Queries in the Dataview settings</li>
</ol>
<p>To enable inline Queries go to the settings for Dataview. Here, you can enable Inline Queries. My queries didn’t work for half an hour before I found this option, so I’m hoping to spare anyone this issue:</p>
<p><img src="/assets/images/screenshot-7vhmB4Fo.png" alt="" />
<em>Inline Queries settings in the Obsidian Dataview plugin</em></p>
<h1 id="queries">Queries</h1>
<p>Now to the good stuff. Here are the queries I use:</p>
<h2 id="count-all-files-in-a-folder">Count all files in a folder</h2>
<p>This is a great one if you want to know how many files are in a folder regardless of any other metadata. It also doesn’t require any custom Metadata <sup id="fnref:Metadata" role="doc-noteref"><a href="#fn:Metadata" class="footnote" rel="footnote">2</a></sup>. We do this with:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">dv</span><span class="p">.</span><span class="nx">pages</span><span class="p">(</span><span class="dl">'</span><span class="s1">"0 INBOX"</span><span class="dl">'</span><span class="p">).</span><span class="nx">length</span><span class="s2">`
</span></code></pre></div></div>
<p>In you markdown this could look like:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">You</span> <span class="nx">have</span> <span class="s2">`$= dv.pages('"0 INBOX"').length`</span> <span class="k">in</span> <span class="nx">your</span> <span class="dl">"</span><span class="s2">0 INBOX</span><span class="dl">"</span> <span class="nx">folder</span>
</code></pre></div></div>
<p>Which will render:</p>
<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code>You have 10 in your "0 INBOX" folder
</code></pre></div></div>
<h2 id="count-all-files-with-links-to">Count all files with links to</h2>
<p>This works on files that link to another file in Obsidian. In this case, we will be using the link to <code class="language-plaintext highlighter-rouge">[[blog]]</code> for an example. To get a count of all files that link to the <code class="language-plaintext highlighter-rouge">[[blog]]</code> we can use:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">dv</span><span class="p">.</span><span class="nx">pages</span><span class="p">(</span><span class="dl">"</span><span class="s2">[[blog]]</span><span class="dl">"</span><span class="p">).</span><span class="nx">length</span>
</code></pre></div></div>
<p>Again, to use it in markdown:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s2">`$= dv.pages("[[blog]]").length`</span>
</code></pre></div></div>
<h2 id="count-all-files-with-tag">Count all files with tag</h2>
<p>Same for tags:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">dv</span><span class="p">.</span><span class="nx">pages</span><span class="p">(</span><span class="dl">"</span><span class="s2">#blogpost).length
</span></code></pre></div></div>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s2">`$= dv.pages("#blogpost).length`</span>
</code></pre></div></div>
<h1 id="combining-queries">Combining queries</h1>
<p>Now it doesn’t stop there. You can easily combine and filter queries. Here are some examples:</p>
<h2 id="count-pages-with-link-and-tag">Count pages with link and tag</h2>
<p>This gets you all pages linking to <code class="language-plaintext highlighter-rouge">[[blog]]</code> with the tag <code class="language-plaintext highlighter-rouge">#blogpost</code></p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s2">`$= dv.pages("[[blog]] and #blogpost")length`</span>
</code></pre></div></div>
<h2 id="filtering-on-metadata">Filtering on metadata</h2>
<p>You can also add filters on top. For example, let’s get all pages linking to <code class="language-plaintext highlighter-rouge">[[blog]]</code> with the tag <code class="language-plaintext highlighter-rouge">#blogpost</code> put that also have a custom metadata fields called <code class="language-plaintext highlighter-rouge">status</code>. Metdata is outside the scope of this post, but you can <a href="https://help.obsidian.md/Editing+and+formatting/Metadata">read about it here</a>. In example, we will use it to indicate the state of the article:</p>
<p>Let’s get a count of all <code class="language-plaintext highlighter-rouge">draft</code>’s:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s2">`$= dv.pages("[[blog]] and #blogpost").where(p => p.status == "draft").length`</span>
</code></pre></div></div>
<p>And get all our published blog’s:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s2">`$= dv.pages("[[blog]] and #blogpost").where(p => p.status == "published").length`</span>
</code></pre></div></div>
<h1 id="sum">SUM</h1>
<p>Inline queries can be used to insert dynamic data in to your Obsidian notes. It’s works great in cases where you want to get a count of things in your vault. You could use this to count the books you read, the number of journal entries written or the amount of notes you made regarding a topic.</p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:Markdown" role="doc-endnote">
<p>https://daringfireball.net/projects/markdown/ <a href="#fnref:Markdown" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:Metadata" role="doc-endnote">
<p>https://help.obsidian.md/Editing+and+formatting/Metadata <a href="#fnref:Metadata" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>Christian BensteinI have been using Obsidian for close to three (3) years now. Like many other users I was drawn to Obsidian for its simple core (everything is a Markdown file 1), it’s extreme extensibility and it’s extreme speed. I use obsidian to store information, concept ideas and even write this blog post you’re reading Writing about Obsidian using Obsidian In this post I’m sharing some in-line examples of one of the most popular Obsidian plugin’s called Dataview. Dataview allow you to Treat your Obsidian Vault as a database which you can query from. Provides a JavaScript API and pipeline-based query language for filtering, sorting, and extracting data from Markdown pages. A popular use for Dataview is to generate tables of pages. You can see a lot of examples of this on the Dataview overview page. In this blog, we will be looking at inline queries. By this, I mean query’s that go in-between text to render counts or other things. I had great difficulty finding examples of this at first, and when I got it working I vowed to share some examples for anyone out there. But first! The why Why in-line queries I use in-line queries to get count’s of pages or links. I use this to show me how many Items I have left in my inbox folder, to count the number of files I have containing the tag #meeting and to count how many books I read. Setting up The things you will need to do are: Install Obsidian Install the Dataview plugin Enable Inline Queries in the Dataview settings To enable inline Queries go to the settings for Dataview. Here, you can enable Inline Queries. My queries didn’t work for half an hour before I found this option, so I’m hoping to spare anyone this issue: Inline Queries settings in the Obsidian Dataview plugin Queries Now to the good stuff. Here are the queries I use: Count all files in a folder This is a great one if you want to know how many files are in a folder regardless of any other metadata. It also doesn’t require any custom Metadata 2. We do this with: dv.pages('"0 INBOX"').length` In you markdown this could look like: You have `$= dv.pages('"0 INBOX"').length` in your "0 INBOX" folder Which will render: You have 10 in your "0 INBOX" folder Count all files with links to This works on files that link to another file in Obsidian. In this case, we will be using the link to [[blog]] for an example. To get a count of all files that link to the [[blog]] we can use: dv.pages("[[blog]]").length Again, to use it in markdown: `$= dv.pages("[[blog]]").length` Count all files with tag Same for tags: dv.pages("#blogpost).length `$= dv.pages("#blogpost).length` Combining queries Now it doesn’t stop there. You can easily combine and filter queries. Here are some examples: Count pages with link and tag This gets you all pages linking to [[blog]] with the tag #blogpost `$= dv.pages("[[blog]] and #blogpost")length` Filtering on metadata You can also add filters on top. For example, let’s get all pages linking to [[blog]] with the tag #blogpost put that also have a custom metadata fields called status. Metdata is outside the scope of this post, but you can read about it here. In example, we will use it to indicate the state of the article: Let’s get a count of all draft’s: `$= dv.pages("[[blog]] and #blogpost").where(p => p.status == "draft").length` And get all our published blog’s: `$= dv.pages("[[blog]] and #blogpost").where(p => p.status == "published").length` SUM Inline queries can be used to insert dynamic data in to your Obsidian notes. It’s works great in cases where you want to get a count of things in your vault. You could use this to count the books you read, the number of journal entries written or the amount of notes you made regarding a topic. https://daringfireball.net/projects/markdown/ ↩ https://help.obsidian.md/Editing+and+formatting/Metadata ↩Shortening shell paths with hash2023-04-10T00:00:00+02:002023-04-10T00:00:00+02:00https://blog.benstein.nl/posts/shortening-shell-paths-with-hash<p>If you’re like most developers, DevOps engineers, or IT professionals, you probably spend a lot of time in your <a href="https://xkcd.com/686/">shell of choice</a>. And if you’re like the typical user, you’re probably always deep in the filesystem. This can get confusing quickly, and there’s only so much space available to display your working path (unless you invest in an ultrawide monitor, but even those have limits).</p>
<p>So in this post we will be looking at <code class="language-plaintext highlighter-rouge">hash</code> to shorten some of those paths’s.</p>
<h1 id="so-whats-this-all-about">So what’s this all about</h1>
<p>In this example, we will use the path <code class="language-plaintext highlighter-rouge">/home/user/baseDir</code> as an example with 3 subdir’s setup. Let’s assume we have this setup:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>baseDir
∟ Work
∟ Personal
∟ Downloads
</code></pre></div></div>
<p>Now, if you’re in any of these folders, your shell path will look like this:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>/home/user/baseDir/Work
</code></pre></div></div>
<p>That’s pretty readable, but if you’re working in a project, and then a directory, and the subfolder of that directory things might quickly look like this:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>/home/user/baseDir/Work/BigImportantProject/FolderWithPictures/PicturesOfParty
</code></pre></div></div>
<p>Now that’s a long dir.</p>
<p>We will use <code class="language-plaintext highlighter-rouge">hash</code> to set up more readable paths for these three directories. Then, I will demonstrate some additional options this enables.</p>
<h2 id="what-is-hash-used-for">What is hash used for?</h2>
<p>Well!</p>
<blockquote>
<p>The /usr/bin/hash utility affects the way the current shell environment remembers the locations of utilities found. Depending on the arguments specified, it adds utility locations to its list of remembered locations or it purges the contents of the list. When no arguments are specified, it reports on the contents of the list. The <code class="language-plaintext highlighter-rouge">-r</code> option causes the shell to forget all remembered locations.</p>
</blockquote>
<blockquote>
<p>Utilities provided as built-ins to the shell are not reported by hash.</p>
</blockquote>
<p>– StackExchange2023 <sup id="fnref:Stack" role="doc-noteref"><a href="#fn:Stack" class="footnote" rel="footnote">1</a></sup></p>
<p>Thanks <a href="https://unix.stackexchange.com/questions/86012/what-is-the-purpose-of-the-hash-command">StackExchange</a>!</p>
<h1 id="shortening-paths">Shortening paths</h1>
<p><code class="language-plaintext highlighter-rouge">hash</code> can be used to shorten path’s with the following command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">hash</span> <span class="nt">-d</span> <span class="nv">ALIAS</span><span class="o">=</span><span class="s2">"/path/to/folder"</span>
</code></pre></div></div>
<p>For example, we will shorten the path <code class="language-plaintext highlighter-rouge">/home/user/baseDir/Work</code> to <code class="language-plaintext highlighter-rouge">Work</code> like this:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">hash</span> <span class="nt">-d</span> <span class="nv">Work</span><span class="o">=</span><span class="s2">"/home/user/baseDir/Work"</span>
</code></pre></div></div>
<p>This will turn your shell path from <code class="language-plaintext highlighter-rouge">/home/user/baseDir/Work</code> to <code class="language-plaintext highlighter-rouge">~Work</code> when you are in that directory.
That’s really great. You can now easily see that you’re in your “Work” folder, giving you a better understanding of your system’s structure.</p>
<p><img src="/assets/images/hash/screenshot-ll2B6SA8.gif" alt="" />
<em>Example of shortening shell path when entering directory</em></p>
<p>But wait! Theres more!</p>
<h2 id="permanence">Permanence</h2>
<p>Aliases are only valid for the current session, so if you want to make them permanent you can add them to your <code class="language-plaintext highlighter-rouge">.bashrc</code> or <code class="language-plaintext highlighter-rouge">.zshrc</code>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># /home/user/.zshrc</span>
....
<span class="nb">hash</span> <span class="nt">-d</span> <span class="nv">Work</span><span class="o">=</span><span class="s2">"/home/user/baseDir/Work"</span>
<span class="nb">hash</span> <span class="nt">-d</span> <span class="nv">Personal</span><span class="o">=</span><span class="s2">"/home/user/baseDir/Personal"</span>
<span class="nb">hash</span> <span class="nt">-d</span> <span class="nv">Downloads</span><span class="o">=</span><span class="s2">"/home/user/baseDir/Downloads"</span>
</code></pre></div></div>
<h2 id="bonus-one-navigation">Bonus one: Navigation</h2>
<p>Set up aliases to easily navigate to them. For example:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cd</span> ~Work
</code></pre></div></div>
<p><img src="/assets/images/hash/screenshot-dk2k2KnR.gif" alt="" />
<em>Moving to hashed dir from anywhere by using ~ALIAS</em></p>
<p>You can also use tab completion to easily navigate to sub folders.</p>
<h2 id="bonus-two-easy-copy">Bonus two: Easy copy</h2>
<p>ANother thing <code class="language-plaintext highlighter-rouge">hash</code> aliasses enable is easy copying of files. For example, you can copy a file from any place to your Work directory with:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cp </span>FILE ~Work
</code></pre></div></div>
<p>This makes it easy to copy files from <code class="language-plaintext highlighter-rouge">~Downloads</code> to <code class="language-plaintext highlighter-rouge">~Work</code>. You could for example move a downloaded pdf easliy like:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cp</span> ~Downloads/TheExpanseRapport.pdf ~Work
</code></pre></div></div>
<p>And this works from anywhere on your system.</p>
<p><img src="/assets/images/hash/screenshot-qGJRnAtH.gif" alt="" />
<em>Example of moving a file to a folder that is hashed</em></p>
<h1 id="conclusion">Conclusion</h1>
<p>We can use <code class="language-plaintext highlighter-rouge">hash</code> to make our terminal usage more clear and organized. If you are like me and work with a pretty rigged folder structure like PARA <sup id="fnref:PARA" role="doc-noteref"><a href="#fn:PARA" class="footnote" rel="footnote">2</a></sup> then you know how nice it is to be able to jump to Projects, Areas and Resources quickly.</p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:Stack" role="doc-endnote">
<p>https://unix.stackexchange.com/questions/86012/what-is-the-purpose-of-the-hash-command <a href="#fnref:Stack" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:PARA" role="doc-endnote">
<p>https://www.buildingasecondbrain.com/para <a href="#fnref:PARA" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>Christian BensteinIf you’re like most developers, DevOps engineers, or IT professionals, you probably spend a lot of time in your shell of choice. And if you’re like the typical user, you’re probably always deep in the filesystem. This can get confusing quickly, and there’s only so much space available to display your working path (unless you invest in an ultrawide monitor, but even those have limits). So in this post we will be looking at hash to shorten some of those paths’s. So what’s this all about In this example, we will use the path /home/user/baseDir as an example with 3 subdir’s setup. Let’s assume we have this setup: baseDir ∟ Work ∟ Personal ∟ Downloads Now, if you’re in any of these folders, your shell path will look like this: $ /home/user/baseDir/Work That’s pretty readable, but if you’re working in a project, and then a directory, and the subfolder of that directory things might quickly look like this: $ /home/user/baseDir/Work/BigImportantProject/FolderWithPictures/PicturesOfParty Now that’s a long dir. We will use hash to set up more readable paths for these three directories. Then, I will demonstrate some additional options this enables. What is hash used for? Well! The /usr/bin/hash utility affects the way the current shell environment remembers the locations of utilities found. Depending on the arguments specified, it adds utility locations to its list of remembered locations or it purges the contents of the list. When no arguments are specified, it reports on the contents of the list. The -r option causes the shell to forget all remembered locations. Utilities provided as built-ins to the shell are not reported by hash. – StackExchange2023 1 Thanks StackExchange! Shortening paths hash can be used to shorten path’s with the following command: hash -d ALIAS="/path/to/folder" For example, we will shorten the path /home/user/baseDir/Work to Work like this: hash -d Work="/home/user/baseDir/Work" This will turn your shell path from /home/user/baseDir/Work to ~Work when you are in that directory. That’s really great. You can now easily see that you’re in your “Work” folder, giving you a better understanding of your system’s structure. Example of shortening shell path when entering directory But wait! Theres more! Permanence Aliases are only valid for the current session, so if you want to make them permanent you can add them to your .bashrc or .zshrc: # /home/user/.zshrc .... hash -d Work="/home/user/baseDir/Work" hash -d Personal="/home/user/baseDir/Personal" hash -d Downloads="/home/user/baseDir/Downloads" Bonus one: Navigation Set up aliases to easily navigate to them. For example: $ cd ~Work Moving to hashed dir from anywhere by using ~ALIAS You can also use tab completion to easily navigate to sub folders. Bonus two: Easy copy ANother thing hash aliasses enable is easy copying of files. For example, you can copy a file from any place to your Work directory with: $ cp FILE ~Work This makes it easy to copy files from ~Downloads to ~Work. You could for example move a downloaded pdf easliy like: $ cp ~Downloads/TheExpanseRapport.pdf ~Work And this works from anywhere on your system. Example of moving a file to a folder that is hashed Conclusion We can use hash to make our terminal usage more clear and organized. If you are like me and work with a pretty rigged folder structure like PARA 2 then you know how nice it is to be able to jump to Projects, Areas and Resources quickly. https://unix.stackexchange.com/questions/86012/what-is-the-purpose-of-the-hash-command ↩ https://www.buildingasecondbrain.com/para ↩Using combos on ZMK2022-12-10T00:00:00+01:002022-12-10T00:00:00+01:00https://blog.benstein.nl/posts/using-zmk-combos<p>One of the greatest things (maybe even the greatest thing) about mechanical keyboards that provide custom firmware support (like QMK and ZMK) is the endless support for customization. I’m still tweaking my keymaps for different keyboards, but I recently started getting in to “Combos”:</p>
<blockquote>
<p>Combo keys are a way to combine multiple key presses to output a different key. For example, you can hit the Q and W keys on your keyboard to output escape.
– https://zmk.dev/docs/features/combos</p>
</blockquote>
<h1 id="the-problem">The problem</h1>
<p>Having found my “ideal” number of key’s being around 56 keys I had some issues with finding the correct place to place the <kbd>ENTER</kbd> key. I tried putting it on a dedicated key but I wanted it on my thumb cluster and I wanted it on a place where I could not press it accidentally. I tried working for a while with the <kbd>ENTER</kbd> being on a second layer but the combination of switching layer and then finding enter was too much of a hassle.</p>
<p>For reference, currently I’m using a <a href="https://fingerpunch.xyz/product-tag/rock-on/">Fingerpunch rock on</a> and a <a href="https://store.montsinger.net/products/rebound-s">Montsinger Rebound-S</a> with <a href="https://zmk.dev">ZMK</a>. I also created a combo on my <a href="https://www.zsa.io/moonlander/">Moonlander</a> but to that we have to <a href="https://github.com/zsa/qmk_firmware/">set up a custom QMK firmware</a>.</p>
<p>The current physical layout/keyboard I’m working with:</p>
<p><img src="/assets/images/rock-on.jpeg" alt="Fingerpunch rock on" />
<em>Fingerpunch rock on</em></p>
<h1 id="enter-combos">Enter combos</h1>
<p>To get around having <kbd>ENTER</kbd> on the main layer, not pressing it on accident and having it be accessible to my thumbs I came up with the idea of using a combo (also called a chord) to trigger <kbd>ENTER</kbd>. In this way I could combo 2 keys and send the keystroke.</p>
<h2 id="deciding-on-the-keys">Deciding on the keys</h2>
<p>I decided on using the innermost thumb keys for sending the <kbd>ENTER</kbd> combo. They are easy to find, and I use them a lot for other operations.
Because ZMK uses the number of the physical key we need to ‘count’ out which keys these are on the layout. All keys count towards the position you want to combo even if the keys are not in use. As explained on the ZMK documentation:</p>
<blockquote>
<p>Key positions are numbered like the keys in your keymap, starting at 0. So, if the first key in your keymap is Q, this key is in position 0. The next key (possibly W) will have position 1, etcetera.
– https://zmk.dev/docs/features/combos</p>
</blockquote>
<p>In my case it came to a combo of key 60 and 61.</p>
<p>See the following example:</p>
<p><img src="/assets/images/rock-on-count.jpeg" alt="Fingerpunch rock on" />
<em>Fingerpunch rock on with explanation</em></p>
<h2 id="setting-up-the-combo">Setting up the combo</h2>
<p>After finding out we wanted to create a combo on key 60 and 61 we can add the combo in our keymap:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">/ {</span>
<span class="s">combos {</span>
<span class="s">compatible = "zmk,combos";</span>
<span class="s">combo_esc {</span>
<span class="s">timeout-ms = <50>;</span>
<span class="s">key-positions = <60 61>;</span>
<span class="s">bindings = <&kp ENTER>;</span>
<span class="s">};</span>
<span class="s">};</span>
<span class="err">}</span><span class="s">;</span>
</code></pre></div></div>
<p>With this setting we simply tell ZMK that if key 60 and 61 are pressed together witing a duration of <code class="language-plaintext highlighter-rouge">50ms</code> it should send the key press <kbd>ENTER</kbd></p>
<h1 id="closing">Closing</h1>
<p>Setting up combo’s on ZMK is really easy (once you know your key positions). It can add some awesome functionality like this <kbd>ENTER</kbd> key press. You can also use it to relocate some popular modifiers to you home row. You could for example, on a QWERTY layout, combo <kbd>d</kbd> and <kbd>f</kbd> to send <kbd>ESC</kbd>. Making it possible to send <kbd>Escape</kbd> without leaving you home row.</p>Christian BensteinOne of the greatest things (maybe even the greatest thing) about mechanical keyboards that provide custom firmware support (like QMK and ZMK) is the endless support for customization. I’m still tweaking my keymaps for different keyboards, but I recently started getting in to “Combos”: Combo keys are a way to combine multiple key presses to output a different key. For example, you can hit the Q and W keys on your keyboard to output escape. – https://zmk.dev/docs/features/combos The problem Having found my “ideal” number of key’s being around 56 keys I had some issues with finding the correct place to place the ENTER key. I tried putting it on a dedicated key but I wanted it on my thumb cluster and I wanted it on a place where I could not press it accidentally. I tried working for a while with the ENTER being on a second layer but the combination of switching layer and then finding enter was too much of a hassle. For reference, currently I’m using a Fingerpunch rock on and a Montsinger Rebound-S with ZMK. I also created a combo on my Moonlander but to that we have to set up a custom QMK firmware. The current physical layout/keyboard I’m working with: Fingerpunch rock on Enter combos To get around having ENTER on the main layer, not pressing it on accident and having it be accessible to my thumbs I came up with the idea of using a combo (also called a chord) to trigger ENTER. In this way I could combo 2 keys and send the keystroke. Deciding on the keys I decided on using the innermost thumb keys for sending the ENTER combo. They are easy to find, and I use them a lot for other operations. Because ZMK uses the number of the physical key we need to ‘count’ out which keys these are on the layout. All keys count towards the position you want to combo even if the keys are not in use. As explained on the ZMK documentation: Key positions are numbered like the keys in your keymap, starting at 0. So, if the first key in your keymap is Q, this key is in position 0. The next key (possibly W) will have position 1, etcetera. – https://zmk.dev/docs/features/combos In my case it came to a combo of key 60 and 61. See the following example: Fingerpunch rock on with explanation Setting up the combo After finding out we wanted to create a combo on key 60 and 61 we can add the combo in our keymap: / { combos { compatible = "zmk,combos"; combo_esc { timeout-ms = <50>; key-positions = <60 61>; bindings = <&kp ENTER>; }; }; }; With this setting we simply tell ZMK that if key 60 and 61 are pressed together witing a duration of 50ms it should send the key press ENTER Closing Setting up combo’s on ZMK is really easy (once you know your key positions). It can add some awesome functionality like this ENTER key press. You can also use it to relocate some popular modifiers to you home row. You could for example, on a QWERTY layout, combo d and f to send ESC. Making it possible to send Escape without leaving you home row.hey for load testing http applications2022-10-25T00:00:00+02:002022-10-25T00:00:00+02:00https://blog.benstein.nl/posts/hey-for-loadtesting-http<p><strong>short</strong></p>
<p><code class="language-plaintext highlighter-rouge">hey</code> is a HTTP load tester CLI tool to benchmark HTTP requests to a HTTP end-point. Get it at <a href="https://github.com/rakyll/hey">GitHub</a></p>
<hr />
<p>Today we will be taking a look at a small utility called <code class="language-plaintext highlighter-rouge">hey</code>. You can use <code class="language-plaintext highlighter-rouge">hey</code> to load test HTTP applications or generate load for a web application. This comes in handy when you want to simulate use or check what your app does when it receives 1000s of requests.</p>
<p class="error"><strong>Warning</strong>: Using a load test on website that you do not own or have permission to test can result in you being banned or blocked</p>
<h1 id="installation">Installation</h1>
<p>Installation on macOS with <code class="language-plaintext highlighter-rouge">brew</code> is really easy, just run:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew <span class="nb">install </span>hey
</code></pre></div></div>
<p>For other installation options, check out: <a href="https://github.com/rakyll/hey#installation">Hey Installation</a></p>
<h1 id="usage">Usage</h1>
<p>So, we are going to run this against a local docker container, just to be sure that we don’t mess with anyone’s website.</p>
<h2 id="setting-up-a-container">Setting up a container</h2>
<p>This is pretty straight forward. Start Docker and run the following command to start it in the background:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>docker run <span class="nt">--name</span> webserver <span class="nt">-p</span> 8080:80 <span class="nt">-d</span> nginx
</code></pre></div></div>
<p>To make sure it’s running we can check using the <code class="language-plaintext highlighter-rouge">docker ps</code> command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c2c829348c89 nginx <span class="s2">"/docker-entrypoint.…"</span> 3 seconds ago Up 3 seconds 0.0.0.0:8080->80/tcp, :::8080->80/tcp webserver
</code></pre></div></div>
<p>Now you can also start a <code class="language-plaintext highlighter-rouge">tail</code> on the container to see your load test in action:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker logs <span class="nt">-f</span> webserver
</code></pre></div></div>
<p>Example output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>172.17.0.1 - - <span class="o">[</span>25/Oct/2022:15:12:18 +0000] <span class="s2">"GET / HTTP/1.1"</span> 200 615 <span class="s2">"-"</span> <span class="s2">"hey/0.0.1"</span> <span class="s2">"-"</span>
172.17.0.1 - - <span class="o">[</span>25/Oct/2022:15:12:18 +0000] <span class="s2">"GET / HTTP/1.1"</span> 200 615 <span class="s2">"-"</span> <span class="s2">"hey/0.0.1"</span> <span class="s2">"-"</span>
172.17.0.1 - - <span class="o">[</span>25/Oct/2022:15:12:18 +0000] <span class="s2">"GET / HTTP/1.1"</span> 200 615 <span class="s2">"-"</span> <span class="s2">"hey/0.0.1"</span> <span class="s2">"-"</span>
</code></pre></div></div>
<h2 id="using-hey">Using hey</h2>
<p>So let’s get testing. <code class="language-plaintext highlighter-rouge">hey</code> supports some great arguments like <code class="language-plaintext highlighter-rouge">-n</code> (number) the amount of requests to send, <code class="language-plaintext highlighter-rouge">-c</code> (concurrently) for the number of workers to run concurrently and <code class="language-plaintext highlighter-rouge">-z</code> (Duration) to perform a test over <em>x</em> time.
All options can be found on <a href="https://github.com/rakyll/hey#usage">Hey Usage</a></p>
<h2 id="examples">Examples</h2>
<p>I’ll be showing some common usages</p>
<h3 id="example-1-one-hundred-100-requests">Example 1: One hundred (100) requests</h3>
<p>Let’s fire up some requests. First up, 100 request using 5 concurrent workers:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>hey <span class="nt">-n</span> 100 <span class="nt">-c</span> 5 http://localhost:8080
</code></pre></div></div>
<p>Results:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">Summary</span><span class="pi">:</span>
<span class="na">Total</span><span class="pi">:</span><span class="err"> </span><span class="s">0.0448 secs</span>
<span class="na">Slowest</span><span class="pi">:</span><span class="err"> </span><span class="s">0.0089 secs</span>
<span class="na">Fastest</span><span class="pi">:</span><span class="err"> </span><span class="s">0.0010 secs</span>
<span class="na">Average</span><span class="pi">:</span><span class="err"> </span><span class="s">0.0022 secs</span>
<span class="s">Requests/sec</span><span class="err">: </span><span class="m">2231.3041</span>
<span class="na">Total data</span><span class="pi">:</span><span class="err"> </span><span class="s">61500 bytes</span>
<span class="s">Size/request</span><span class="err">: </span><span class="s">615 bytes</span>
<span class="na">Response time histogram</span><span class="pi">:</span>
<span class="s">0.001 [1]</span><span class="err"> </span><span class="s">|■</span>
<span class="s">0.002 [34]</span><span class="err"> </span><span class="s">|■■■■■■■■■■■■■■■■■■■■■■■</span>
<span class="s">0.003 [60]</span><span class="err"> </span><span class="s">|■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■</span>
<span class="s">0.003 [0]</span><span class="err"> </span><span class="s">|</span>
<span class="s">0.004 [0]</span><span class="err"> </span><span class="s">|</span>
<span class="s">0.005 [0]</span><span class="err"> </span><span class="s">|</span>
<span class="s">0.006 [0]</span><span class="err"> </span><span class="s">|</span>
<span class="s">0.007 [0]</span><span class="err"> </span><span class="s">|</span>
<span class="s">0.007 [0]</span><span class="err"> </span><span class="s">|</span>
<span class="s">0.008 [0]</span><span class="err"> </span><span class="s">|</span>
<span class="s">0.009 [5]</span><span class="err"> </span><span class="s">|■■■</span>
<span class="na">Latency distribution</span><span class="pi">:</span>
<span class="s">10% in 0.0016 secs</span>
<span class="s">25% in 0.0017 secs</span>
<span class="s">50% in 0.0019 secs</span>
<span class="s">75% in 0.0021 secs</span>
<span class="s">90% in 0.0022 secs</span>
<span class="s">95% in 0.0087 secs</span>
<span class="s">99% in 0.0089 secs</span>
<span class="s">Details (average, fastest, slowest)</span><span class="pi">:</span>
<span class="s">DNS+dialup</span><span class="err">: </span><span class="s">0.0002 secs, 0.0010 secs, 0.0089 secs</span>
<span class="s">DNS-lookup</span><span class="err">: </span><span class="s">0.0002 secs, 0.0000 secs, 0.0041 secs</span>
<span class="s">req write</span><span class="err">: </span><span class="s">0.0000 secs, 0.0000 secs, 0.0002 secs</span>
<span class="s">resp wait</span><span class="err">: </span><span class="s">0.0019 secs, 0.0009 secs, 0.0038 secs</span>
<span class="s">resp read</span><span class="err">: </span><span class="s">0.0000 secs, 0.0000 secs, 0.0002 secs</span>
<span class="na">Status code distribution</span><span class="pi">:</span>
<span class="pi">[</span><span class="nv">200</span><span class="pi">]</span><span class="err"> </span><span class="s">100 responses</span>
</code></pre></div></div>
<h3 id="example-2-five-5-requests-with-csv-output">Example 2: Five (5) requests with csv output</h3>
<p>Dump the results to <code class="language-plaintext highlighter-rouge">csv</code> output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>hey <span class="nt">-n</span> 1 <span class="nt">-c</span> 1 <span class="nt">-o</span> csv http://localhost:8080
</code></pre></div></div>
<p>Result:</p>
<pre><code class="language-csv">response-time,DNS+dialup,DNS,Request-write,Response-delay,Response-read,status-code,offset
0.0087,0.0047,0.0042,0.0001,0.0019,0.0001,200,0.0016
</code></pre>
<h3 id="example-3-thirty-30-seconds-of-requests">Example 3: Thirty (30) seconds of requests</h3>
<p>Now we are going to use the duration (<code class="language-plaintext highlighter-rouge">-z</code>) option:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>hey <span class="nt">-z</span> 30s http://localhost:8080
</code></pre></div></div>
<p>Results:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Summary:
Total: 30.0082 secs
Slowest: 0.1231 secs
Fastest: 0.0010 secs
Average: 0.0070 secs
Requests/sec: 7096.5514
Total data: 130967325 bytes
Size/request: 615 bytes
Response <span class="nb">time </span>histogram:
0.001 <span class="o">[</span>1] |
0.013 <span class="o">[</span>199692] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
0.025 <span class="o">[</span>12165] |■■
0.038 <span class="o">[</span>868] |
0.050 <span class="o">[</span>159] |
0.062 <span class="o">[</span>47] |
0.074 <span class="o">[</span>11] |
0.086 <span class="o">[</span>9] |
0.099 <span class="o">[</span>1] |
0.111 <span class="o">[</span>1] |
0.123 <span class="o">[</span>1] |
Latency distribution:
10% <span class="k">in </span>0.0035 secs
25% <span class="k">in </span>0.0045 secs
50% <span class="k">in </span>0.0061 secs
75% <span class="k">in </span>0.0085 secs
90% <span class="k">in </span>0.0115 secs
95% <span class="k">in </span>0.0140 secs
99% <span class="k">in </span>0.0212 secs
Details <span class="o">(</span>average, fastest, slowest<span class="o">)</span>:
DNS+dialup: 0.0000 secs, 0.0010 secs, 0.1231 secs
DNS-lookup: 0.0000 secs, 0.0000 secs, 0.0069 secs
req write: 0.0000 secs, 0.0000 secs, 0.0025 secs
resp <span class="nb">wait</span>: 0.0069 secs, 0.0009 secs, 0.1230 secs
resp <span class="nb">read</span>: 0.0001 secs, 0.0000 secs, 0.0431 secs
Status code distribution:
<span class="o">[</span>200] 212955 responses
</code></pre></div></div>
<h3 id="limiting-requests">Limiting requests</h3>
<p>Now, as you can see, we just fired off 212955 requests. That might be a bit of overkill. To prevent this, we can use the <code class="language-plaintext highlighter-rouge">-q</code> (Rate limit) and <code class="language-plaintext highlighter-rouge">-c</code> option. We will perform a load test of 5 seconds and use <code class="language-plaintext highlighter-rouge">-c</code> to limit ourselves to 2 workers, and we will set a limit of 5 request per second per worker:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>hey <span class="nt">-z</span> 5s <span class="nt">-c</span> 2 <span class="nt">-q</span> 5 http://localhost:8080
</code></pre></div></div>
<p>This results in 50 requests being made:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...
Status code distribution:
<span class="o">[</span>200] 50 responses
</code></pre></div></div>
<h1 id="wrapping-up">Wrapping up</h1>
<p>So that’s <code class="language-plaintext highlighter-rouge">hey</code>. A super simple HTTP load tester. You can use <code class="language-plaintext highlighter-rouge">hey</code> to do some advanced things like posting code, testing authentication and other things. Take a look at the <a href="https://github.com/rakyll/hey">readme</a></p>Christian Bensteinshort hey is a HTTP load tester CLI tool to benchmark HTTP requests to a HTTP end-point. Get it at GitHub Today we will be taking a look at a small utility called hey. You can use hey to load test HTTP applications or generate load for a web application. This comes in handy when you want to simulate use or check what your app does when it receives 1000s of requests. Warning: Using a load test on website that you do not own or have permission to test can result in you being banned or blocked Installation Installation on macOS with brew is really easy, just run: brew install hey For other installation options, check out: Hey Installation Usage So, we are going to run this against a local docker container, just to be sure that we don’t mess with anyone’s website. Setting up a container This is pretty straight forward. Start Docker and run the following command to start it in the background: $ docker run --name webserver -p 8080:80 -d nginx To make sure it’s running we can check using the docker ps command: $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c2c829348c89 nginx "/docker-entrypoint.…" 3 seconds ago Up 3 seconds 0.0.0.0:8080->80/tcp, :::8080->80/tcp webserver Now you can also start a tail on the container to see your load test in action: docker logs -f webserver Example output: 172.17.0.1 - - [25/Oct/2022:15:12:18 +0000] "GET / HTTP/1.1" 200 615 "-" "hey/0.0.1" "-" 172.17.0.1 - - [25/Oct/2022:15:12:18 +0000] "GET / HTTP/1.1" 200 615 "-" "hey/0.0.1" "-" 172.17.0.1 - - [25/Oct/2022:15:12:18 +0000] "GET / HTTP/1.1" 200 615 "-" "hey/0.0.1" "-" Using hey So let’s get testing. hey supports some great arguments like -n (number) the amount of requests to send, -c (concurrently) for the number of workers to run concurrently and -z (Duration) to perform a test over x time. All options can be found on Hey Usage Examples I’ll be showing some common usages Example 1: One hundred (100) requests Let’s fire up some requests. First up, 100 request using 5 concurrent workers: $ hey -n 100 -c 5 http://localhost:8080 Results: Summary: Total: 0.0448 secs Slowest: 0.0089 secs Fastest: 0.0010 secs Average: 0.0022 secs Requests/sec: 2231.3041 Total data: 61500 bytes Size/request: 615 bytes Response time histogram: 0.001 [1] |■ 0.002 [34] |■■■■■■■■■■■■■■■■■■■■■■■ 0.003 [60] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 0.003 [0] | 0.004 [0] | 0.005 [0] | 0.006 [0] | 0.007 [0] | 0.007 [0] | 0.008 [0] | 0.009 [5] |■■■ Latency distribution: 10% in 0.0016 secs 25% in 0.0017 secs 50% in 0.0019 secs 75% in 0.0021 secs 90% in 0.0022 secs 95% in 0.0087 secs 99% in 0.0089 secs Details (average, fastest, slowest): DNS+dialup: 0.0002 secs, 0.0010 secs, 0.0089 secs DNS-lookup: 0.0002 secs, 0.0000 secs, 0.0041 secs req write: 0.0000 secs, 0.0000 secs, 0.0002 secs resp wait: 0.0019 secs, 0.0009 secs, 0.0038 secs resp read: 0.0000 secs, 0.0000 secs, 0.0002 secs Status code distribution: [200] 100 responses Example 2: Five (5) requests with csv output Dump the results to csv output: $ hey -n 1 -c 1 -o csv http://localhost:8080 Result: response-time,DNS+dialup,DNS,Request-write,Response-delay,Response-read,status-code,offset 0.0087,0.0047,0.0042,0.0001,0.0019,0.0001,200,0.0016 Example 3: Thirty (30) seconds of requests Now we are going to use the duration (-z) option: $ hey -z 30s http://localhost:8080 Results: Summary: Total: 30.0082 secs Slowest: 0.1231 secs Fastest: 0.0010 secs Average: 0.0070 secs Requests/sec: 7096.5514 Total data: 130967325 bytes Size/request: 615 bytes Response time histogram: 0.001 [1] | 0.013 [199692] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 0.025 [12165] |■■ 0.038 [868] | 0.050 [159] | 0.062 [47] | 0.074 [11] | 0.086 [9] | 0.099 [1] | 0.111 [1] | 0.123 [1] | Latency distribution: 10% in 0.0035 secs 25% in 0.0045 secs 50% in 0.0061 secs 75% in 0.0085 secs 90% in 0.0115 secs 95% in 0.0140 secs 99% in 0.0212 secs Details (average, fastest, slowest): DNS+dialup: 0.0000 secs, 0.0010 secs, 0.1231 secs DNS-lookup: 0.0000 secs, 0.0000 secs, 0.0069 secs req write: 0.0000 secs, 0.0000 secs, 0.0025 secs resp wait: 0.0069 secs, 0.0009 secs, 0.1230 secs resp read: 0.0001 secs, 0.0000 secs, 0.0431 secs Status code distribution: [200] 212955 responses Limiting requests Now, as you can see, we just fired off 212955 requests. That might be a bit of overkill. To prevent this, we can use the -q (Rate limit) and -c option. We will perform a load test of 5 seconds and use -c to limit ourselves to 2 workers, and we will set a limit of 5 request per second per worker: $ hey -z 5s -c 2 -q 5 http://localhost:8080 This results in 50 requests being made: ... Status code distribution: [200] 50 responses Wrapping up So that’s hey. A super simple HTTP load tester. You can use hey to do some advanced things like posting code, testing authentication and other things. Take a look at the readmeBuild-log: Montsinger Rebound-S2022-08-27T00:00:00+02:002022-08-27T00:00:00+02:00https://blog.benstein.nl/posts/building-the-montsinger-rebound-s<p><a href="https://store.montsinger.net/products/rebound-s">The Rebound-S by Montsinger</a> is a 60% case compatible ~40% keyboard ortholinear keyboard with an ergonomically-friendly 7° typing angle. It differs from the <a href="https://store.montsinger.net/products/rebound">Rebound</a> because it has a staggered contour. The Rebound’s have some great customisation options like spot for a dedicated for an EC12 encoder and some extra keys located between the two halves of the keyboard. The PCB also allows you to build the keyboard with a choice of switches (Choc or MX style) and microcontrollers letting you choose which controller to use.
For my build I went with the <a href="https://nicekeyboards.com/nice-nano/">nice!nano</a>for wireless support. I also decided not to install all keys (because I don’t use them all) and to use M2 screws and bolts to get a very low profile keyboard.
This is more of a build log than a complete guide. You can use it to check if you’d like to build your own keyboard but it’s by no means complete. The official buildguide is over at http://docs.montsinger.net</p>
<h1 id="parts-list">Parts-list</h1>
<p>To build this keyboard you will need the following, from the Montsinger website you will need:</p>
<ul>
<li>Montsinger Rebound S PCB</li>
<li>Montsinger Rebound S Plate</li>
<li>Montsinger Small adapter board</li>
</ul>
<p>You will need to provide the following parts yourself:</p>
<ul>
<li>A nice!nano controller</li>
<li>A small 100mah battery (or bigger)</li>
<li>Choc or MX switches (builders choice, the Rebound supports both)</li>
<li>Keycaps of choice</li>
</ul>
<p>Optional parts:</p>
<ul>
<li>A EC12 encoder and knob</li>
<li>Mill Max Low Profile Sockets, highly recommended, to make the nice!nano removable</li>
</ul>
<h1 id="installing-the-nicenano">Installing the nice!nano</h1>
<p>I decided to start by installing the controller because this keyboard does not have native hot swappable switch support. By installing the controller first I could test the PCB first before adding switches save myself a lot of troubleshooting after installing the switches. Because I opted to use a nice!nano I had to use the Small adapter bord (provided by Montsinger) to install my controller on to the keyboard.
Many instructions have been written on how to socket your controller, so I’m just going to refer to the <a href="https://docs.splitkb.com/hc/en-us/articles/360011263059">How do I socket a microcontroller?</a> by splitkb.com.</p>
<p>Setting up the pins on the sockets on the adapter board:</p>
<p><img src="/assets/images/montsinger/20220901214744.jpg" alt="" />
<em>Preparing the socket pins on the milmax</em></p>
<p>Preparing the nice!nano for soldering. I put some isolation tape in between the nice!nano and the sockets to prevent the solder from leaking in to the socket. This way, the controller is easily removable. A lot of people on Discord let me know they don’t do this and never had any issues, but the last two controllers I socketed did not want to come off after, so better safer than sorrow.</p>
<p><img src="/assets/images/montsinger/20220901214243.jpg" alt="" />
<em>Adding a piece of electrical tape prevents the solder from seeping trough</em></p>
<p>Nice and soldered:</p>
<p><img src="/assets/images/montsinger/20220901214446.jpg" alt="" /></p>
<p>And un socketed:</p>
<p><img src="/assets/images/montsinger/20220901214513.jpg" alt="" /></p>
<p>Then just solder the sockets to the adapter board, put it on the PCB and connect the rest:</p>
<p><img src="/assets/images/montsinger/20220901214919.jpg" alt="" />
<em>The Nice!Nano on the Montsinger Adapter</em></p>
<h1 id="installing-switches">Installing switches</h1>
<p>I started by getting the EC12 in place that I wanted to use:</p>
<p><img src="/assets/images/montsinger/20220901214956.jpg" alt="" />
<em>The EC12 encoder on the center with the pins bent in</em></p>
<p>I noticed the plate did not want to align nicely on the PCB after installing the EC12 encoder. To fix this I bent the pins of the encoder inwards after soldering it and I filed down the top plate a bit:</p>
<p><img src="/assets/images/montsinger/IMG_6850.jpg" alt="" /></p>
<p>Then I simply added the switches to the top plate:</p>
<p><img src="/assets/images/montsinger/IMG_6845.jpg" alt="" /></p>
<p>And aligned it on the PCB:</p>
<p><img src="/assets/images/montsinger/IMG_6848.jpg" alt="" /></p>
<p>Then just flip it over and solder on the switches.</p>
<h1 id="adding-the-battery">Adding the battery</h1>
<p>Initially I thought about adding the battery between the plates. But after putting on all the switches I needed I saw that there was a nice space left on the left and right corners of my board. So I decided to but a battery there and route the cables troug a switch hole. I super glued a small on-off switch on the the underside of the PCB to act as a power-switch.</p>
<p><img src="/assets/images/montsinger/IMG_7285.jpg" alt="" /></p>
<p>With some cramming I got my powercables routed through two connectors on the PCB that are not in use (not sure how safe this is but it works)</p>
<p><img src="/assets/images/montsinger/IMG_7274.jpg" alt="" /></p>
<p>Once on the other side I connected them up to my nice!nano:</p>
<p><img src="/assets/images/montsinger/IMG_7273.jpg" alt="" /></p>
<p><img src="/assets/images/montsinger/20220901220740.jpg" alt="" /></p>
<p>Whith everyting in place I tugged away the cables:</p>
<p><img src="/assets/images/montsinger/IMG_7277.jpg" alt="" /></p>
<h2 id="baseplate-and-keycaps">Baseplate and keycaps</h2>
<p>The last step was to add the baseplate. This is pretty straight foward. To minimize heigt I decided to use short M2 screws with M2 bolts in between as stand off</p>
<p>When everything is put together, it looks like this:</p>
<p><img src="/assets/images/montsinger/IMG_7294.jpg" alt="" /></p>
<h1 id="software-and-layout">Software and layout</h1>
<p>I went with a simpel layout. I cloned the repo from <a href="https://github.com/Gorbataras/zmk-nn-rebound">https://github.com/Gorbataras/zmk-nn-rebound</a> and gave it my own spin over at <a href="https://github.com/KingOfSpades/zmk-nn-rebound">https://github.com/KingOfSpades/zmk-nn-rebound</a>.
Tip, to enable the use of the rotary encoder you will need to enable them in <a href="https://github.com/KingOfSpades/zmk-nn-rebound/blob/master/config/rebound_v4.conf#L4"><code class="language-plaintext highlighter-rouge">config/rebound_v4.conf</code></a>:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Uncomment these two lines to add support for encoders</span>
<span class="s">CONFIG_EC11=y</span>
<span class="s">CONFIG_EC11_TRIGGER_GLOBAL_THREAD=y</span>
</code></pre></div></div>
<p>You can add a binding to the encoder per layer on the keyboard like so:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">sensor-bindings = <&inc_dec_kp C_VOLUME_UP C_VOLUME_DOWN>;</span>
</code></pre></div></div>
<p>An example can be found here: <a href="https://github.com/KingOfSpades/zmk-nn-rebound/blob/master/config/rebound_v4.keymap#L81">https://github.com/KingOfSpades/zmk-nn-rebound/blob/master/config/rebound_v4.keymap#L81</a></p>
<h1 id="closing-thoughts">Closing thoughts</h1>
<p>The Montsinger Rebound S is a fun build with lot’s of custom options. It’s my first choc low profile keyboard and it works great! It’s also pretty affordable to build! I decided to go with an EC12 encoder that was really low-profile. This does remove the “click” feature found on other encoders but I don’t feel like I’m missing allot.</p>Christian BensteinThe Rebound-S by Montsinger is a 60% case compatible ~40% keyboard ortholinear keyboard with an ergonomically-friendly 7° typing angle. It differs from the Rebound because it has a staggered contour. The Rebound’s have some great customisation options like spot for a dedicated for an EC12 encoder and some extra keys located between the two halves of the keyboard. The PCB also allows you to build the keyboard with a choice of switches (Choc or MX style) and microcontrollers letting you choose which controller to use. For my build I went with the nice!nanofor wireless support. I also decided not to install all keys (because I don’t use them all) and to use M2 screws and bolts to get a very low profile keyboard. This is more of a build log than a complete guide. You can use it to check if you’d like to build your own keyboard but it’s by no means complete. The official buildguide is over at http://docs.montsinger.net Parts-list To build this keyboard you will need the following, from the Montsinger website you will need: Montsinger Rebound S PCB Montsinger Rebound S Plate Montsinger Small adapter board You will need to provide the following parts yourself: A nice!nano controller A small 100mah battery (or bigger) Choc or MX switches (builders choice, the Rebound supports both) Keycaps of choice Optional parts: A EC12 encoder and knob Mill Max Low Profile Sockets, highly recommended, to make the nice!nano removable Installing the nice!nano I decided to start by installing the controller because this keyboard does not have native hot swappable switch support. By installing the controller first I could test the PCB first before adding switches save myself a lot of troubleshooting after installing the switches. Because I opted to use a nice!nano I had to use the Small adapter bord (provided by Montsinger) to install my controller on to the keyboard. Many instructions have been written on how to socket your controller, so I’m just going to refer to the How do I socket a microcontroller? by splitkb.com. Setting up the pins on the sockets on the adapter board: Preparing the socket pins on the milmax Preparing the nice!nano for soldering. I put some isolation tape in between the nice!nano and the sockets to prevent the solder from leaking in to the socket. This way, the controller is easily removable. A lot of people on Discord let me know they don’t do this and never had any issues, but the last two controllers I socketed did not want to come off after, so better safer than sorrow. Adding a piece of electrical tape prevents the solder from seeping trough Nice and soldered: And un socketed: Then just solder the sockets to the adapter board, put it on the PCB and connect the rest: The Nice!Nano on the Montsinger Adapter Installing switches I started by getting the EC12 in place that I wanted to use: The EC12 encoder on the center with the pins bent in I noticed the plate did not want to align nicely on the PCB after installing the EC12 encoder. To fix this I bent the pins of the encoder inwards after soldering it and I filed down the top plate a bit: Then I simply added the switches to the top plate: And aligned it on the PCB: Then just flip it over and solder on the switches. Adding the battery Initially I thought about adding the battery between the plates. But after putting on all the switches I needed I saw that there was a nice space left on the left and right corners of my board. So I decided to but a battery there and route the cables troug a switch hole. I super glued a small on-off switch on the the underside of the PCB to act as a power-switch. With some cramming I got my powercables routed through two connectors on the PCB that are not in use (not sure how safe this is but it works) Once on the other side I connected them up to my nice!nano: Whith everyting in place I tugged away the cables: Baseplate and keycaps The last step was to add the baseplate. This is pretty straight foward. To minimize heigt I decided to use short M2 screws with M2 bolts in between as stand off When everything is put together, it looks like this: Software and layout I went with a simpel layout. I cloned the repo from https://github.com/Gorbataras/zmk-nn-rebound and gave it my own spin over at https://github.com/KingOfSpades/zmk-nn-rebound. Tip, to enable the use of the rotary encoder you will need to enable them in config/rebound_v4.conf: # Uncomment these two lines to add support for encoders CONFIG_EC11=y CONFIG_EC11_TRIGGER_GLOBAL_THREAD=y You can add a binding to the encoder per layer on the keyboard like so: sensor-bindings = <&inc_dec_kp C_VOLUME_UP C_VOLUME_DOWN>; An example can be found here: https://github.com/KingOfSpades/zmk-nn-rebound/blob/master/config/rebound_v4.keymap#L81 Closing thoughts The Montsinger Rebound S is a fun build with lot’s of custom options. It’s my first choc low profile keyboard and it works great! It’s also pretty affordable to build! I decided to go with an EC12 encoder that was really low-profile. This does remove the “click” feature found on other encoders but I don’t feel like I’m missing allot.Controlling Ingress with Openshift Network Policy’s2022-02-27T00:00:00+01:002022-02-27T00:00:00+01:00https://blog.benstein.nl/posts/controlling-network-access-in-openshift<p>This blog will go in to the “software defined networking” of <a href="https://www.redhat.com/en/services/training/ex280-red-hat-certified-specialist-in-openshift-administration-exam?section=Objectives">“Configure networking components”</a> objective of the <a href="https://www.redhat.com/en/services/training/ex280-red-hat-certified-specialist-in-openshift-administration-exam?section=Overview">EX280</a> exam from RedHat. In this post we will:</p>
<ul>
<li>Traffic to pods</li>
<li>The types of Network Policy’s we can create</li>
<li>Create a Network Policy based on a application label</li>
</ul>
<blockquote class="prompt-tip">
<p>This post focuses on Ingress (incoming traffic). You can also create Egress policy’s to manage outgoing traffic</p>
</blockquote>
<p>As always. We will be doing all the examples in a CRC (<a href="https://developers.redhat.com/products/codeready-containers/overview">Code Ready Containers</a>) environment.</p>
<h1 id="traffic-to-pods">Traffic to pods</h1>
<p>As explained in my post about <a href="">Services and Routes</a> pods are accessed in the cluster by using services. We can filter traffic to these services using Network Policy’s. These can allow traffic based on different ‘keys’ called identifiers. By default no traffic is blocked to a service and you can not block traffic from a pod to itself. When you add a Network Policy all traffic is blocked by default.
Also, good to keep in mind is that Network Policy’s are cumulative. Meaning they won’t cancel each other out.</p>
<h2 id="types-of-network-policy-identifiers">Types of Network Policy Identifiers</h2>
<p>You can use three (3) ways to block or allow traffic to your Service, you can filter:</p>
<ol>
<li><strong>Pods:</strong> by using a label (podSelector). You can allow traffic from certain pods in your cluster.</li>
<li><strong>Namespace:</strong> by using the label (namespaceSelector) you can allow access from a given namespace in the cluster</li>
<li><strong>IP Blocks:</strong> by using the IP (ipBlock) you can block or allow IPv4 addresses to access a service</li>
</ol>
<h2 id="example-of-a-network-policy">Example of a Network Policy</h2>
<p>A <code class="language-plaintext highlighter-rouge">{networkPolicy}</code> can look like this:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">kind</span><span class="pi">:</span> <span class="s">NetworkPolicy</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">networking.k8s.io/v1</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">allow-from-label</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">podSelector</span><span class="pi">:</span>
<span class="na">matchLabels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">nginx</span>
<span class="na">ingress</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">from</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">podSelector</span><span class="pi">:</span>
<span class="na">matchLabels</span><span class="pi">:</span>
<span class="na">access-to-service</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>
</code></pre></div></div>
<p>In this example:</p>
<ul>
<li>We create a network policy called <code class="language-plaintext highlighter-rouge">allow-from-label</code></li>
<li>It will work on the pods that are labeled as <code class="language-plaintext highlighter-rouge">app: nginx</code></li>
<li>It will allow access to our service if the pod that access our service has a label <code class="language-plaintext highlighter-rouge">access-to-service</code> which is set to <code class="language-plaintext highlighter-rouge">true</code></li>
</ul>
<p>To create a IPBlock type Network Policy we would use:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">kind</span><span class="pi">:</span> <span class="s">NetworkPolicy</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">networking.k8s.io/v1</span>
<span class="s">....</span>
<span class="s">ingress</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">from</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">ipBlock</span><span class="pi">:</span>
<span class="na">cidr</span><span class="pi">:</span> <span class="s">172.17.0.0/16</span>
<span class="na">except</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">172.17.1.0/24</span>
</code></pre></div></div>
<p>To create a namespace type Network Policy we would use:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">kind</span><span class="pi">:</span> <span class="s">NetworkPolicy</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">networking.k8s.io/v1</span>
<span class="s">....</span>
<span class="s">ingress</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">from</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">namespaceSelector</span><span class="pi">:</span>
<span class="na">matchLabels</span><span class="pi">:</span>
<span class="na">project</span><span class="pi">:</span> <span class="s">myproject</span>
</code></pre></div></div>
<h1 id="creating-a-policy">Creating a policy</h1>
<p>To test if we can block traffic to a pod using a Network Policy we will spin up two (2) apps called <code class="language-plaintext highlighter-rouge">server</code> and <code class="language-plaintext highlighter-rouge">client</code> in our namespace called <code class="language-plaintext highlighter-rouge">restricted-network</code>. We will secure access to our <code class="language-plaintext highlighter-rouge">server</code> service by creating a Network Policy called <code class="language-plaintext highlighter-rouge">access-policy</code>:</p>
<p>Creating the project and apps:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc new-project restricted-network
<span class="nv">$ </span>oc new-app <span class="nt">--name</span> client <span class="nt">--image</span> bitnami/nginx
<span class="nv">$ </span>oc new-app <span class="nt">--name</span> server <span class="nt">--image</span> bitnami/nginx
</code></pre></div></div>
<p>Now that we have created the server app we can run a <code class="language-plaintext highlighter-rouge">curl</code> command using <code class="language-plaintext highlighter-rouge">oc exec</code> with our client pod and check if we can connect to it. But first we have to <code class="language-plaintext highlighter-rouge">expose</code> the server:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc expose service/server
<span class="nv">$ </span>oc get route
NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD
server server-restricted-network.apps-crc.testing server 8080 None
</code></pre></div></div>
<p>Check, our target will be the service: <code class="language-plaintext highlighter-rouge">server</code>. Lets <code class="language-plaintext highlighter-rouge">curl</code> it:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc <span class="nb">exec</span> <span class="nt">-it</span> client-76ccdb697d-n2xqp <span class="nt">--</span> curl <span class="nt">-v</span> server:8080 | <span class="nb">grep </span>HTTP
<span class="o">></span> GET / HTTP/1.1
< HTTP/1.1 200 OK
</code></pre></div></div>
<p>Great! We can set up a connection. Now lets see what happens when we create the following network policy:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># allow-from-label.yaml</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">NetworkPolicy</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">networking.k8s.io/v1</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">allow-from-label</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">podSelector</span><span class="pi">:</span>
<span class="na">matchLabels</span><span class="pi">:</span>
<span class="na">policy</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>
<span class="na">ingress</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">from</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">podSelector</span><span class="pi">:</span>
<span class="na">matchLabels</span><span class="pi">:</span>
<span class="na">access-to-service</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>
</code></pre></div></div>
<p>And let’s apply it:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc apply <span class="nt">-f</span> allow-from-label.yaml
networkpolicy.networking.k8s.io/allow-from-label configured
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc get networkpolicies.networking.k8s.io
NAME POD-SELECTOR AGE
allow-from-label <span class="nv">deployment</span><span class="o">=</span>server 5s
</code></pre></div></div>
<p>Now we only need to add a label to our server to link this Network Policy. We will apply the label: <code class="language-plaintext highlighter-rouge">policy: "true"</code>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc label pod server-68ff6d4bfd-prd4w <span class="nv">policy</span><span class="o">=</span><span class="nb">true
</span>pod/server-68ff6d4bfd-prd4w labeled
</code></pre></div></div>
<p>Let’s test our access again with the <code class="language-plaintext highlighter-rouge">-m</code> flag (<code class="language-plaintext highlighter-rouge">--max-time</code>), otherwise we will be waiting a long time:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc <span class="nb">exec</span> <span class="nt">-it</span> client-76ccdb697d-n2xqp <span class="nt">--</span> curl <span class="nt">-v</span> <span class="nt">-m</span> 3 server:8080 | <span class="nb">grep </span>HTTP
<span class="nb">command </span>terminated with <span class="nb">exit </span>code 28
</code></pre></div></div>
<p>No we will add the label <code class="language-plaintext highlighter-rouge">access-to-service: "true"</code> to our pod client and try again:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc label pod client-76ccdb697d-n2xqp access-to-service<span class="o">=</span><span class="nb">true
</span>pod/client-76ccdb697d-n2xqp labeled
<span class="nv">$ </span>oc <span class="nb">exec</span> <span class="nt">-it</span> client-76ccdb697d-n2xqp <span class="nt">--</span> curl <span class="nt">-v</span> server:8080 | <span class="nb">grep </span>HTTP
<span class="o">></span> GET / HTTP/1.1
< HTTP/1.1 200 OK
</code></pre></div></div>
<p>And thats a simple demo of adding a Network Policy, applying it to a pod and granting access to it by adding a label to a pod.</p>
<h1 id="wrapping-up">Wrapping up</h1>
<p>Controlling ingress traffic to services and pods gives you (and your developers) a great way to increase security in the cluster. By segmenting access based on labels or namespaces you can easily isolate important services from the rest of the cluster.</p>
<p>I hope this post has helped you. Check out my other EX280 related content on my <a href="https://blog.benstein.nl/ex280/">EX280 page</a></p>Christian BensteinThis blog will go in to the “software defined networking” of “Configure networking components” objective of the EX280 exam from RedHat. In this post we will: Traffic to pods The types of Network Policy’s we can create Create a Network Policy based on a application label This post focuses on Ingress (incoming traffic). You can also create Egress policy’s to manage outgoing traffic As always. We will be doing all the examples in a CRC (Code Ready Containers) environment. Traffic to pods As explained in my post about Services and Routes pods are accessed in the cluster by using services. We can filter traffic to these services using Network Policy’s. These can allow traffic based on different ‘keys’ called identifiers. By default no traffic is blocked to a service and you can not block traffic from a pod to itself. When you add a Network Policy all traffic is blocked by default. Also, good to keep in mind is that Network Policy’s are cumulative. Meaning they won’t cancel each other out. Types of Network Policy Identifiers You can use three (3) ways to block or allow traffic to your Service, you can filter: Pods: by using a label (podSelector). You can allow traffic from certain pods in your cluster. Namespace: by using the label (namespaceSelector) you can allow access from a given namespace in the cluster IP Blocks: by using the IP (ipBlock) you can block or allow IPv4 addresses to access a service Example of a Network Policy A {networkPolicy} can look like this: kind: NetworkPolicy apiVersion: networking.k8s.io/v1 metadata: name: allow-from-label spec: podSelector: matchLabels: app: nginx ingress: - from: - podSelector: matchLabels: access-to-service: "true" In this example: We create a network policy called allow-from-label It will work on the pods that are labeled as app: nginx It will allow access to our service if the pod that access our service has a label access-to-service which is set to true To create a IPBlock type Network Policy we would use: kind: NetworkPolicy apiVersion: networking.k8s.io/v1 .... ingress: - from: - ipBlock: cidr: 172.17.0.0/16 except: - 172.17.1.0/24 To create a namespace type Network Policy we would use: kind: NetworkPolicy apiVersion: networking.k8s.io/v1 .... ingress: - from: - namespaceSelector: matchLabels: project: myproject Creating a policy To test if we can block traffic to a pod using a Network Policy we will spin up two (2) apps called server and client in our namespace called restricted-network. We will secure access to our server service by creating a Network Policy called access-policy: Creating the project and apps: $ oc new-project restricted-network $ oc new-app --name client --image bitnami/nginx $ oc new-app --name server --image bitnami/nginx Now that we have created the server app we can run a curl command using oc exec with our client pod and check if we can connect to it. But first we have to expose the server: $ oc expose service/server $ oc get route NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD server server-restricted-network.apps-crc.testing server 8080 None Check, our target will be the service: server. Lets curl it: $ oc exec -it client-76ccdb697d-n2xqp -- curl -v server:8080 | grep HTTP > GET / HTTP/1.1 < HTTP/1.1 200 OK Great! We can set up a connection. Now lets see what happens when we create the following network policy: # allow-from-label.yaml kind: NetworkPolicy apiVersion: networking.k8s.io/v1 metadata: name: allow-from-label spec: podSelector: matchLabels: policy: "true" ingress: - from: - podSelector: matchLabels: access-to-service: "true" And let’s apply it: $ oc apply -f allow-from-label.yaml networkpolicy.networking.k8s.io/allow-from-label configured $ oc get networkpolicies.networking.k8s.io NAME POD-SELECTOR AGE allow-from-label deployment=server 5s Now we only need to add a label to our server to link this Network Policy. We will apply the label: policy: "true": $ oc label pod server-68ff6d4bfd-prd4w policy=true pod/server-68ff6d4bfd-prd4w labeled Let’s test our access again with the -m flag (--max-time), otherwise we will be waiting a long time: $ oc exec -it client-76ccdb697d-n2xqp -- curl -v -m 3 server:8080 | grep HTTP command terminated with exit code 28 No we will add the label access-to-service: "true" to our pod client and try again: $ oc label pod client-76ccdb697d-n2xqp access-to-service=true pod/client-76ccdb697d-n2xqp labeled $ oc exec -it client-76ccdb697d-n2xqp -- curl -v server:8080 | grep HTTP > GET / HTTP/1.1 < HTTP/1.1 200 OK And thats a simple demo of adding a Network Policy, applying it to a pod and granting access to it by adding a label to a pod. Wrapping up Controlling ingress traffic to services and pods gives you (and your developers) a great way to increase security in the cluster. By segmenting access based on labels or namespaces you can easily isolate important services from the rest of the cluster. I hope this post has helped you. Check out my other EX280 related content on my EX280 pageConfiguring default Project creation in Openshift2022-02-27T00:00:00+01:002022-02-27T00:00:00+01:00https://blog.benstein.nl/posts/customizing-project-creation-openshift<p>In this blog we will have a look at “Configuring project creation” in an Openshift cluster. We will:</p>
<ul>
<li>Create a Project Template</li>
<li>Add resources like a limit-range to the template</li>
<li>Disable project self-provisioning</li>
</ul>
<p>As always. We will be doing all the examples in a CRC (<a href="https://developers.redhat.com/products/codeready-containers/overview">Code Ready Containers</a>) environment.</p>
<h1 id="project-template">Project template</h1>
<p>When creating a new project in Openshift (a namespace) the API query’s the default template that is in use by the cluster. We can however change this to better suit our needs.
By creating a project template and adding resources to it we can setup new Projects to be in line with our workflow and we can apply limits on creation.</p>
<h1 id="creating-a-template">Creating a template</h1>
<p>Templates are stored in the namespace <code class="language-plaintext highlighter-rouge">openshift-config</code> as <code class="language-plaintext highlighter-rouge">template.template.openshift.io</code> objects. To create a new template we can use the special <code class="language-plaintext highlighter-rouge">oc adm</code> command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc adm create-bootstrap-project-template <span class="nt">-o</span> yaml <span class="o">></span> our-template.yaml
</code></pre></div></div>
<p>This template will include some basic things like:</p>
<ul>
<li>Creating a <code class="language-plaintext highlighter-rouge">admin</code> role-binding to the user that creates the project</li>
<li>Setting up parameters like <code class="language-plaintext highlighter-rouge">PROJECT_REQUESTING_USER</code></li>
</ul>
<h2 id="customizing-our-template">Customizing our template</h2>
<p>Adding custom object is done by adding to the <code class="language-plaintext highlighter-rouge">-objects</code> array. Here we can add object’s like <code class="language-plaintext highlighter-rouge">LimitRange</code>, <code class="language-plaintext highlighter-rouge">Quota</code> and other Openshift resources.</p>
<blockquote class="prompt-tip">
<p><strong>Tip:</strong> When adding to the template validate the object’s first by creating them. Otherwise you might get syntax error’s when creating new projects</p>
</blockquote>
<p>Let’s change a few things in our template:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># our-template.yam</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">template.openshift.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Template</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">creationTimestamp</span><span class="pi">:</span> <span class="no">null</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">our-template</span>
<span class="na">objects</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">LimitRange</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${PROJECT_NAME}-resource-limits"</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">limits</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">type</span><span class="pi">:</span> <span class="s">Container</span>
<span class="na">default</span><span class="pi">:</span>
<span class="na">cpu</span><span class="pi">:</span> <span class="s">50m</span>
<span class="pi">-</span> <span class="na">apiVersion</span><span class="pi">:</span> <span class="s">project.openshift.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Project</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">annotations</span><span class="pi">:</span>
<span class="s">openshift.io/description</span><span class="pi">:</span> <span class="s">${PROJECT_DESCRIPTION}</span>
<span class="s">openshift.io/display-name</span><span class="pi">:</span> <span class="s">${PROJECT_DISPLAYNAME}</span>
<span class="s">openshift.io/requester</span><span class="pi">:</span> <span class="s">${PROJECT_REQUESTING_USER}</span>
<span class="na">creationTimestamp</span><span class="pi">:</span> <span class="no">null</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">${PROJECT_NAME}-from-template</span>
<span class="na">spec</span><span class="pi">:</span> <span class="pi">{}</span>
<span class="na">status</span><span class="pi">:</span> <span class="pi">{}</span>
<span class="na">parameters</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">PROJECT_NAME</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">PROJECT_DISPLAYNAME</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">PROJECT_DESCRIPTION</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">PROJECT_ADMIN_USER</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">PROJECT_REQUESTING_USER</span>
</code></pre></div></div>
<p>In this <code class="language-plaintext highlighter-rouge">yaml</code> we have:</p>
<ul>
<li>added to the project name <code class="language-plaintext highlighter-rouge">-from-template</code>. Every new project that is created will now be called PROJECT-from-template</li>
<li>Added a LimitRange with the name <code class="language-plaintext highlighter-rouge">${PROJECT_NAME}-resource-limits</code> to all new projects that sets a default cpu limit of <code class="language-plaintext highlighter-rouge">50m</code></li>
<li>Removed the default admin role binding</li>
</ul>
<h2 id="applying-our-custom-template">Applying our custom template</h2>
<p>Remember to create the template in <code class="language-plaintext highlighter-rouge">openshift-config</code>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc apply <span class="nt">-f</span> our-template.yaml <span class="nt">-n</span> openshift-config
template.template.openshift.io/our-template created
</code></pre></div></div>
<p>To make this template the default we need to add it at the end of <code class="language-plaintext highlighter-rouge">project.config.openshift.io/cluster</code>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc edit project.config.openshift.io/cluster
</code></pre></div></div>
<p>Change the last line from:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">spec</span><span class="pi">:</span> <span class="pi">{}</span>
</code></pre></div></div>
<p>To:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">spec</span><span class="pi">:</span>
<span class="na">projectRequestTemplate</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">our-template</span>
</code></pre></div></div>
<p>Now we can check out our template:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc get templates.template.openshift.io <span class="nt">-n</span> openshift-config
NAME DESCRIPTION PARAMETERS OBJECTS
our-template 5 <span class="o">(</span>5 blank<span class="o">)</span> 2
</code></pre></div></div>
<p>No we need to wait a bit for the cluster to pick up the change. You can monitor this by checking the api pods:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc get pods <span class="nt">-n</span> openshift-apiserver
AME READY STATUS RESTARTS AGE
apiserver-b47db7bc4-x79sm 0/2 Pending 0 41s
apiserver-ccc6bf7b5-gbbq2 2/2 Terminating 0 125m
</code></pre></div></div>
<p>Once the new pods are up and running we can test out our new template.</p>
<h2 id="creating-a-new-project-with-our-template">Creating a new project with our template</h2>
<p>Ok check, new config is online? Let’s create a project:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc new-project template-test-project
Now using project <span class="s2">"template-test-project-from-template"</span> on server <span class="s2">"https://api.crc.testing:6443"</span><span class="nb">.</span>
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc get limitrange
NAME CREATED AT
template-test-project-resource-limits 2022-02-27T19:38:12Z
</code></pre></div></div>
<h1 id="disabling-project-self-provisioning">Disabling project self-provisioning</h1>
<p>Letting users create new projects is a main principle of the DevOps setup of any cluster. There might however be situations where you don’t want users to create their own projects. You could enforce project creation with a GitOps pipeline and ensure that no rouge projects are created from the CLI or web-interface.</p>
<h2 id="patching-the-self-provisioner-role">Patching the self-provisioner role</h2>
<p>By default all authenticated uses are able to create new projects. To disable this we can patch this binding with:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc patch clusterrolebinding.rbac self-provisioners <span class="nt">-p</span> <span class="s1">'{"subjects": null}'</span>
</code></pre></div></div>
<p>After this, users can no longer create projects:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc new-project test-project
Error from server <span class="o">(</span>Forbidden<span class="o">)</span>: You may not request a new project via this API.
</code></pre></div></div>
<blockquote class="prompt-tip">
<p><strong>Auto update:</strong> This patching will work until the cluster is updated. To make this permanent follow the instructions in the <a href="https://docs.openshift.com/container-platform/4.6/applications/projects/configuring-project-creation.html">RedHat Openshift documentation</a></p>
</blockquote>
<h2 id="creating-a-provisioning-role">Creating a provisioning role</h2>
<p>In certain scenarios you might still want some users to create projects. All users with the clusterolebinding <code class="language-plaintext highlighter-rouge">cluster-admin</code> can still create projects. For users with less privileges we will create a group <code class="language-plaintext highlighter-rouge">ProjectCreators</code>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc adm <span class="nb">groups </span>new ProjectCreators
<span class="nv">$ </span>oc adm policy add-cluster-role-to-group self-provisioner ProjectCreators
<span class="nv">$ </span>oc adm <span class="nb">groups </span>add-users ProjectCreators Jim
</code></pre></div></div>
<p>Now all members of the group can create projects.</p>
<h1 id="wrapping-up">Wrapping up</h1>
<p>This was a really simple demo of changing the default project template to something that fits our needs better.</p>
<p>I hope this post has helped you. Check out my other EX280 related content on my <a href="https://blog.benstein.nl/ex280/">EX280 page</a></p>Christian BensteinIn this blog we will have a look at “Configuring project creation” in an Openshift cluster. We will: Create a Project Template Add resources like a limit-range to the template Disable project self-provisioning As always. We will be doing all the examples in a CRC (Code Ready Containers) environment. Project template When creating a new project in Openshift (a namespace) the API query’s the default template that is in use by the cluster. We can however change this to better suit our needs. By creating a project template and adding resources to it we can setup new Projects to be in line with our workflow and we can apply limits on creation. Creating a template Templates are stored in the namespace openshift-config as template.template.openshift.io objects. To create a new template we can use the special oc adm command: $ oc adm create-bootstrap-project-template -o yaml > our-template.yaml This template will include some basic things like: Creating a admin role-binding to the user that creates the project Setting up parameters like PROJECT_REQUESTING_USER Customizing our template Adding custom object is done by adding to the -objects array. Here we can add object’s like LimitRange, Quota and other Openshift resources. Tip: When adding to the template validate the object’s first by creating them. Otherwise you might get syntax error’s when creating new projects Let’s change a few things in our template: # our-template.yam apiVersion: template.openshift.io/v1 kind: Template metadata: creationTimestamp: null name: our-template objects: - apiVersion: v1 kind: LimitRange metadata: name: "${PROJECT_NAME}-resource-limits" spec: limits: - type: Container default: cpu: 50m - apiVersion: project.openshift.io/v1 kind: Project metadata: annotations: openshift.io/description: ${PROJECT_DESCRIPTION} openshift.io/display-name: ${PROJECT_DISPLAYNAME} openshift.io/requester: ${PROJECT_REQUESTING_USER} creationTimestamp: null name: ${PROJECT_NAME}-from-template spec: {} status: {} parameters: - name: PROJECT_NAME - name: PROJECT_DISPLAYNAME - name: PROJECT_DESCRIPTION - name: PROJECT_ADMIN_USER - name: PROJECT_REQUESTING_USER In this yaml we have: added to the project name -from-template. Every new project that is created will now be called PROJECT-from-template Added a LimitRange with the name ${PROJECT_NAME}-resource-limits to all new projects that sets a default cpu limit of 50m Removed the default admin role binding Applying our custom template Remember to create the template in openshift-config: $ oc apply -f our-template.yaml -n openshift-config template.template.openshift.io/our-template created To make this template the default we need to add it at the end of project.config.openshift.io/cluster: $ oc edit project.config.openshift.io/cluster Change the last line from: spec: {} To: spec: projectRequestTemplate: name: our-template Now we can check out our template: $ oc get templates.template.openshift.io -n openshift-config NAME DESCRIPTION PARAMETERS OBJECTS our-template 5 (5 blank) 2 No we need to wait a bit for the cluster to pick up the change. You can monitor this by checking the api pods: $ oc get pods -n openshift-apiserver AME READY STATUS RESTARTS AGE apiserver-b47db7bc4-x79sm 0/2 Pending 0 41s apiserver-ccc6bf7b5-gbbq2 2/2 Terminating 0 125m Once the new pods are up and running we can test out our new template. Creating a new project with our template Ok check, new config is online? Let’s create a project: $ oc new-project template-test-project Now using project "template-test-project-from-template" on server "https://api.crc.testing:6443". $ oc get limitrange NAME CREATED AT template-test-project-resource-limits 2022-02-27T19:38:12Z Disabling project self-provisioning Letting users create new projects is a main principle of the DevOps setup of any cluster. There might however be situations where you don’t want users to create their own projects. You could enforce project creation with a GitOps pipeline and ensure that no rouge projects are created from the CLI or web-interface. Patching the self-provisioner role By default all authenticated uses are able to create new projects. To disable this we can patch this binding with: $ oc patch clusterrolebinding.rbac self-provisioners -p '{"subjects": null}' After this, users can no longer create projects: $ oc new-project test-project Error from server (Forbidden): You may not request a new project via this API. Auto update: This patching will work until the cluster is updated. To make this permanent follow the instructions in the RedHat Openshift documentation Creating a provisioning role In certain scenarios you might still want some users to create projects. All users with the clusterolebinding cluster-admin can still create projects. For users with less privileges we will create a group ProjectCreators: $ oc adm groups new ProjectCreators $ oc adm policy add-cluster-role-to-group self-provisioner ProjectCreators $ oc adm groups add-users ProjectCreators Jim Now all members of the group can create projects. Wrapping up This was a really simple demo of changing the default project template to something that fits our needs better. I hope this post has helped you. Check out my other EX280 related content on my EX280 pageExposing services with routes and SSL2022-02-26T00:00:00+01:002022-02-26T00:00:00+01:00https://blog.benstein.nl/posts/exposing-services-with-routes-and-ssl<p>This blog will go in to <a href="https://www.redhat.com/en/services/training/ex280-red-hat-certified-specialist-in-openshift-administration-exam?section=Objectives">“Configure networking components”</a> objective of the <a href="https://www.redhat.com/en/services/training/ex280-red-hat-certified-specialist-in-openshift-administration-exam?section=Overview">EX280</a> exam from RedHat. In this post we will:</p>
<ul>
<li>Have a look at services</li>
<li>Expose a service with a URL</li>
<li>Check out the types of routes we can create</li>
<li>Creating a route to a service that is encrypted with SSL</li>
</ul>
<p>As always. We will be doing all the examples in a CRC (<a href="https://developers.redhat.com/products/codeready-containers/overview">Code Ready Containers</a>) environment.</p>
<h1 id="understanding-services">Understanding services</h1>
<p>Lets start with the last component when connecting to an application that is running in OpenShift, the <code class="language-plaintext highlighter-rouge">pod</code> where the application is running. How does it get here? When you create a application using <code class="language-plaintext highlighter-rouge">oc new-app</code> the cluster automatically creates a <code class="language-plaintext highlighter-rouge">service</code> for you (checkout <code class="language-plaintext highlighter-rouge">oc explain service</code>).</p>
<p>A <code class="language-plaintext highlighter-rouge">service</code> acts as a load balancer for your pod(s) running your application. This way, no matter how many pods you have running (or where they are running) there is a single service object that can be used to access them.</p>
<pre><code class="language-mermaid">flowchart LR
subgraph Internet
Client
end
subgraph Openshift
Client -->|Public URL| Route
Route --> Service
subgraph Namespace
Service --> Pod01
Service --> Pod02
Service --> Pod03
end
end
</code></pre>
<h2 id="services-in-detail">Services in detail</h2>
<p>A service {object} in Openshift can look like this:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Service</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">annotations</span><span class="pi">:</span>
<span class="s">openshift.io/generated-by</span><span class="pi">:</span> <span class="s">OpenShiftNewApp</span>
<span class="na">labels</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span> <span class="s">nginx</span>
<span class="s">app.kubernetes.io/component</span><span class="pi">:</span> <span class="s">nginx</span>
<span class="s">app.kubernetes.io/instance</span><span class="pi">:</span> <span class="s">nginx</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">nginx</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">clusterIP</span><span class="pi">:</span> <span class="s">10.217.4.22</span>
<span class="na">clusterIPs</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">10.217.4.22</span>
<span class="na">internalTrafficPolicy</span><span class="pi">:</span> <span class="s">Cluster</span>
<span class="na">ipFamilies</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">IPv4</span>
<span class="na">ipFamilyPolicy</span><span class="pi">:</span> <span class="s">SingleStack</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">8080-tcp</span>
<span class="na">port</span><span class="pi">:</span> <span class="m">8080</span>
<span class="na">protocol</span><span class="pi">:</span> <span class="s">TCP</span>
<span class="na">targetPort</span><span class="pi">:</span> <span class="m">8080</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">8443-tcp</span>
<span class="na">port</span><span class="pi">:</span> <span class="m">8443</span>
<span class="na">protocol</span><span class="pi">:</span> <span class="s">TCP</span>
<span class="na">targetPort</span><span class="pi">:</span> <span class="m">8443</span>
<span class="na">selector</span><span class="pi">:</span>
<span class="na">deployment</span><span class="pi">:</span> <span class="s">nginx</span>
<span class="na">sessionAffinity</span><span class="pi">:</span> <span class="s">None</span>
<span class="na">type</span><span class="pi">:</span> <span class="s">ClusterIP</span>
<span class="na">status</span><span class="pi">:</span>
<span class="na">loadBalancer</span><span class="pi">:</span> <span class="pi">{}</span>
</code></pre></div></div>
<p>In this example we can see:</p>
<ul>
<li>That the cluster ip of this service (the IP that can be used to reach the pods) is <code class="language-plaintext highlighter-rouge">10.217.4.22</code></li>
<li>That this route will send traffic to deployments with a <code class="language-plaintext highlighter-rouge">selector</code> (<code class="language-plaintext highlighter-rouge">deployment: nginx</code>)</li>
<li>The type of this service is <code class="language-plaintext highlighter-rouge">ClusterIP</code>. This is the default. There are more types, check them out at the <a href="https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types">Kubernetes Service Types</a> page</li>
</ul>
<h1 id="understanding-routes">Understanding Routes</h1>
<p>We can access the pods using a service with an IP. Great, but no one (hopefully) is typing IP’s in to their browser on a daily basis to get to your app. That’s where <code class="language-plaintext highlighter-rouge">routes</code> come in to play.
With a route we can expose our service with a http URL like <code class="language-plaintext highlighter-rouge">amazing-app.ourcluster.com</code>. And if we configure Openshift even further we can make things available on base domains like <code class="language-plaintext highlighter-rouge">amazing-app.com</code>.
Routes are used by the central cluster ingress (the point where all the traffic comes in) to route (pun!) the traffic to the right service.</p>
<h2 id="expose-vs-route"><code class="language-plaintext highlighter-rouge">expose</code> vs <code class="language-plaintext highlighter-rouge">route</code></h2>
<p>There are two (2) ways to create a route. <code class="language-plaintext highlighter-rouge">oc expose</code> and <code class="language-plaintext highlighter-rouge">oc create route</code>. When we talk about creating a route we often jump to the latter because it has ‘route’ in it’s name. It is even the one we should be using because <code class="language-plaintext highlighter-rouge">oc create route</code> is used to create Secure routes. And here lies the difference in the two. When using <code class="language-plaintext highlighter-rouge">expose</code> we can expose our service in a number of different ways which include using a <code class="language-plaintext highlighter-rouge">http</code> hostname. When using <code class="language-plaintext highlighter-rouge">oc create route</code> we can create a secure route that uses TLS/SSL to encrypt the traffic.</p>
<h2 id="types-of-secure-routes">Types of secure routes</h2>
<p>When using <code class="language-plaintext highlighter-rouge">oc create route</code> we have to choose between a set of different types of routes, these are:</p>
<ul>
<li><strong>Edge:</strong> This will expose a route on the <em>edge</em> of your cluster. The route will contain the SSL certificate files. Once the traffic goes to the service it will no longe be encrypted</li>
<li><strong>Passtrough:</strong> This will connect you pod directly to the cluster Ingress. The SSL certificate will be stored in the pod</li>
<li><strong>Reencrypt</strong>: Is a combination of Edge and Passtrough. It will re encrypt the traffic once it is passed the Ingress</li>
</ul>
<h1 id="exposing-an-application">Exposing an application</h1>
<p>We will now move on to some examples, we will:</p>
<ol>
<li>Create a route using <code class="language-plaintext highlighter-rouge">expose</code> to create our route on <code class="language-plaintext highlighter-rouge">http</code></li>
<li>We will create a safe edge route using a SSL certificate</li>
</ol>
<blockquote class="prompt-tip">
<p>You can find instructions on how to create a Self Signed Certificate <a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/4/html/system_administration_guide/apache_http_secure_server_configuration-creating_a_self_signed_certificate">here</a></p>
</blockquote>
<h2 id="creating-a-route">Creating a route</h2>
<p>We will create a route called <code class="language-plaintext highlighter-rouge">unsecure-route</code> on our app called <code class="language-plaintext highlighter-rouge">unsecure-app</code> in the namespace: <code class="language-plaintext highlighter-rouge">this-is-insecure</code>. The route will be <code class="language-plaintext highlighter-rouge">insecure-app-this-is-insecure.apps-crc.testing</code> because we are on CRC. Let’s get started:</p>
<p>Creating a project and a simple app:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc new-project this-is-insecure
<span class="nv">$ </span>oc new-app <span class="nt">--name</span> insecure-app <span class="nt">--image</span> bitnami/nginx
<span class="nt">--</span><span class="o">></span> Found container image 2309fdc <span class="o">(</span>15 hours old<span class="o">)</span> from Docker Hub <span class="k">for</span> <span class="s2">"bitnami/nginx"</span>
<span class="k">*</span> An image stream tag will be created as <span class="s2">"insecure-app:latest"</span> that will track this image
<span class="nt">--</span><span class="o">></span> Creating resources ...
imagestream.image.openshift.io <span class="s2">"insecure-app"</span> created
deployment.apps <span class="s2">"insecure-app"</span> created
service <span class="s2">"insecure-app"</span> created
<span class="nt">--</span><span class="o">></span> Success
Application is not exposed. You can expose services to the outside world by executing one or more of the commands below:
<span class="s1">'oc expose service/insecure-app'</span>
Run <span class="s1">'oc status'</span> to view your app.
</code></pre></div></div>
<p>Creating our route using <code class="language-plaintext highlighter-rouge">expose</code>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span> oc expose service/insecure-app <span class="se">\</span>
<span class="nt">--name</span> unsecure-route <span class="se">\</span>
<span class="nt">--hostname</span> insecure-app-this-is-insecure.apps-crc.testing
route.route.openshift.io/unsecure-route expos
</code></pre></div></div>
<h3 id="testing-our-route">Testing our route</h3>
<p>Testing can be done by spinning up another container or using the <code class="language-plaintext highlighter-rouge">oc debug</code> command:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc debug insecure-app-7d8667f64b-cvkcz
<span class="o">(</span>pod<span class="o">)</span> <span class="nv">$ </span>curl <span class="nt">-v</span> insecure-app-this-is-insecure.apps-crc.testing
....
< HTTP/1.1 200 OK
< server: nginx
< <span class="nb">date</span>: Sat, 26 Feb 2022 15:20:53 GMT
< content-type: text/html
< content-length: 615
< last-modified: Tue, 15 Feb 2022 23:05:56 GMT
< etag: <span class="s2">"620c31d4-267"</span>
< x-frame-options: SAMEORIGIN
< accept-ranges: bytes
< set-cookie: <span class="nv">cef860fd45f3b8c73246fd4b31240250</span><span class="o">=</span>22b324be48072691fec3f3ebe83c8473<span class="p">;</span> <span class="nv">path</span><span class="o">=</span>/<span class="p">;</span> HttpOnly
< cache-control: private
....
</code></pre></div></div>
<p>And that’s that!</p>
<h2 id="creating-a-secure-route">Creating a secure route</h2>
<p>Now we will create a route but we will secure it with a TLS/SSL cert. The route will be called <code class="language-plaintext highlighter-rouge">secure-route</code> with the hostname <code class="language-plaintext highlighter-rouge">secure-app-this-is-secure.apps-crc.testing</code> in our project <code class="language-plaintext highlighter-rouge">this-is-secure</code> with the app <code class="language-plaintext highlighter-rouge">secure-app</code>:</p>
<p>Creating a project and app:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc new-project this-is-secure
<span class="nv">$ </span>oc new-app <span class="nt">--name</span> secure-app <span class="nt">--image</span> bitnami/nginx
</code></pre></div></div>
<p>Now we will create the secure route:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc create route edge secure-route <span class="se">\</span>
<span class="nt">--service</span> secure-app <span class="se">\</span>
<span class="nt">--hostname</span> secure-app-this-is-secure.apps-crc.testing <span class="se">\</span>
<span class="nt">--key</span> secure-app.key <span class="se">\</span>
<span class="nt">--cert</span> secure-app.crt
</code></pre></div></div>
<h3 id="testing-our-secure-route">Testing our secure route</h3>
<p>Let’s check it using <code class="language-plaintext highlighter-rouge">oc debug PODNAME</code>, don’t forget to add the <code class="language-plaintext highlighter-rouge">-v</code> (verbose) and <code class="language-plaintext highlighter-rouge">-k</code> (insecure) flag’s to the <code class="language-plaintext highlighter-rouge">curl</code> command otherwise you will not see the protocol and it will throw up an error because the SSL certificate is not signed by a valid CA:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc debug secure-app-bccd9557b-s2nd4
<span class="o">(</span>pod<span class="o">)</span> <span class="nv">$ </span>curl <span class="nt">-vk</span> https://secure-app-this-is-secure.apps-crc.testing
<span class="k">*</span> SSL certificate verify result: self signed certificate <span class="o">(</span>18<span class="o">)</span>, continuing anyway.
<span class="o">></span> GET / HTTP/1.1
<span class="o">></span> Host: secure-app-this-is-secure.apps-crc.testing
<span class="o">></span> User-Agent: curl/7.64.0
<span class="o">></span> Accept: <span class="k">*</span>/<span class="k">*</span>
<span class="o">></span>
<span class="k">*</span> TLSv1.3 <span class="o">(</span>IN<span class="o">)</span>, TLS handshake, Newsession Ticket <span class="o">(</span>4<span class="o">)</span>:
<span class="k">*</span> TLSv1.3 <span class="o">(</span>IN<span class="o">)</span>, TLS handshake, Newsession Ticket <span class="o">(</span>4<span class="o">)</span>:
<span class="k">*</span> old SSL session ID is stale, removing
< HTTP/1.1 200 OK
< server: nginx
< <span class="nb">date</span>: Sat, 26 Feb 2022 15:29:16 GMT
< content-type: text/html
< content-length: 615
< last-modified: Tue, 15 Feb 2022 23:05:56 GMT
< etag: <span class="s2">"620c31d4-267"</span>
< x-frame-options: SAMEORIGIN
< accept-ranges: bytes
< set-cookie: <span class="nv">0e799158437fe6ddd46e0e7a82dd3c25</span><span class="o">=</span>25f02ff58528846c979c113d77ea289e<span class="p">;</span> <span class="nv">path</span><span class="o">=</span>/<span class="p">;</span> HttpOnly<span class="p">;</span> Secure<span class="p">;</span> <span class="nv">SameSite</span><span class="o">=</span>None
< cache-control: private
....
</code></pre></div></div>
<h1 id="wrapping-up">Wrapping up</h1>
<p>The way that services and routes play together is a fundamental building block of exposing your applications to the outside world in a safe way. The thing that I found so confusing is that <code class="language-plaintext highlighter-rouge">oc expose</code> creates routes just like <code class="language-plaintext highlighter-rouge">oc create route</code> but in the insecure way.</p>
<p>I hope this post has helped you. Check out my other EX280 related content on my <a href="https://blog.benstein.nl/ex280/">EX280 page</a></p>Christian BensteinThis blog will go in to “Configure networking components” objective of the EX280 exam from RedHat. In this post we will: Have a look at services Expose a service with a URL Check out the types of routes we can create Creating a route to a service that is encrypted with SSL As always. We will be doing all the examples in a CRC (Code Ready Containers) environment. Understanding services Lets start with the last component when connecting to an application that is running in OpenShift, the pod where the application is running. How does it get here? When you create a application using oc new-app the cluster automatically creates a service for you (checkout oc explain service). A service acts as a load balancer for your pod(s) running your application. This way, no matter how many pods you have running (or where they are running) there is a single service object that can be used to access them. flowchart LR subgraph Internet Client end subgraph Openshift Client -->|Public URL| Route Route --> Service subgraph Namespace Service --> Pod01 Service --> Pod02 Service --> Pod03 end end Services in detail A service {object} in Openshift can look like this: apiVersion: v1 kind: Service metadata: annotations: openshift.io/generated-by: OpenShiftNewApp labels: app: nginx app.kubernetes.io/component: nginx app.kubernetes.io/instance: nginx name: nginx spec: clusterIP: 10.217.4.22 clusterIPs: - 10.217.4.22 internalTrafficPolicy: Cluster ipFamilies: - IPv4 ipFamilyPolicy: SingleStack ports: - name: 8080-tcp port: 8080 protocol: TCP targetPort: 8080 - name: 8443-tcp port: 8443 protocol: TCP targetPort: 8443 selector: deployment: nginx sessionAffinity: None type: ClusterIP status: loadBalancer: {} In this example we can see: That the cluster ip of this service (the IP that can be used to reach the pods) is 10.217.4.22 That this route will send traffic to deployments with a selector (deployment: nginx) The type of this service is ClusterIP. This is the default. There are more types, check them out at the Kubernetes Service Types page Understanding Routes We can access the pods using a service with an IP. Great, but no one (hopefully) is typing IP’s in to their browser on a daily basis to get to your app. That’s where routes come in to play. With a route we can expose our service with a http URL like amazing-app.ourcluster.com. And if we configure Openshift even further we can make things available on base domains like amazing-app.com. Routes are used by the central cluster ingress (the point where all the traffic comes in) to route (pun!) the traffic to the right service. expose vs route There are two (2) ways to create a route. oc expose and oc create route. When we talk about creating a route we often jump to the latter because it has ‘route’ in it’s name. It is even the one we should be using because oc create route is used to create Secure routes. And here lies the difference in the two. When using expose we can expose our service in a number of different ways which include using a http hostname. When using oc create route we can create a secure route that uses TLS/SSL to encrypt the traffic. Types of secure routes When using oc create route we have to choose between a set of different types of routes, these are: Edge: This will expose a route on the edge of your cluster. The route will contain the SSL certificate files. Once the traffic goes to the service it will no longe be encrypted Passtrough: This will connect you pod directly to the cluster Ingress. The SSL certificate will be stored in the pod Reencrypt: Is a combination of Edge and Passtrough. It will re encrypt the traffic once it is passed the Ingress Exposing an application We will now move on to some examples, we will: Create a route using expose to create our route on http We will create a safe edge route using a SSL certificate You can find instructions on how to create a Self Signed Certificate here Creating a route We will create a route called unsecure-route on our app called unsecure-app in the namespace: this-is-insecure. The route will be insecure-app-this-is-insecure.apps-crc.testing because we are on CRC. Let’s get started: Creating a project and a simple app: $ oc new-project this-is-insecure $ oc new-app --name insecure-app --image bitnami/nginx --> Found container image 2309fdc (15 hours old) from Docker Hub for "bitnami/nginx" * An image stream tag will be created as "insecure-app:latest" that will track this image --> Creating resources ... imagestream.image.openshift.io "insecure-app" created deployment.apps "insecure-app" created service "insecure-app" created --> Success Application is not exposed. You can expose services to the outside world by executing one or more of the commands below: 'oc expose service/insecure-app' Run 'oc status' to view your app. Creating our route using expose: $ oc expose service/insecure-app \ --name unsecure-route \ --hostname insecure-app-this-is-insecure.apps-crc.testing route.route.openshift.io/unsecure-route expos Testing our route Testing can be done by spinning up another container or using the oc debug command: $ oc debug insecure-app-7d8667f64b-cvkcz (pod) $ curl -v insecure-app-this-is-insecure.apps-crc.testing .... < HTTP/1.1 200 OK < server: nginx < date: Sat, 26 Feb 2022 15:20:53 GMT < content-type: text/html < content-length: 615 < last-modified: Tue, 15 Feb 2022 23:05:56 GMT < etag: "620c31d4-267" < x-frame-options: SAMEORIGIN < accept-ranges: bytes < set-cookie: cef860fd45f3b8c73246fd4b31240250=22b324be48072691fec3f3ebe83c8473; path=/; HttpOnly < cache-control: private .... And that’s that! Creating a secure route Now we will create a route but we will secure it with a TLS/SSL cert. The route will be called secure-route with the hostname secure-app-this-is-secure.apps-crc.testing in our project this-is-secure with the app secure-app: Creating a project and app: $ oc new-project this-is-secure $ oc new-app --name secure-app --image bitnami/nginx Now we will create the secure route: $ oc create route edge secure-route \ --service secure-app \ --hostname secure-app-this-is-secure.apps-crc.testing \ --key secure-app.key \ --cert secure-app.crt Testing our secure route Let’s check it using oc debug PODNAME, don’t forget to add the -v (verbose) and -k (insecure) flag’s to the curl command otherwise you will not see the protocol and it will throw up an error because the SSL certificate is not signed by a valid CA: $ oc debug secure-app-bccd9557b-s2nd4 (pod) $ curl -vk https://secure-app-this-is-secure.apps-crc.testing * SSL certificate verify result: self signed certificate (18), continuing anyway. > GET / HTTP/1.1 > Host: secure-app-this-is-secure.apps-crc.testing > User-Agent: curl/7.64.0 > Accept: */* > * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): * old SSL session ID is stale, removing < HTTP/1.1 200 OK < server: nginx < date: Sat, 26 Feb 2022 15:29:16 GMT < content-type: text/html < content-length: 615 < last-modified: Tue, 15 Feb 2022 23:05:56 GMT < etag: "620c31d4-267" < x-frame-options: SAMEORIGIN < accept-ranges: bytes < set-cookie: 0e799158437fe6ddd46e0e7a82dd3c25=25f02ff58528846c979c113d77ea289e; path=/; HttpOnly; Secure; SameSite=None < cache-control: private .... Wrapping up The way that services and routes play together is a fundamental building block of exposing your applications to the outside world in a safe way. The thing that I found so confusing is that oc expose creates routes just like oc create route but in the insecure way. I hope this post has helped you. Check out my other EX280 related content on my EX280 pageManage users and policies, groups and permissions2022-02-20T00:00:00+01:002022-02-20T00:00:00+01:00https://blog.benstein.nl/posts/setting-up-auth-and-users<p>This blog will cover the <a href="https://www.redhat.com/en/services/training/ex280-red-hat-certified-specialist-in-openshift-administration-exam?section=Objectives">“Manage users and policies”</a> objective of the <a href="">EX280</a> exam from RedHat. In this post we will:</p>
<ul>
<li>Configure the HTPasswd identity provider for authentication</li>
<li>Create and delete users</li>
<li>Modify user passwords</li>
<li>Modify user and group permissions</li>
<li>Create and manage groups</li>
</ul>
<p>Will also sprinkle in a little bit from “Manage users and policies” with:</p>
<ul>
<li>Define role-based access controls</li>
<li>Apply permissions to users</li>
</ul>
<p>The most easy way to try OpenShift is by using CRC. In this case this gives as an advantage because HTPasswd is already set up. We will however be configuring it from the ground up using the example provided by the OpenShift documentation <sup id="fnref:ConfiguringHTPasswd" role="doc-noteref"><a href="#fn:ConfiguringHTPasswd" class="footnote" rel="footnote">1</a></sup>.</p>
<h1 id="setting-up-htpasswd-authentication">Setting up HTPasswd Authentication</h1>
<p>One of the simplest way to authenticate users to a cluster is by using the HTPasswd provider. This is based on a text based secret containing a <code class="language-plaintext highlighter-rouge">username:password</code> key-pair. It’s easy to set up and understand.</p>
<h2 id="creating-the-htpasswd-file">Creating the HTPasswd file</h2>
<p>Creating a new HTPasswd file can be done in a single command from the command-line:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>htpasswd <span class="nt">-c</span> <span class="nt">-B</span> <span class="nt">-b</span> FILENAME USERNAME PASSWORD
</code></pre></div></div>
<p>Let create our first user with username admin and password … dunder:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>htpasswd <span class="nt">-c</span> <span class="nt">-B</span> <span class="nt">-b</span> htpasswd-file Michael dunder
Adding password <span class="k">for </span>user Michael
</code></pre></div></div>
<h3 id="adding-users">Adding users</h3>
<p>After creating the <code class="language-plaintext highlighter-rouge">htpasswd</code> file we can drop the <code class="language-plaintext highlighter-rouge">-c</code> (create) flag and add some more users:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>htpasswd <span class="nt">-B</span> <span class="nt">-b</span> htpasswd-file Pam secret01
Adding password <span class="k">for </span>user Pam
<span class="nv">$ </span>htpasswd <span class="nt">-B</span> <span class="nt">-b</span> htpasswd-file Dwight secret02
<span class="nv">$ </span>htpasswd <span class="nt">-B</span> <span class="nt">-b</span> htpasswd-file Jim secret03
</code></pre></div></div>
<p class="error"><strong>Warning</strong>: Usernames and passwords are case sensitive</p>
<p>And let’s have a look at our file:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cat </span>htpasswd-file
Michael:<span class="nv">$2y$05$f2SD9CoUnLzqkA</span>.AgTVToOb6fSKhmN.5xwHqq/Cz/zUZ4ZSXqsyze
Pam:<span class="nv">$2y$05$fRR</span>.5EmSaDGd1rMtAygWxexpuiMGnZJOgk0Oo.kuocyKtKoin5Z0e
Dwight:<span class="nv">$2y$05$EKp1MLW</span>/anaJ1R2wKpAkju6oPZvxV47tTtTq8KKp7x.cjFeOTke5u
Jim:<span class="nv">$2y$05$k1Nlh3ZhqY8E6wK</span>.v1exfOuieLkmZT2MRwFFxVuYZ8KQZ3xcLHWg.
</code></pre></div></div>
<h3 id="applying-secret-to-the-cluster">Applying secret to the cluster</h3>
<p>In order to use our <code class="language-plaintext highlighter-rouge">htpasswd</code> file we need to make it available in the cluster as a secret. <a href="https://blog.benstein.nl/posts/create-and-use-secrets-openshift/">I wrote about creating secrets before here</a>.</p>
<p>We will create a secret called <code class="language-plaintext highlighter-rouge">htpasswd-source</code> in the cluster with the content of our <code class="language-plaintext highlighter-rouge">htpasswd</code> file. This has to be done in the project <code class="language-plaintext highlighter-rouge">openshift-config</code>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc create secret generic htpasswd-source <span class="se">\</span>
<span class="nt">--from-file</span> <span class="nv">htpasswd</span><span class="o">=</span>htpasswd-file <span class="se">\</span>
<span class="nt">--namespace</span> openshift-config
secret/htpasswd-source created
</code></pre></div></div>
<h2 id="setting-up-the-identity-provider">Setting up the identity provider</h2>
<p>Now that we have the secret in place we can create our identity provider. We will do this with the template thats available from the OpenShift Documentation <sup id="fnref:ConfiguringHTPasswd:1" role="doc-noteref"><a href="#fn:ConfiguringHTPasswd" class="footnote" rel="footnote">1</a></sup> :</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">config.openshift.io/v1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">OAuth</span>
<span class="na">metadata</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">cluster</span>
<span class="na">spec</span><span class="pi">:</span>
<span class="na">identityProviders</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">custom_htpasswd_provider</span>
<span class="na">mappingMethod</span><span class="pi">:</span> <span class="s">claim</span>
<span class="na">type</span><span class="pi">:</span> <span class="s">HTPasswd</span>
<span class="na">htpasswd</span><span class="pi">:</span>
<span class="na">fileData</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">htpasswd-source</span>
</code></pre></div></div>
<p>By applying this <code class="language-plaintext highlighter-rouge">yaml</code> to the cluster we will:</p>
<ul>
<li>Create a OAuth Identity provider called <code class="language-plaintext highlighter-rouge">custom_htpasswd_provider</code></li>
<li>With the source secret <code class="language-plaintext highlighter-rouge">htpasswd-source</code></li>
</ul>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc apply <span class="nt">-f</span> custom_htpasswd_provider.yaml <span class="se">\</span>
<span class="nt">--namespace</span> openshift-config
oauth.config.openshift.io/cluster configured
</code></pre></div></div>
<p>It might take a while but after some syncing in you cluster your new auth provider should be online. You can check this with <code class="language-plaintext highlighter-rouge">oc get oauth</code>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc get oauth <span class="nt">-o</span> yaml
apiVersion: v1
items:
- apiVersion: config.openshift.io/v1
....
spec:
identityProviders:
- htpasswd:
fileData:
name: htpasswd-source
mappingMethod: claim
name: custom_htpasswd_provider
<span class="nb">type</span>: HTPasswd
....
</code></pre></div></div>
<h2 id="viewing-users-and-logging-in">Viewing users and logging in</h2>
<p>After getting our new authentication provider up and running we can test it by logging in with one of our new users. But first, lets take a look at the users we have:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc get <span class="nb">users
</span>NAME UID FULL NAME IDENTITIES
developer 623cd251-b25b-44b5-a00e-67f311029588 developer:developer
kubeadmin 06238f0b-4f58-45f2-9b61-94b482bb4b74 developer:kubeadmin
</code></pre></div></div>
<p>As you can see our new users are not yet listed. This is because they will be created after they log in. For example:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc login <span class="nt">-u</span> Pam https://api.crc.testing:6443
Authentication required <span class="k">for </span>https://api.crc.testing:6443 <span class="o">(</span>openshift<span class="o">)</span>
Username: Pam
Password:
Login successful.
</code></pre></div></div>
<p>Switch back to a user with admin rights and check te users again:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc login <span class="nt">-u</span> kubeadmin
Logged into <span class="s2">"https://api.crc.testing:6443"</span> as <span class="s2">"kubeadmin"</span> using existing credentials.
<span class="nv">$ </span>oc get <span class="nb">users
</span>NAME UID FULL NAME IDENTITIES
Pam cfe773a0-e0e2-40e1-bb5f-799db1ceaeb7 custom_htpasswd_provider:Pam
developer 623cd251-b25b-44b5-a00e-67f311029588 developer:developer
kubeadmin 06238f0b-4f58-45f2-9b61-94b482bb4b74 developer:kubeadmin
</code></pre></div></div>
<p>We can see that Pam was created and that her identity is provided by <code class="language-plaintext highlighter-rouge">custom_htpasswd_provider:Pam</code>. At this point we can see that everything works. I would however advise you to test all users that you have created. This will prevent errors from showing when we want to add users to a group.</p>
<h1 id="creating-groups">Creating groups</h1>
<p>Creating groups is actually quite strait forward with the <code class="language-plaintext highlighter-rouge">oc adm groups</code> command. First off, lets create a group called managers:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc adm <span class="nb">groups </span>new managers
group.user.openshift.io/managers created
</code></pre></div></div>
<p>And let’s add Micheal to that group:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc adm <span class="nb">groups </span>add-users managers Michael
group.user.openshift.io/managers added: <span class="s2">"Michael"</span>
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc get <span class="nb">groups
</span>NAME USERS
managers Michael
</code></pre></div></div>
<p>It’s that easy. We can also create a group and add users at the same time:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc adm <span class="nb">groups </span>new sales Jim Dwight
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc adm <span class="nb">groups </span>new sales Jim Dwight
group.user.openshift.io/sales created
<span class="nv">$ </span>oc adm <span class="nb">groups </span>new reception Pam
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>oc get <span class="nb">groups
</span>NAME USERS
managers Michael
reception Pam
sales Jim, Dwight
</code></pre></div></div>
<p>It’s that easy!</p>
<h1 id="assigning-permissions">Assigning Permissions</h1>
<p>Now it’s time to assign some permissions. We do this by using the <code class="language-plaintext highlighter-rouge">oc adm policy</code> command. By default a user can create projects and manage objects in that project. We can assign permissions to a user or a group. We are going to give Michael full permissions to the cluster by giving him <code class="language-plaintext highlighter-rouge">cluster-admin</code> cluster role:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>oc adm policy add-cluster-role-to-user cluster-admin Michael
clusterrole.rbac.authorization.k8s.io/cluster-admin added: <span class="s2">"Michael"</span>
</code></pre></div></div>
<p>Now we will create a project as Micheal and give the sales group permissions to it:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc new-project sales
<span class="nv">$ </span>oc adm policy add-role-to-group edit sales <span class="se">\</span>
<span class="nt">--namespace</span> sales
clusterrole.rbac.authorization.k8s.io/edit added: <span class="s2">"sales"</span>
</code></pre></div></div>
<p>Take note of the <code class="language-plaintext highlighter-rouge">--namespace</code> flag in this command. This will bind the permissions to the namespace sales.</p>
<p>We also want to give the reception groups view permissions to the namespace sales:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc adm policy add-role-to-group view reception <span class="nt">--namespace</span> sales
clusterrole.rbac.authorization.k8s.io/view added: <span class="s2">"reception"</span>
</code></pre></div></div>
<p>To view all bound permissions to a namespace we can use:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc describe rolebinding.rbac <span class="nt">-n</span> sale
</code></pre></div></div>
<p>For example:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc describe rolebinding.rbac <span class="nt">-n</span> sales | <span class="nb">grep </span>reception <span class="nt">-B</span> 9
Name: view
Labels: <none>
Annotations: <none>
Role:
Kind: ClusterRole
Name: view
Subjects:
Kind Name Namespace
<span class="nt">----</span> <span class="nt">----</span> <span class="nt">---------</span>
Group reception
</code></pre></div></div>
<p>For a full list of roles and permissions check out the <a href="https://docs.openshift.com/container-platform/4.6/authentication/using-rbac.html">RedHat OpenShift documentation on Roles</a>.</p>
<h2 id="optional-removing-the-default-kubeadmin">Optional: Removing the default kubeadmin</h2>
<p>A great best practice is to remove the default kubeadmin user. However, we should only do this after assigning the cluster-role <code class="language-plaintext highlighter-rouge">cluster-admin</code> to a user that we have testes. To make sure of this let’s check the binding:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc describe rolebinding.rbac | <span class="nb">grep </span>ClusterRole <span class="nt">-A</span> 5
Kind: ClusterRole
Name: admin
Subjects:
Kind Name Namespace
<span class="nt">----</span> <span class="nt">----</span> <span class="nt">---------</span>
User Michael
....
</code></pre></div></div>
<p>Check! Now we can delete the default kubeadmin user <sup id="fnref:DeletingTheKubeadminUser" role="doc-noteref"><a href="#fn:DeletingTheKubeadminUser" class="footnote" rel="footnote">2</a></sup> :</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>oc delete secrets kubeadmin <span class="nt">-n</span> kube-system
secret <span class="s2">"kubeadmin"</span> deleted
</code></pre></div></div>
<h1 id="wrapping-up">Wrapping up</h1>
<p>Understanding how user creation, authentication and permissions works is a basic principle of working with cluster. Is is however often overlooked because there are so many pre configured ways out there to get this working (like integration with Azure or a direct LDPA sync). It’s important have a firm understanding of Users, Groups and Permissions in order to be proficient OpenShift Admin.</p>
<p>I hope this post has helped you. Check out my other EX280 related content on my <a href="https://blog.benstein.nl/ex280/">EX280 page</a></p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:ConfiguringHTPasswd" role="doc-endnote">
<p>https://docs.openshift.com/container-platform/4.6/authentication/identity_providers/configuring-htpasswd-identity-provider.html <a href="#fnref:ConfiguringHTPasswd" class="reversefootnote" role="doc-backlink">↩</a> <a href="#fnref:ConfiguringHTPasswd:1" class="reversefootnote" role="doc-backlink">↩<sup>2</sup></a></p>
</li>
<li id="fn:DeletingTheKubeadminUser" role="doc-endnote">
<p>https://docs.openshift.com/container-platform/4.6/authentication/remove-kubeadmin.html#removing-kubeadmin_removing-kubeadmin <a href="#fnref:DeletingTheKubeadminUser" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>Christian BensteinThis blog will cover the “Manage users and policies” objective of the EX280 exam from RedHat. In this post we will: Configure the HTPasswd identity provider for authentication Create and delete users Modify user passwords Modify user and group permissions Create and manage groups Will also sprinkle in a little bit from “Manage users and policies” with: Define role-based access controls Apply permissions to users The most easy way to try OpenShift is by using CRC. In this case this gives as an advantage because HTPasswd is already set up. We will however be configuring it from the ground up using the example provided by the OpenShift documentation 1. Setting up HTPasswd Authentication One of the simplest way to authenticate users to a cluster is by using the HTPasswd provider. This is based on a text based secret containing a username:password key-pair. It’s easy to set up and understand. Creating the HTPasswd file Creating a new HTPasswd file can be done in a single command from the command-line: $ htpasswd -c -B -b FILENAME USERNAME PASSWORD Let create our first user with username admin and password … dunder: $ htpasswd -c -B -b htpasswd-file Michael dunder Adding password for user Michael Adding users After creating the htpasswd file we can drop the -c (create) flag and add some more users: $ htpasswd -B -b htpasswd-file Pam secret01 Adding password for user Pam $ htpasswd -B -b htpasswd-file Dwight secret02 $ htpasswd -B -b htpasswd-file Jim secret03 Warning: Usernames and passwords are case sensitive And let’s have a look at our file: $ cat htpasswd-file Michael:$2y$05$f2SD9CoUnLzqkA.AgTVToOb6fSKhmN.5xwHqq/Cz/zUZ4ZSXqsyze Pam:$2y$05$fRR.5EmSaDGd1rMtAygWxexpuiMGnZJOgk0Oo.kuocyKtKoin5Z0e Dwight:$2y$05$EKp1MLW/anaJ1R2wKpAkju6oPZvxV47tTtTq8KKp7x.cjFeOTke5u Jim:$2y$05$k1Nlh3ZhqY8E6wK.v1exfOuieLkmZT2MRwFFxVuYZ8KQZ3xcLHWg. Applying secret to the cluster In order to use our htpasswd file we need to make it available in the cluster as a secret. I wrote about creating secrets before here. We will create a secret called htpasswd-source in the cluster with the content of our htpasswd file. This has to be done in the project openshift-config: $ oc create secret generic htpasswd-source \ --from-file htpasswd=htpasswd-file \ --namespace openshift-config secret/htpasswd-source created Setting up the identity provider Now that we have the secret in place we can create our identity provider. We will do this with the template thats available from the OpenShift Documentation 1 : apiVersion: config.openshift.io/v1 kind: OAuth metadata: name: cluster spec: identityProviders: - name: custom_htpasswd_provider mappingMethod: claim type: HTPasswd htpasswd: fileData: name: htpasswd-source By applying this yaml to the cluster we will: Create a OAuth Identity provider called custom_htpasswd_provider With the source secret htpasswd-source $ oc apply -f custom_htpasswd_provider.yaml \ --namespace openshift-config oauth.config.openshift.io/cluster configured It might take a while but after some syncing in you cluster your new auth provider should be online. You can check this with oc get oauth: $ oc get oauth -o yaml apiVersion: v1 items: - apiVersion: config.openshift.io/v1 .... spec: identityProviders: - htpasswd: fileData: name: htpasswd-source mappingMethod: claim name: custom_htpasswd_provider type: HTPasswd .... Viewing users and logging in After getting our new authentication provider up and running we can test it by logging in with one of our new users. But first, lets take a look at the users we have: $ oc get users NAME UID FULL NAME IDENTITIES developer 623cd251-b25b-44b5-a00e-67f311029588 developer:developer kubeadmin 06238f0b-4f58-45f2-9b61-94b482bb4b74 developer:kubeadmin As you can see our new users are not yet listed. This is because they will be created after they log in. For example: $ oc login -u Pam https://api.crc.testing:6443 Authentication required for https://api.crc.testing:6443 (openshift) Username: Pam Password: Login successful. Switch back to a user with admin rights and check te users again: $ oc login -u kubeadmin Logged into "https://api.crc.testing:6443" as "kubeadmin" using existing credentials. $ oc get users NAME UID FULL NAME IDENTITIES Pam cfe773a0-e0e2-40e1-bb5f-799db1ceaeb7 custom_htpasswd_provider:Pam developer 623cd251-b25b-44b5-a00e-67f311029588 developer:developer kubeadmin 06238f0b-4f58-45f2-9b61-94b482bb4b74 developer:kubeadmin We can see that Pam was created and that her identity is provided by custom_htpasswd_provider:Pam. At this point we can see that everything works. I would however advise you to test all users that you have created. This will prevent errors from showing when we want to add users to a group. Creating groups Creating groups is actually quite strait forward with the oc adm groups command. First off, lets create a group called managers: $ oc adm groups new managers group.user.openshift.io/managers created And let’s add Micheal to that group: $ oc adm groups add-users managers Michael group.user.openshift.io/managers added: "Michael" $ oc get groups NAME USERS managers Michael It’s that easy. We can also create a group and add users at the same time: $ oc adm groups new sales Jim Dwight $ oc adm groups new sales Jim Dwight group.user.openshift.io/sales created $ oc adm groups new reception Pam oc get groups NAME USERS managers Michael reception Pam sales Jim, Dwight It’s that easy! Assigning Permissions Now it’s time to assign some permissions. We do this by using the oc adm policy command. By default a user can create projects and manage objects in that project. We can assign permissions to a user or a group. We are going to give Michael full permissions to the cluster by giving him cluster-admin cluster role: oc adm policy add-cluster-role-to-user cluster-admin Michael clusterrole.rbac.authorization.k8s.io/cluster-admin added: "Michael" Now we will create a project as Micheal and give the sales group permissions to it: $ oc new-project sales $ oc adm policy add-role-to-group edit sales \ --namespace sales clusterrole.rbac.authorization.k8s.io/edit added: "sales" Take note of the --namespace flag in this command. This will bind the permissions to the namespace sales. We also want to give the reception groups view permissions to the namespace sales: $ oc adm policy add-role-to-group view reception --namespace sales clusterrole.rbac.authorization.k8s.io/view added: "reception" To view all bound permissions to a namespace we can use: $ oc describe rolebinding.rbac -n sale For example: $ oc describe rolebinding.rbac -n sales | grep reception -B 9 Name: view Labels: <none> Annotations: <none> Role: Kind: ClusterRole Name: view Subjects: Kind Name Namespace ---- ---- --------- Group reception For a full list of roles and permissions check out the RedHat OpenShift documentation on Roles. Optional: Removing the default kubeadmin A great best practice is to remove the default kubeadmin user. However, we should only do this after assigning the cluster-role cluster-admin to a user that we have testes. To make sure of this let’s check the binding: $ oc describe rolebinding.rbac | grep ClusterRole -A 5 Kind: ClusterRole Name: admin Subjects: Kind Name Namespace ---- ---- --------- User Michael .... Check! Now we can delete the default kubeadmin user 2 : $ oc delete secrets kubeadmin -n kube-system secret "kubeadmin" deleted Wrapping up Understanding how user creation, authentication and permissions works is a basic principle of working with cluster. Is is however often overlooked because there are so many pre configured ways out there to get this working (like integration with Azure or a direct LDPA sync). It’s important have a firm understanding of Users, Groups and Permissions in order to be proficient OpenShift Admin. I hope this post has helped you. Check out my other EX280 related content on my EX280 page https://docs.openshift.com/container-platform/4.6/authentication/identity_providers/configuring-htpasswd-identity-provider.html ↩ ↩2 https://docs.openshift.com/container-platform/4.6/authentication/remove-kubeadmin.html#removing-kubeadmin_removing-kubeadmin ↩