<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Rocket.Chat</title>
    <description>The latest articles on Forem by Rocket.Chat (@rocketchat).</description>
    <link>https://forem.com/rocketchat</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F4399%2Fad8ff560-f4dd-4299-b6c8-d22639525490.png</url>
      <title>Forem: Rocket.Chat</title>
      <link>https://forem.com/rocketchat</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/rocketchat"/>
    <language>en</language>
    <item>
      <title>Beginners' guide to Go Contexts: The Magic Controller of Goroutines</title>
      <dc:creator>Debdut Chakraborty</dc:creator>
      <pubDate>Fri, 06 Mar 2026 21:17:15 +0000</pubDate>
      <link>https://forem.com/rocketchat/beginners-guide-to-go-contexts-the-magic-controller-of-goroutines-158c</link>
      <guid>https://forem.com/rocketchat/beginners-guide-to-go-contexts-the-magic-controller-of-goroutines-158c</guid>
      <description>&lt;p&gt;We've all used contexts, usually by passing them to functions that require them, like HTTP handlers or database queries. But what exactly are contexts, and how do they work under the hood?&lt;/p&gt;

&lt;p&gt;In Go, a Context is essentially a signal. It travels through your functions to tell them when they should stop working because the data is no longer needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Basic Check
&lt;/h2&gt;

&lt;p&gt;The most fundamental way to use a context is to check its state manually. This is perfect for long-running loops or heavy calculations. If a function is a "one-off" and finishes instantly, a context doesn't add much value.&lt;/p&gt;

&lt;p&gt;However, for a loop like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="m"&gt;1000000&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="c"&gt;// check if the signal says we should stop  &lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"stopping early:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
            &lt;span class="k"&gt;return&lt;/span&gt;  
        &lt;span class="p"&gt;}&lt;/span&gt;  

        &lt;span class="c"&gt;// simulate some work  &lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;   
    &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we didn't have that if &lt;code&gt;err := ctx.Err()&lt;/code&gt; check, the goroutine would keep spinning even if the user who started it has already disconnected or timed out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Powering up with Select
&lt;/h2&gt;

&lt;p&gt;While checking &lt;code&gt;ctx.Err()&lt;/code&gt; works for loops, the real magic happens with the &lt;code&gt;select&lt;/code&gt; statement. This is how you make a goroutine "listen" for a cancellation signal while it is busy doing something else, like waiting for a channel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Waiting for a result
&lt;/h3&gt;

&lt;p&gt;Imagine you are fetching data from a slow API. You want the data, but you aren't willing to wait forever.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="n"&gt;resultCh&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// simulate a slow task  &lt;/span&gt;
        &lt;span class="n"&gt;resultCh&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="s"&gt;"got the data!"&lt;/span&gt;  
    &lt;span class="p"&gt;}()&lt;/span&gt;

    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;resultCh&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"received:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  
        &lt;span class="c"&gt;// ctx.Done() is a channel that closes when the context is cancelled  &lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"gave up waiting:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By using select, your code becomes responsive. The moment the context expires, the &lt;code&gt;&amp;lt;-ctx.Done()&lt;/code&gt; case triggers, and your function can exit immediately instead of hanging for the full 5 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layered Control
&lt;/h2&gt;

&lt;p&gt;Contexts are designed to be passed down. If you create a "child" context from a "parent," and the parent is cancelled, all the children are cancelled too. This lets you stop an entire tree of goroutines from one single place.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="c"&gt;// create a child context we can cancel manually  &lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithCancel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// this starts the loop from earlier&lt;/span&gt;

    &lt;span class="c"&gt;// simulate another part of the app failing  &lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"something else failed!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
        &lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// this kills the 'process' goroutine too  &lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Making Existing Code Context-Aware
&lt;/h2&gt;

&lt;p&gt;You might have a library or an old function that doesn't support contexts yet. How do you "wrap" it so it respects a timeout?&lt;/p&gt;

&lt;p&gt;The trick is to run the old code in a separate goroutine and use a select statement to wait for either the result or the context signal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;ContextAwareWrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="n"&gt;resultCh&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
        &lt;span class="c"&gt;// call the old, non-context-aware function  &lt;/span&gt;
        &lt;span class="n"&gt;resultCh&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;OldLegacyFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="p"&gt;}()&lt;/span&gt;

    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  
        &lt;span class="c"&gt;// if the context expires first, we return an error  &lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;resultCh&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  
        &lt;span class="c"&gt;// if the work finishes first, we return the result  &lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;  
    &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: Using a &lt;strong&gt;buffered channel&lt;/strong&gt; (&lt;code&gt;make(chan string, 1)&lt;/code&gt;) is important here. It ensures that if the context times out and we exit the function, the goroutine still running the OldLegacyFunction can send its result to the channel and exit without getting stuck forever (a goroutine leak).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Importance of Cancel and Defer
&lt;/h2&gt;

&lt;p&gt;Whenever you use &lt;code&gt;context.WithCancel&lt;/code&gt;, &lt;code&gt;WithTimeout&lt;/code&gt;, or &lt;code&gt;WithDeadline&lt;/code&gt;, the standard library gives you back a new &lt;code&gt;context&lt;/code&gt; and a &lt;code&gt;cancel&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You must call that cancel function.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Even if your function finishes successfully, you should call it. The best way to do this is with &lt;code&gt;defer&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  
    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="c"&gt;// this ensures that when main finishes, the context is cleaned up  &lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 

    &lt;span class="n"&gt;doWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why is this important?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Resource Cleanup:&lt;/strong&gt; Behind the scenes, the parent context keeps track of its children. If you don't call &lt;code&gt;cancel&lt;/code&gt;, the parent might keep a reference to the child in memory until the parent itself dies, leading to a memory leak.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop Ongoing Work:&lt;/strong&gt; Calling &lt;code&gt;cancel()&lt;/code&gt; sends the signal through the &lt;code&gt;ctx.Done()&lt;/code&gt; channel. It tells every function using that context: "The party is over, stop whatever you are doing."&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>go</category>
      <category>developer</category>
      <category>programming</category>
    </item>
    <item>
      <title>Conditions, Phases, and Declarative Phase Rules in Kubernetes Operators</title>
      <dc:creator>Debdut Chakraborty</dc:creator>
      <pubDate>Fri, 13 Feb 2026 22:40:31 +0000</pubDate>
      <link>https://forem.com/rocketchat/conditions-phases-and-declarative-phase-rules-in-kubernetes-operators-l2h</link>
      <guid>https://forem.com/rocketchat/conditions-phases-and-declarative-phase-rules-in-kubernetes-operators-l2h</guid>
      <description>&lt;h2&gt;
  
  
  Tl;Dr;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You can start with experimenting with the demo: &lt;a href="https://debdutdeb.github.io/kubernetes-phase-rules/" rel="noopener noreferrer"&gt;https://debdutdeb.github.io/kubernetes-phase-rules/&lt;/a&gt;, linking conditions with phases. It's fun. At least was to me.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spec&lt;/strong&gt; = desired state; &lt;strong&gt;status&lt;/strong&gt; = observed state. Controllers write status; the &lt;a href="https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status" rel="noopener noreferrer"&gt;API conventions&lt;/a&gt; describe this split and the role of conditions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conditions&lt;/strong&gt; are the right primitive: one observation per condition type, standardized and tooling-friendly. We use &lt;em&gt;specific&lt;/em&gt; condition types so each observable fact is explicit and consumable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase&lt;/strong&gt; is still useful as a single, high-level label for observability (UIs, alerts, filters), but it should be &lt;em&gt;derived&lt;/em&gt; from conditions, not maintained as a separate state machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase rules&lt;/strong&gt; declare “when these conditions hold, phase is X.” The first matching rule wins. That keeps a single source of truth (conditions), makes phase logic testable and explicit, and avoids duplication across consumers.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;kubernetes-phase-rules&lt;/strong&gt; (&lt;a href="https://github.com/debdutdeb/kubernetes-phase-rules" rel="noopener noreferrer"&gt;https://github.com/debdutdeb/kubernetes-phase-rules&lt;/a&gt;) package provides the rule types, matchers, and a StatusManager that keeps conditions and phase in sync and patches status for you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fohrw89t9oyerfkgc4of9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fohrw89t9oyerfkgc4of9.png" alt="Demo page" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem we faced
&lt;/h2&gt;

&lt;p&gt;At Rocket.Chat we have an operator called Airlock that originally started as an operator for mongodb user management. But recently expanding to help us manage a few more aspects of our database operations.&lt;/p&gt;

&lt;p&gt;We added a custom resource, Backup, whose status depended on several independent facts: Is the backup store available? Is database access granted? Has the backup job been scheduled? Did it complete or fail? Each of those is a separate observation, and in practice they’re often discovered or updated at different times, sometimes by different parts of the same controller or even by different controllers. So we ended up with &lt;strong&gt;multiple conditions&lt;/strong&gt; on one resource: &lt;code&gt;BucketStoreReady&lt;/code&gt;, &lt;code&gt;MongoDBAccessRequestReady&lt;/code&gt;, &lt;code&gt;JobScheduled&lt;/code&gt;, &lt;code&gt;JobCompleted&lt;/code&gt;, &lt;code&gt;JobFailed&lt;/code&gt;, and so on.&lt;/p&gt;

&lt;p&gt;We also needed &lt;strong&gt;one phase&lt;/strong&gt;—a single label like &lt;code&gt;Pending&lt;/code&gt;, &lt;code&gt;Running&lt;/code&gt;, &lt;code&gt;Completed&lt;/code&gt;, or &lt;code&gt;Failed&lt;/code&gt;—for UIs, alerts, and runbooks. That phase had to reflect the &lt;em&gt;combination&lt;/em&gt; of all those conditions: “Failed if the store is missing or the job failed; Running if the store and access are ready and the job is scheduled; Completed if the job completed,” etc. The hard part was &lt;strong&gt;managing that mapping&lt;/strong&gt; as we scaled out: multiple controllers or reconciliation steps each setting a subset of conditions, and every consumer (and the controller itself) needing a consistent answer to “what phase is this?” without reimplementing the same condition→phase logic in multiple places. We wanted a single source of truth (the conditions), one place that defined how conditions map to phase, and no drift between what the controller thinks the phase is and what dashboards or alerts assume.&lt;/p&gt;

&lt;p&gt;This is about why &lt;strong&gt;conditions&lt;/strong&gt; are the right primitive, why we still want a &lt;strong&gt;phase&lt;/strong&gt;, and how &lt;strong&gt;phase rules&lt;/strong&gt; let us derive phase from conditions in one place and keep everything in sync, including when multiple controllers touch the same resource.&lt;/p&gt;

&lt;p&gt;The post will consistently refer to airlock and the backup resource as examples. It's easier that way for my memory to keep track of things.&lt;/p&gt;




&lt;h2&gt;
  
  
  Spec and status: desired vs observed
&lt;/h2&gt;

&lt;p&gt;In the Kubernetes API, every resource that has mutable state is split into two parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;spec&lt;/code&gt;&lt;/strong&gt; — The &lt;em&gt;desired&lt;/em&gt; state: what the user or automation asked for. It is the source of truth for “what should be true.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;status&lt;/code&gt;&lt;/strong&gt; — The &lt;em&gt;observed&lt;/em&gt; state: what the system has actually observed. Controllers write here; users typically don’t. It answers “what is true right now.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status" rel="noopener noreferrer"&gt;Kubernetes API conventions&lt;/a&gt; spell this out clearly: the specification is a complete description of the desired state and is persisted with the object; the status summarizes the current state and is “usually persisted with the object by automated processes.” So when you build a controller, you read &lt;code&gt;spec&lt;/code&gt;, do work, and write what you observed into &lt;code&gt;status&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That separation matters. It keeps user intent (spec) from being overwritten by controller updates, allows different access control for spec vs status, and gives clients a stable place to read “what’s actually happening” without parsing controller logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why conditions (and why specific ones)
&lt;/h2&gt;

&lt;p&gt;The standard way to put “what’s actually happening” into status is &lt;strong&gt;conditions&lt;/strong&gt;. A condition is a single observation: a &lt;strong&gt;type&lt;/strong&gt; (e.g. &lt;code&gt;Ready&lt;/code&gt;, &lt;code&gt;JobCompleted&lt;/code&gt;, &lt;code&gt;BucketStoreReady&lt;/code&gt;), a &lt;strong&gt;status&lt;/strong&gt; (&lt;code&gt;True&lt;/code&gt;, &lt;code&gt;False&lt;/code&gt;, or &lt;code&gt;Unknown&lt;/code&gt;), and usually a &lt;strong&gt;reason&lt;/strong&gt; and &lt;strong&gt;message&lt;/strong&gt;. The API conventions describe conditions as &lt;a href="https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties" rel="noopener noreferrer"&gt;“a standard mechanism for higher-level status reporting”&lt;/a&gt;: they let tools and other controllers understand resource state without implementing your controller’s logic. Conditions should “complement more detailed information” in status; they’re the contract for “is this thing ready / failed / still working?”&lt;/p&gt;

&lt;p&gt;So &lt;strong&gt;why specific condition types?&lt;/strong&gt; Because each condition should represent &lt;em&gt;one&lt;/em&gt; observable fact. If you only had a single “Status” condition, you’d lose information: you couldn’t tell “store not ready” from “job failed” from “job still running.” By defining conditions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;BucketStoreReady&lt;/code&gt; — Can we see and use the backup store?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MongoDBAccessRequestReady&lt;/code&gt; — Is database access granted?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;JobScheduled&lt;/code&gt; — Has the backup job been scheduled?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;JobCompleted&lt;/code&gt; — Did the job finish successfully?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;JobFailed&lt;/code&gt; — Did the job fail?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;you give the controller a place to report each fact as it learns it, and you give dashboards, alerts, and other controllers a way to react to specific causes (e.g. “alert when &lt;code&gt;JobFailed&lt;/code&gt; is True” or “show message when &lt;code&gt;BucketStoreReady&lt;/code&gt; is False”).&lt;/p&gt;

&lt;p&gt;The conventions also say: condition type names should describe the &lt;em&gt;current observed state&lt;/em&gt; (adjectives or past-tense verbs like “Ready”, “Succeeded”, “Failed”), and the &lt;em&gt;absence&lt;/em&gt; of a condition should be treated like &lt;code&gt;Unknown&lt;/code&gt;. So you design conditions to be small, explicit observations; the controller sets them as it reconciles; and the rest of the system consumes them without reimplementing your state machine.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why phase when we already have conditions?
&lt;/h2&gt;

&lt;p&gt;Conditions are the right &lt;em&gt;primitive&lt;/em&gt;: they’re granular, extensible, and standardized. But they’re also &lt;em&gt;many&lt;/em&gt;. For a Backup or any other resource for that matter, you might have five or six conditions. For a user or a dashboard, the first question is often: “What state is this in? Running? Failed? Pending?” Answering that from raw conditions means “run the same logic the controller would use to decide the high-level state” — and that logic then lives in every consumer (CLI, UI, Prometheus, runbooks). That duplicates logic and drifts over time.&lt;/p&gt;

&lt;p&gt;A familiar example is the &lt;strong&gt;Node&lt;/strong&gt; in Kubernetes. The node controller and kubelet set several &lt;strong&gt;conditions&lt;/strong&gt; on a Node (e.g. &lt;code&gt;Ready&lt;/code&gt;, &lt;code&gt;DiskPressure&lt;/code&gt;, &lt;code&gt;MemoryPressure&lt;/code&gt;, &lt;code&gt;NetworkUnavailable&lt;/code&gt;). Those conditions drive behavior: the scheduler uses them to decide where to place pods; taints and other node properties can be updated based on conditions. But for “is this node usable?” you need a single picture. The Node’s &lt;strong&gt;phase&lt;/strong&gt; (e.g. &lt;code&gt;Running&lt;/code&gt;) is that summary, the final, high-level state that users and automation care about. So conditions are the levers the system uses to change node properties and make decisions; phase is the outcome you read when you want to know the node’s overall state.&lt;/p&gt;

&lt;p&gt;So we still want a &lt;strong&gt;phase&lt;/strong&gt;: a single, high-level label like &lt;code&gt;Pending&lt;/code&gt;, &lt;code&gt;Running&lt;/code&gt;, &lt;code&gt;Completed&lt;/code&gt;, or &lt;code&gt;Failed&lt;/code&gt; that means “the outcome of applying our rules to the current conditions.” Phase is the &lt;strong&gt;observability contract&lt;/strong&gt;: one field that UIs can show in a column, that alerting can filter on (“alert if phase != Ready”), and that runbooks can branch on, without each consumer reimplementing the condition→state rules.&lt;/p&gt;

&lt;p&gt;The Kubernetes API conventions actually &lt;a href="https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties" rel="noopener noreferrer"&gt;deprecate the old use of &lt;code&gt;phase&lt;/code&gt;&lt;/a&gt; as a first-class state-machine enum in core resources, because adding new enum values breaks compatibility and phase was often used instead of explicit conditions. The better pattern is: &lt;strong&gt;conditions are the source of truth; phase is a derived summary&lt;/strong&gt;. So we keep conditions as the only thing the controller writes, and we &lt;em&gt;compute&lt;/em&gt; phase from those conditions using a clear, declarative set of rules. That way we get the observability benefit of a single phase field without turning phase into an independent state machine.&lt;/p&gt;




&lt;h2&gt;
  
  
  The phase rule idea
&lt;/h2&gt;

&lt;p&gt;Instead of the controller imperatively setting phase in code (“if store missing then phase = Failed”), we &lt;strong&gt;declare&lt;/strong&gt; rules: “phase is &lt;code&gt;Failed&lt;/code&gt; when condition A is True or B is True; phase is &lt;code&gt;Running&lt;/code&gt; when C and D are True and E is False; …”. The relationship is one-way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The controller only updates &lt;strong&gt;conditions&lt;/strong&gt; (e.g. via &lt;code&gt;SetCondition&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phase&lt;/strong&gt; is derived by evaluating an ordered list of &lt;strong&gt;phase rules&lt;/strong&gt; over the current conditions.&lt;/li&gt;
&lt;li&gt;The first rule whose condition matcher matches the current conditions gives the phase; if none match, phase is &lt;code&gt;Unknown&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So conditions drive phase, phase never drives conditions. The same condition set always produces the same phase for a given rule list. Rule order encodes priority (e.g. “Completed” before “Running” before “Failed” before “Pending”). Because phase is always recomputed from the &lt;em&gt;current&lt;/em&gt; conditions, it doesn’t matter which controller or which reconciliation step last wrote a condition—whoever updates conditions, the same rules apply and phase stays consistent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example (conceptually):&lt;/strong&gt; For a Backup custom resource:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Phase &lt;strong&gt;Completed&lt;/strong&gt; when: &lt;code&gt;BucketStoreReady=True&lt;/code&gt;, &lt;code&gt;MongoDBAccessRequestReady=True&lt;/code&gt;, &lt;code&gt;JobCompleted=True&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Phase &lt;strong&gt;Running&lt;/strong&gt; when: store and access are True, &lt;code&gt;JobScheduled=True&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Phase &lt;strong&gt;Failed&lt;/strong&gt; when: store or access is False, or &lt;code&gt;JobFailed=True&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Phase &lt;strong&gt;Pending&lt;/strong&gt; when: store or access is Unknown, or job not yet scheduled.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these is a &lt;strong&gt;phase rule&lt;/strong&gt;: a phase name plus a matcher over conditions. You evaluate the list in order; the first match wins. That’s the phase rule idea: declarative, testable, and a single place to define “what phase means.”&lt;/p&gt;




&lt;h2&gt;
  
  
  The kubernetes-phase-rules package
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/debdutdeb/kubernetes-phase-rules" rel="noopener noreferrer"&gt;kubernetes-phase-rules&lt;/a&gt; module provides exactly that: a small, &lt;strong&gt;experimental&lt;/strong&gt; and &lt;strong&gt;potentially incomplete&lt;/strong&gt; Go library for defining phase rules and computing phase from &lt;code&gt;[]metav1.Condition&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The core type is the &lt;strong&gt;&lt;code&gt;PhaseRule&lt;/code&gt;&lt;/strong&gt; interface. A phase rule has three methods: &lt;strong&gt;&lt;code&gt;Satisfies(conditions)&lt;/code&gt;&lt;/strong&gt; returns true if the given slice of &lt;code&gt;metav1.Condition&lt;/code&gt; matches the rule (e.g. all required conditions present with the right statuses for an AND rule, or at least one for an OR rule); &lt;strong&gt;&lt;code&gt;Phase()&lt;/code&gt;&lt;/strong&gt; returns the phase name this rule represents (e.g. &lt;code&gt;"Running"&lt;/code&gt;, &lt;code&gt;"Failed"&lt;/code&gt;); &lt;strong&gt;&lt;code&gt;ComputePhase(conditions)&lt;/code&gt;&lt;/strong&gt; returns that phase name when the rule is satisfied, and the constant &lt;strong&gt;&lt;code&gt;PhaseUnknown&lt;/code&gt;&lt;/strong&gt; (&lt;code&gt;"Unknown"&lt;/code&gt;) otherwise. So a phase rule is “when these conditions hold, the phase is X”; you build concrete rules with &lt;code&gt;NewPhaseRule(phaseName, matcher)&lt;/code&gt; and pass them to the StatusManager or evaluate them yourself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;PhaseRule&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Satisfies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conditions&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;metav1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Condition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;Phase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;ComputePhase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conditions&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;metav1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Condition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Package &lt;code&gt;rules&lt;/code&gt;&lt;/strong&gt; — You build &lt;strong&gt;phase rules&lt;/strong&gt; with &lt;code&gt;NewPhaseRule(phaseName, matcher)&lt;/code&gt;. Matchers are built from:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ConditionEquals(conditionType, statuses...)&lt;/code&gt; — this condition type must have one of the given statuses (&lt;code&gt;True&lt;/code&gt;, &lt;code&gt;False&lt;/code&gt;, &lt;code&gt;Unknown&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ConditionsAll(...)&lt;/code&gt; — all of the given condition matchers must match (logical AND).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ConditionsAny(...)&lt;/code&gt; — at least one must match (logical OR).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Given a slice of &lt;code&gt;metav1.Condition&lt;/code&gt;, you call &lt;code&gt;rule.Satisfies(conditions)&lt;/code&gt; or &lt;code&gt;rule.ComputePhase(conditions)&lt;/code&gt;; for a list of rules, you iterate in order and take the first satisfied rule’s phase (or &lt;code&gt;PhaseUnknown&lt;/code&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Package &lt;code&gt;conditions&lt;/code&gt;&lt;/strong&gt; — A &lt;strong&gt;StatusManager&lt;/strong&gt; ties this into controller-runtime: you give it the CR’s status conditions pointer, the CR (implementing a small interface with &lt;code&gt;SetPhase&lt;/code&gt; / &lt;code&gt;GetPhase&lt;/code&gt; / &lt;code&gt;SetObservedGeneration&lt;/code&gt;), and the phase rules. When you call &lt;code&gt;SetCondition&lt;/code&gt; or &lt;code&gt;SetConditions&lt;/code&gt;, it updates the condition slice, recomputes phase from the rules, updates the object’s phase and observed generation, and patches status via &lt;code&gt;client.Status().Patch&lt;/code&gt; only when something changed. So the controller only sets conditions; the manager keeps phase and status in sync.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Yes, I ran out of name ideas.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Object2&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;SetPhase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phase&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;GetPhase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;SetObservedGeneration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generation&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The module is &lt;strong&gt;experimental&lt;/strong&gt; and kept intentionally simple: minimal API, minimal dependencies, no feature creep. You can use it as a starting point and adapt the rules or the manager to your CRDs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it in the browser
&lt;/h2&gt;

&lt;p&gt;You can see phase rules in action and experiment with conditions and rule order in the interactive demo, load the templates of build your own rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Demo (GitHub Pages):&lt;/strong&gt; &lt;a href="https://debdutdeb.github.io/kubernetes-phase-rules" rel="noopener noreferrer"&gt;https://debdutdeb.github.io/kubernetes-phase-rules/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The demo lets you define condition types, set their statuses, and define an ordered list of phase rules (with AND/OR and allowed statuses). It computes the resulting phase so you can build intuition for how conditions map to phase and why rule order matters.&lt;/p&gt;




</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>opensource</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Simplifying Tenable.io Agent Deployment in Kubernetes Clusters</title>
      <dc:creator>Igor Rincon</dc:creator>
      <pubDate>Wed, 24 May 2023 20:06:50 +0000</pubDate>
      <link>https://forem.com/rocketchat/simplifying-tenableio-agent-deployment-in-kubernetes-clusters-lg9</link>
      <guid>https://forem.com/rocketchat/simplifying-tenableio-agent-deployment-in-kubernetes-clusters-lg9</guid>
      <description>&lt;p&gt;Deploying and managing the Tenable.io agent in a Kubernetes cluster nodes can be a manual and time-consuming when you want to guarantee that agents are automatically installed in new nodes and avoid permanment writing of files inside the node's storage. In this article, we will introduce a solution that automates the deployment and management of the Tenable.io agent as a &lt;strong&gt;DaemonSet&lt;/strong&gt; in a Kubernetes cluster. This solution simplifies the installation process and provides visibility into the security posture of your cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problems
&lt;/h2&gt;

&lt;p&gt;Before we dive into the solution, let's understand the problems that we're trying to cover with this solution:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. First problem:
&lt;/h3&gt;

&lt;p&gt;"I need to start a manual process of installing the tenable.io agent everytime that a node joins my cluster"&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Second problem:
&lt;/h3&gt;

&lt;p&gt;"I need to implement tenable.io agent in a pod and use this pod to scan the node filesystem"&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Third problem
&lt;/h3&gt;

&lt;p&gt;"I don't want to have a process writing permanent files in my node's storage"&lt;/p&gt;

&lt;h2&gt;
  
  
  Our way to solve it:
&lt;/h2&gt;

&lt;p&gt;Github Repository: &lt;a href="https://github.com/RocketChat/TenableAgent-Daemonset"&gt;https://github.com/RocketChat/TenableAgent-Daemonset&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Having tenable.io agent implemented as a kubernetes Daemonset can be a solution to the problems mentioned above. It automates the deployment of the Tenable.io agent as a DaemonSet in a Kubernetes cluster and streamlines the process of scanning the node's filesystem without writing permanent files in the node's storage, if the DaemonSet is gone, the files will be gone. To solve it, we create a k8s manifest with the specifications:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Unprivileged DaemonSet POD:&lt;/strong&gt; The kubernetes manifest creates an unprivileged DaemonSet pod that runs the Tenable.io agent. This pod will be deployed on every node in the Kubernetes cluster.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Filesystem Access:&lt;/strong&gt; To enable the Tenable.io agent to scan the node's filesystem, the manifest changes the filesystem root of the agent process to the node's filesystem. This ensures that the agent has the necessary access to perform security scans effectively&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ephemeral Filesystem Writing:&lt;/strong&gt; It's simple: If the Daemonset is gone, the files will be removed from the node's filesystem.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Deployment Steps
&lt;/h3&gt;

&lt;p&gt;To deploy this manifest and start using the Tenable.io agent in your Kubernetes cluster, follow these steps:&lt;/p&gt;

&lt;h4&gt;
  
  
  Prerequisites
&lt;/h4&gt;

&lt;p&gt;Before getting started, make sure you have the following prerequisites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sealed Secrets implemented in your cluster.&lt;/li&gt;
&lt;li&gt;kubectl installed and configured to access your cluster.&lt;/li&gt;
&lt;li&gt;A Tenable.io link key to link the agent with your Tenable.io Manager.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Preparing files and applying it
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Create a Sealed Secrets key inside your cluster. Replace &lt;strong&gt;'YOUR TENABLE KEY GOES HERE'&lt;/strong&gt; with your Tenable.io sync token and convert it to base64 format:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo -n '{"link":{"host": "cloud.tenable.com","port": 443,"key": "YOUR TENABLE KEY GOES HERE","name": "$NODE_NAME", "groups": ["agent-group"]}}' | base64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Insert the base64 encoded string in the &lt;em&gt;'secrets.yaml'&lt;/em&gt; file. Use &lt;strong&gt;'kubeseal'&lt;/strong&gt; to encrypt it:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat secret.yaml | kubeseal \
    --controller-namespace kube-system \
    --controller-name sealed-secrets \
    --format yaml \
    &amp;gt; sealed-secret.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Apply the sealed secrets to your cluster:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl apply -f sealed-secret.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Deploy the DaemonSet using the following command:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl apply -f manifest/tenable-pod.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a few minutes, you should be able to see your node information in the tenable.io sensors list.&lt;/p&gt;

&lt;p&gt;If you think that you have something to improve this solution, feel free to PR. We will review it and approve as soon as possible.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Implementing Client SSL Authentication</title>
      <dc:creator>Aaron Ogle</dc:creator>
      <pubDate>Wed, 24 May 2023 13:06:19 +0000</pubDate>
      <link>https://forem.com/rocketchat/how-to-set-up-client-ssl-certificate-authentication-for-rocketchat-836</link>
      <guid>https://forem.com/rocketchat/how-to-set-up-client-ssl-certificate-authentication-for-rocketchat-836</guid>
      <description>&lt;p&gt;Are you looking to add an extra layer of security to access Rocket.Chat? One way to do this is by implementing client SSL certificate authentication. This authentication method requires clients to present a valid SSL certificate to authenticate themselves to the server.&lt;/p&gt;

&lt;p&gt;In this post, we will walk through the steps to set up client SSL certificate authentication using Nginx.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A server running Ubuntu

&lt;ul&gt;
&lt;li&gt;Allow HTTP/HTTPS/ssh in security group&lt;/li&gt;
&lt;li&gt;Create a public IP and associate it.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;A domain&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Domain pointed to the server&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Install Docker
&lt;/h2&gt;

&lt;p&gt;Lets do a quick and easy install of Docker if you haven't already.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -L https://get.docker.com | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now Lets add your user to the docker group so you don't have to use &lt;code&gt;sudo&lt;/code&gt; before every docker command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo usermod -aG docker $USER
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normally here we'd say logout and back in.. but I don't want to mess with it so I use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;newgrp docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Install Rocket.Chat
&lt;/h2&gt;

&lt;p&gt;Now that we have Docker installed lets get Rocket.Chat up and running.&lt;/p&gt;

&lt;p&gt;Start off with creating a rocketchat directory and grabbing the docker-compose file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir rocketchat &amp;amp;&amp;amp; cd "$_"
curl -L https://go.rocket.chat/i/docker-compose.yml -O
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next lets fire it up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker compose up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If impatient or only came here to install Rocket.Chat you could access on port 3000 if your firewall is open.  But I suspect you came here for seeing how to do client SSL.  So lets carry on!&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Install Nginx
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install -y nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Install Certbot
&lt;/h2&gt;

&lt;p&gt;To help us get a valid certificate we are going to use letsencrypt but using a tool called certbot.  This will help us make sure to keep the certificate valid as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo snap install --classic certbot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now use certbot to generate and plug everything up for letsencrypt and nginx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo certbot --nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll be asked to provide a valid email and the domain set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Generate Certificate Authority (CA) Certificates
&lt;/h2&gt;

&lt;p&gt;In order to do client SSL Authentication we’re going to need a CA.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate a key for your CA:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl genrsa -des3 -out ca.key 4096
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Generate a certificate for your CA:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl req -new -x509 -days 365 -key ca.key -out ca.crt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Note what you’ve entered for Country, State, Locality, and Organization; you’ll want these to match later when you renew the certificate.&lt;/li&gt;
&lt;li&gt;Do not enter a common name (CN) for the certificate.&lt;/li&gt;
&lt;li&gt;Email can be omitted.&lt;/li&gt;
&lt;li&gt;Note renewing certificate involves running the same command. If you need to remember what options you chose you can run:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl x509 -in ca.crt -noout -text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Move CA cert
&lt;/h3&gt;

&lt;p&gt;To: &lt;code&gt;/etc/ssl/private/client-cert-ca.crt&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Update Nginx config
&lt;/h3&gt;

&lt;p&gt;We need to add CA cert, turn on client ssl authentication and add location block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssl_client_certificate /etc/ssl/private/client-cert-ca.crt;
ssl_verify_client optional;

location / {
   if ($ssl_client_verify != SUCCESS) {
     return 403;
   }

    proxy_pass http://localhost:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Nginx-Proxy true;
    proxy_redirect off;
  }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6: Issue Client SSL Certificates for users
&lt;/h2&gt;

&lt;p&gt;You can have your users perform most of these steps if you want. But the following are the steps needed to create a certificate to present as client authentication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate key for user
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl genrsa -des3 -out user.key 4096
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Generate a CSR
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl req -new -key user.key -out user.csr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;A number of questions will be asked; answer each one, including the Common Name (CN) and email address. The CSR needs to be sent to the admin (or you if you are doing this for the user).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sign CSR with CA
&lt;/h3&gt;

&lt;p&gt;As the admin, take the CSR given to you or generated by you and sign the CSR and create valid certificate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl x509 -req -days 365 -in user.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out user.crt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;You’ll want to increment the serial number with each signing. Once the certificate expires, a new CSR doesn’t need to be recreated; the same one can be signed, which will create a new certificate tied to that public key.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Return Certificate
&lt;/h3&gt;

&lt;p&gt;The signed certificate (user.crt) can now be sent back to the user along with the CA cert(ca.crt).&lt;/p&gt;

&lt;p&gt;To be able to use in browsers and mobile generate a pkcs #12 using the user cert and key along with the Ca:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl pkcs12 -export -out user.pfx -inkey user.key -in user.crt -certfile ca.crt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beware if you intend to install on Mac or iOS, you may need to use an older version of OpenSSL. More info here: &lt;a href="https://developer.apple.com/forums/thread/697030?answerId=710429022#710429022"&gt;https://developer.apple.com/forums/thread/697030?answerId=710429022#710429022&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Access Rocket.Chat using Client SSL Certificate
&lt;/h2&gt;

&lt;p&gt;To access your application with client certificate authentication:&lt;/p&gt;

&lt;h3&gt;
  
  
  On iOS:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Add the file on Files app (depending on what you do, iOS will try to install it on the whole OS. e.g. copying from airdrop)&lt;/li&gt;
&lt;li&gt;Go to new server screen and try the server URL just to confirm (an error message should show because you haven't applied the cert yet)&lt;/li&gt;
&lt;li&gt;Tap &lt;code&gt;apply your certificate&lt;/code&gt; on the bottom of the screen and select it from the Files app&lt;/li&gt;
&lt;li&gt;Try again, and it should navigate to the workspace info&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  On Android:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Install the certificate&lt;/li&gt;
&lt;li&gt;Go to new server screen and try the server URL just to confirm (an error message should show because you haven't applied the cert yet)&lt;/li&gt;
&lt;li&gt;Tap &lt;code&gt;apply your certificate&lt;/code&gt;, and the OS should prompt you to select a cert&lt;/li&gt;
&lt;li&gt;Select it&lt;/li&gt;
&lt;li&gt;Tap connect, and it should navigate to the workspace info&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  On Firefox:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Open Firefox and click on the three horizontal lines in the top-right corner of the window.&lt;/li&gt;
&lt;li&gt;Select "Preferences".&lt;/li&gt;
&lt;li&gt;In the left-hand menu, click on "Privacy &amp;amp; Security".&lt;/li&gt;
&lt;li&gt;Scroll down to the "Certificates" section and click on "View Certificates".&lt;/li&gt;
&lt;li&gt;Click on the "Your Certificates" tab.&lt;/li&gt;
&lt;li&gt;Click on "Import".&lt;/li&gt;
&lt;li&gt;Browse to the location of your client certificate file and select it.&lt;/li&gt;
&lt;li&gt;Enter the password for the certificate if prompted.&lt;/li&gt;
&lt;li&gt;Click "OK".&lt;/li&gt;
&lt;li&gt;Your client certificate should now be imported and ready to use in Firefox.&lt;/li&gt;
&lt;li&gt;Visit the address and you should be prompted to select the certificate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Congratulations! You have successfully set up client SSL certificate authentication for &lt;a href="http://Rocket.Chat"&gt;Rocket.Chat&lt;/a&gt; using Nginx.&lt;/p&gt;

&lt;p&gt;Extremely useful Reference: &lt;a href="https://fardog.io/blog/2017/12/30/client-side-certificate-authentication-with-nginx/"&gt;https://fardog.io/blog/2017/12/30/client-side-certificate-authentication-with-nginx/&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
