<?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>DEV Community: Farzad Sadeghi</title>
    <description>The latest articles on DEV Community by Farzad Sadeghi (@terminaldweller).</description>
    <link>https://dev.to/terminaldweller</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%2Fuser%2Fprofile_image%2F1017367%2F831a1f13-01be-42ea-ae56-bfabc2bcb5cf.png</url>
      <title>DEV Community: Farzad Sadeghi</title>
      <link>https://dev.to/terminaldweller</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/terminaldweller"/>
    <language>en</language>
    <item>
      <title>Make the zsh Prompt Go Faster</title>
      <dc:creator>Farzad Sadeghi</dc:creator>
      <pubDate>Sun, 29 Dec 2024 16:36:31 +0000</pubDate>
      <link>https://dev.to/terminaldweller/make-the-zsh-prompt-go-faster-5hal</link>
      <guid>https://dev.to/terminaldweller/make-the-zsh-prompt-go-faster-5hal</guid>
      <description>&lt;h2&gt;
  
  
  How to make the zsh prompt faster
&lt;/h2&gt;

&lt;p&gt;In this post, we are going to take a look at 3 ways of making the zsh prompt return faster.&lt;br&gt;&lt;br&gt;
We will talk about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;plugin precompilation&lt;/li&gt;
&lt;li&gt;caching&lt;/li&gt;
&lt;li&gt;async functions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are obviously other methods, of course, but you can find them on almost any other blog talking about zsh prompts so for the sake of brevity, I'm not going to repeat them(an example: use &lt;a href="https://github.com/Schniz/fnm" rel="noopener noreferrer"&gt;fnm&lt;/a&gt; instead of &lt;a href="https://github.com/nvm-sh/nvm" rel="noopener noreferrer"&gt;nvm&lt;/a&gt; or why would you not use &lt;a href="https://github.com/jdx/mise" rel="noopener noreferrer"&gt;mise&lt;/a&gt;). There is also a faster syntax highlighter for zsh. so on and so forth.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using the color red for all segments in your zsh prompt will make it go faster because, as everyone should know by now, "red ones go faster"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before we start though, a bit of reasoning on why I would be doing the things the way I am doing them. A prompt written entirely in a faster language will be faster, yes, but I'm not willing to write a couple of thousand lines of code to get my current shell prompt(the major issue here being maintenance throughout the years,i.e. technical debt). My shell prompt is pretty critical for me so I want to be in complete control over it which means I am unwilling to use ready-made prompts with thousands of lines of code which don't have half the segments that I need.&lt;/p&gt;

&lt;p&gt;And finally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;If you say you care about your zsh prompt's performance then that means that you already have `zmodload zsh/zprof` in your rc file.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you talk about performance, then you have to have benchmarks and profilers. &lt;code&gt;zprof&lt;/code&gt; is built in.&lt;br&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Plugin Precompilation
&lt;/h2&gt;

&lt;p&gt;zsh can compile zsh scripts using the builtin &lt;code&gt;zcompile&lt;/code&gt; into wordcode. This will have the effect of having faster parsing.&lt;br&gt;
The way we use this to get a faster prompt is to explicitly ask zsh to compile certain chunky plugins(think your &lt;a href="https://github.com/zsh-users/zsh-syntax-highlighting" rel="noopener noreferrer"&gt;syntax highlighters&lt;/a&gt; and &lt;a href="https://github.com/zsh-users/zsh-autosuggestions" rel="noopener noreferrer"&gt;completion&lt;/a&gt; plugins) into wordcode.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;zcompile-many ~/.oh-my-zsh/custom/plugins/zsh-autosuggestions/&lt;span class="o"&gt;{&lt;/span&gt;zsh-autosuggestions.zsh,src/&lt;span class="k"&gt;**&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;.zsh&lt;span class="o"&gt;}&lt;/span&gt;
zcompile-many ~/zsh-async.git/v1.8.5/async.zsh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can read more about &lt;code&gt;zcompile&lt;/code&gt; in &lt;code&gt;man 1 zshall&lt;/code&gt;, under &lt;code&gt;SHELL BUILTIN COMMANDS&lt;/code&gt;, under &lt;code&gt;zcompile&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caching
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Eval Caching
&lt;/h3&gt;

&lt;p&gt;First we will talk about evalcaching: caching the results of &lt;code&gt;eval&lt;/code&gt;s. This is a thing because some heavier/older version managers use this to inject themselves into your shell and some of them just take too much time.&lt;br&gt;
For those kinds of plugins you can use &lt;a href="https://github.com/mroth/evalcache/" rel="noopener noreferrer"&gt;evalcache&lt;/a&gt;.&lt;br&gt;
After sourcing the script, you can use it like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;_evalcache rbenv init -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We basically just swap out all instances of &lt;code&gt;eval&lt;/code&gt; with &lt;code&gt;_evalcache&lt;/code&gt;.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Admittedly, this is very limited in scope and it has no smart way of redoing the cache. A function is provided, &lt;code&gt;_evalcache_clear&lt;/code&gt;, which will clear the cache, which in turn will result in the cache being regenerated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ye Old Result Caching
&lt;/h3&gt;

&lt;p&gt;You can also just cache some results in a file and then just read from a file. Now how you update the cache is really up to you. You can use a user service, you can use cron jobs. You can even run it in the prompt itself or run the function asynchronously which we will get to in the next section.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Below is a simple example of implementing such a cache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;caching_function&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;CACHE_OUTPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;TIME_CACHE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;

  &lt;span class="c"&gt;# check if the cache result is too old&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="k"&gt;$((&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;stat&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;%Y &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CACHE_OUTPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; TIME_CACHE&lt;span class="k"&gt;))&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="c"&gt;# the cache is still valid, we don't need to do anything&lt;/span&gt;
    :
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="c"&gt;# the cache value is too old&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nv"&gt;OUTPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;"your function that generates the output goes here"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTPUT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OUTPUT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CACHE_OUTPUT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="k"&gt;fi
    fi
  fi

  &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CACHE_OUTPUT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our function has two arguments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first arg tells it where to store the cache. I keep mine under &lt;code&gt;/tmp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The second is the amount of time the cache result will be valid for.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The function will check the last time the file was updated and compare that using the &lt;code&gt;CACHE_TIME&lt;/code&gt; var and the current time. If the cache value is new enough, we just return the cache value and we are done. If the cache is old, we run the function that generates the result, update the cache and return the new result.&lt;br&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Async Functions
&lt;/h2&gt;

&lt;p&gt;This is probably the most important one. Again, no surprises here. We will use a zsh async library,&lt;a href="https://github.com/mafredri/zsh-async" rel="noopener noreferrer"&gt;zsh-async&lt;/a&gt;, to have some async segments in our zsh prompt.&lt;/p&gt;

&lt;p&gt;Let's take this zsh function from my prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker_compose_running_pwd&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;cwd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;docker compose &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; json | jq &lt;span class="s1"&gt;'.[].ConfigFiles'&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'"'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;array&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;(@f)&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;list&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;elem &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;cwd&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;elem&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="k"&gt;then
      &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\U&lt;/span&gt;&lt;span class="s2"&gt;1F7E6"&lt;/span&gt;
      &lt;span class="k"&gt;return
    else&lt;/span&gt;
        &lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;fi
  done&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function prints a blue square if there is a docker compose file running from the current path we are in and outputs nothing if we are not.&lt;br&gt;&lt;br&gt;
Under normal circumstances this does not require to be an async segment but as soon as you switch your docker context to a remote docker host, then you will start to feel the pain.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;We first define a start function that registers an async worker with the library and then define a callback function that gets called when the async runner returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;_async_dcpr_start&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  async_start_worker dcpr_info
  async_register_callback dcpr_info _async_dcpr_info_done
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The start function has nothing special in it. We just register an async worker and then register a callback function that will be called when the runner returns. Please do note that &lt;code&gt;async_register_callback&lt;/code&gt; will require two arguments, the name of the async runner and the name of our callback function.&lt;br&gt;&lt;br&gt;
Next we will define our callback function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;_async_dcpr_info_done&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;#first part&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;job&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;return_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;more&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$6&lt;/span&gt;
  &lt;span class="c"&gt;#second part&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt;  &lt;span class="nv"&gt;$job&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'[async]'&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$return_code&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 2 &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;_async_dcpr_start
      &lt;span class="k"&gt;return
    fi
  fi&lt;/span&gt;
  &lt;span class="c"&gt;#third part&lt;/span&gt;
  &lt;span class="nv"&gt;dcpr_info_msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$stdout&lt;/span&gt;
  &lt;span class="c"&gt;#fourth part&lt;/span&gt;
  &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$more&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; 1 &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; set-prompt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; zle reset-prompt
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The callback functions gets 6 arguments(copied from &lt;a href="https://github.com/mafredri/zsh-async/blob/main/README.md" rel="noopener noreferrer"&gt;here&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;$1 job name, e.g. the function passed to async_job&lt;/li&gt;
&lt;li&gt;$2 return code

&lt;ul&gt;
&lt;li&gt;Returns -1 if return code is missing, this should never happen, if it does, you have likely run into a bug. Please open a new issue with a detailed description of what you were doing.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;$3 resulting (stdout) output from job execution&lt;/li&gt;

&lt;li&gt;$4 execution time, floating point e.g. 0.0076138973 seconds&lt;/li&gt;

&lt;li&gt;$5 resulting (stderr) error output from job execution&lt;/li&gt;

&lt;li&gt;$6 has next result in buffer (0 = buffer empty, 1 = yes)

&lt;ul&gt;
&lt;li&gt;This means another async job has completed and is pending in the buffer, it's very likely that your callback function will be called a second time (or more) in this execution. It's generally a good idea to e.g. delay prompt updates (zle reset-prompt) until the buffer is empty to prevent strange states in ZLE.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The function itself is straightforward. I like to rename the shell arguments so i have to deal with a name a couple of months from the time of writing the function and not some random numbers.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Next is the part where we handle the errors. These are the errors returned by the async runner. I like to call the start function of the async job again to get it to start again. This is not, generally speaking, a good idea since if something is broken and a rerun won't fix it, you end up in a loop. This is essentially my version of having the async job "failing loudly".&lt;br&gt;&lt;/p&gt;

&lt;p&gt;The third part is where we assign the stdout that our function, &lt;code&gt;docker_compose_running_pwd&lt;/code&gt;, made to a "global" variable. This variable,&lt;code&gt;dcpr_info_msg&lt;/code&gt;, is the variable that we will use in our prompt.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;The fourth part is the most important part. The first condition checks for an empty buffer. If the buffer is not empty, it means we have more async jobs that have finished and are waiting for their turn, in which case it will not update the prompt. If, however the prompt buffer is empty, then we will &lt;code&gt;set&lt;/code&gt; and &lt;code&gt;reset&lt;/code&gt; the prompt to get an updated prompt displayed when the buffer is empty.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;This is ideal, because, first and foremost, the library's documentation tells us to do that to avoid having &lt;code&gt;zle&lt;/code&gt; ending up in a weird state. The other reason would be to avoid unnecessary zle resets to cut down on needless flickering and to save us some CPU cycles.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;set-prompt&lt;/code&gt; is a function that I use to set the actual prompt. You may not need to call that or whatever equivalent that you have. In my case, I have to do it since my prompt lies on the fancier side of prompts and a change in the amount of characters in the prompts(because an async function is providing its output after the initial prompt display) will need to be taken into account so that's why i run the prompt after everything(all the async functions) has returned, so that the final character lengths are known and correct.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;In the next section, we simply call the init function of the library and then call our own start function after that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;async_init
_async_dcpr_start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this section we add our async job to the precmd hook for zsh so that our async job runs on the precmd hook on every prompt. The &lt;code&gt;precmd&lt;/code&gt; hook is executed before the prompt is displayed.&lt;br&gt;&lt;br&gt;
Also please do note that this is where we actually tell the async runner what function to actually run. Moreover, this is where we pass any arguments that may or may not be needed by the said function.&lt;br&gt;&lt;br&gt;
Do keep in mind that the async execution environment our function will run in is not the same as the one your shell prompt will be run in. This means that env vars will not carry over. In our example our docker compose function cannot get the current working directory by just accessing the &lt;code&gt;$PWD&lt;/code&gt; env var so we will have to pass &lt;code&gt;$PWD&lt;/code&gt; to it as a function argument manually.&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;add-zsh-hook precmd &lt;span class="o"&gt;(){&lt;/span&gt;
  async_job dcpr_info docker_compose_running_pwd &lt;span class="nv"&gt;$PWD&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This final part serves two purposes. First, it clears the prompt var on changing a directory, so that we don't get a wrong result until we get a new result on a new prompt. Second, this also serves as the definition for our global var that we will use in the prompt.&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;add-zsh-hook chpwd&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;dcpr_info_msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's everything put together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker_compose_running_pwd&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;cwd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;docker compose &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; json | jq &lt;span class="s1"&gt;'.[].ConfigFiles'&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'"'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;array&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;(@f)&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;list&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;elem &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;cwd&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;elem&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="k"&gt;then
      &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\U&lt;/span&gt;&lt;span class="s2"&gt;1F7E6"&lt;/span&gt;
      &lt;span class="k"&gt;return
    else&lt;/span&gt;
        &lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;fi
  done&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

_async_dcpr_start&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  async_start_worker dcpr_info
  async_register_callback dcpr_info _async_dcpr_info_done
&lt;span class="o"&gt;}&lt;/span&gt;

_async_dcpr_info_done&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;#first part&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;job&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;return_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;more&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$6&lt;/span&gt;
  &lt;span class="c"&gt;#second part&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt;  &lt;span class="nv"&gt;$job&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'[async]'&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$return_code&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 2 &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;_async_dcpr_start
      &lt;span class="k"&gt;return
    fi
  fi&lt;/span&gt;
  &lt;span class="c"&gt;#third part&lt;/span&gt;
  &lt;span class="nv"&gt;dcpr_info_msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$stdout&lt;/span&gt;
  &lt;span class="c"&gt;#fourth part&lt;/span&gt;
  &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$more&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; 1 &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; set-prompt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; zle reset-prompt
&lt;span class="o"&gt;}&lt;/span&gt;

async_init
_async_dcpr_start

add-zsh-hook precmd &lt;span class="o"&gt;(){&lt;/span&gt;
  async_job dcpr_info docker_compose_running_pwd &lt;span class="nv"&gt;$PWD&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

add-zsh-hook chpwd &lt;span class="o"&gt;(){&lt;/span&gt;
  &lt;span class="nv"&gt;dcpr_info_msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>A Disposable Firefox Instance</title>
      <dc:creator>Farzad Sadeghi</dc:creator>
      <pubDate>Wed, 26 Jun 2024 19:30:08 +0000</pubDate>
      <link>https://dev.to/terminaldweller/a-disposable-firefox-instance-3p03</link>
      <guid>https://dev.to/terminaldweller/a-disposable-firefox-instance-3p03</guid>
      <description>&lt;h2&gt;
  
  
  Making a Disposable Firefox Instance
&lt;/h2&gt;

&lt;p&gt;We want to make a disposable firefox instance.&lt;br&gt;&lt;br&gt;
Why firefox? well the only other choice is chromium really. Mozilla are no choir boys either. Basically we are choosing between the lesser of two evils here. There is also the whole google killing off manifest v2.&lt;br&gt;&lt;br&gt;
Qutebrowser and netsurf are solid but for this one, we will choose something that has more compatibility.&lt;br&gt;&lt;br&gt;
Now let's talk about the requirements and goals for this lil undertaking of ours:&lt;/p&gt;
&lt;h2&gt;
  
  
  Requirements and Goals
&lt;/h2&gt;

&lt;p&gt;We want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the instance to be ephemeral. This will prevent any persistent threat to remain on the VM.&lt;/li&gt;
&lt;li&gt;the instance to be isolated from the host.&lt;/li&gt;
&lt;li&gt;to prevent our IP address from being revealed to the websites we visit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will not be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;doing any fingerprint-resisting. In case someone wants to do it, here's a good place to start: &lt;a href="https://github.com/arkenfox/user.js/"&gt;arkenfox's user.js&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;we are trying to keep our IP from being revealed to the websites we visit. We don't care whether a VPN provider can be subpoenaed or not. Otherwise, needless to say, use your own VPN server but that will limit the IP choices you have. Trade-offs people, trade-offs. There is also the better choice, imho, which is use a SOCKS5 proxy.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Isolation and Sandboxing
&lt;/h3&gt;

&lt;p&gt;We will be practicing compartmentalization. This makes it harder for threats to spread. There are more than one way to do this in the current Linux landscape. We will be using a virtual machine and not a container. Needless to say, defense in depth is a good practice so in case your threat model calls for it, one could run firefox in a container inside the VM but for our purposes running inside a virtual machine is enough.&lt;br&gt;&lt;br&gt;
To streamline the process, we will be using vagrant to provision the VM. Like already mentioned, we will use Vagrant's plugin for libvirt to build/manage the VM which in turn will use qemu/kvm as the hypervisor.&lt;br&gt;&lt;br&gt;
We value transparency so we will use an open-source stack for the virtualisation: Vagrant+libvirt+qemu/kvm&lt;br&gt;&lt;br&gt;
The benefits of using an open-source backend include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;we don't have to worry about any backdoors in the software. There is a big difference between "they &lt;strong&gt;probably&lt;/strong&gt; don't put backdoors into their software" and "there are no backdoors on this piece of software"(the xz incident non-withstanding)&lt;/li&gt;
&lt;li&gt;we don't have to deal with very late and lackluster responses to security vulnerabilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yes. We just took shots at two specific hypervisors. If you know, you know.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Now lets move on to the base for the VM.&lt;br&gt;&lt;br&gt;
We need something small for two reasons: a smaller attack surface and a smaller memory footprint(yes. A smaller memory-footprint. We will talk about this a bit later).&lt;br&gt;&lt;br&gt;
So the choice is simple if we are thinking of picking a linux distro. We use an alpine linux base image. We could pick an openbsd base. That has the added benefit of the host and the guest not running the same OS which makes it harder for the threats to break isolation but for the current iteration we will be using alpine linux.&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  IP Address Leak prevention
&lt;/h3&gt;

&lt;p&gt;The choice here is rather simple:&lt;br&gt;&lt;br&gt;
We either decide to use a VPN or a SOCKS5 proxy. You could make your own VPN and or SOCKS5 proxy. This IS the more secure option but will limit the ip choices we have. If your threat model calls for it, then by all means, take that route. For my purposes using a VPN provider is enough. We will be using mullvad vpn. Specifically, we will be using the openvpn config that mullvad generates for us. We will not be using the mullvad vpn app mostly because a VPN app is creepy.&lt;br&gt;&lt;br&gt;
We will also be implementing a kill-switch for the VPN. In case the VPN fails at any point, we don't want to leak our IP address. A kill-switch makes sure nothing is sent out when the VPN fails.&lt;br&gt;
We will use ufw to implement the kill-switch feature. This is similar to what &lt;a href="https://tails.net/contribute/design/#index18h3"&gt;tails OS does&lt;/a&gt; as in, it tries to route everything through tor but it also blocks any non-tor traffic, thus ensuring there are no leaks. We will be doing the same.&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Non-Persistance
&lt;/h3&gt;

&lt;p&gt;We are running inside a VM so in order to achieve non-persistence we could just make a new VM instance, run that and after we are done with the instance, we can just destroy it. We will be doing just that but we will be using a &lt;code&gt;tmpfs&lt;/code&gt; filesystem and put our VM's disk on that. This has a couple of benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RAM is faster than disk. Even faster than an nvme drive&lt;/li&gt;
&lt;li&gt;RAM is volatile&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One thing to be wary of is swap. In our case we will be using the newer &lt;code&gt;tmpfs&lt;/code&gt; which will use swap if we go over our disk limit so keep this in mind while making the tmpfs mount. Please note that there are ways around this as well. One could use the older &lt;code&gt;ramfs&lt;/code&gt; but in my case this is not necessary since I'm using zram for my host's swap solution. This means that the swap space will be on the RAM itself so hitting the swap will still mean we never hit the disk.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;To mount a tmpfs, we can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="nt"&gt;-t&lt;/span&gt; tmpfs &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4096M tmpfs /tmp/tmpfs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember we talked about a smaller memory footprint? This is why. An alpine VM with firefox on top of it is smaller both in disk-size and memory used(mostly because of alpine using libmusl instead of glibc).&lt;br&gt;&lt;br&gt;
The above command will mount a 4GB tmpfs on &lt;code&gt;/tmp/tmpfs&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
Next we want to create a new storage pool for libvirt so that we can specify the VM to use that in Vagrant.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh pool-define-as tmpfs_pool /tmp/tmpfs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then start the pool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virsh pool-start tmpfs_pool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementing the Kill-Switch Using UFW
&lt;/h2&gt;

&lt;p&gt;The concept is simple. We want to stop sending packets to any external IP address once the VPN is down.&lt;br&gt;&lt;br&gt;
In order to achieve this, we will fulfill a much stricter requirement. We will go for a tails-like setup, in that the only allowed external traffic will be to the IP address of the VPN server(s).&lt;br&gt;&lt;br&gt;
Here's what that will look like:&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ufw &lt;span class="nt"&gt;--force&lt;/span&gt; reset
ufw default deny incoming
ufw default deny outgoing
ufw allow &lt;span class="k"&gt;in &lt;/span&gt;on tun0
ufw allow out on tun0
&lt;span class="c"&gt;# enable libvirt bridge&lt;/span&gt;
ufw allow &lt;span class="k"&gt;in &lt;/span&gt;on eth0 from 192.168.121.1 proto tcp
ufw allow out on eth0 to 192.168.121.1 proto tcp
&lt;span class="c"&gt;# server block&lt;/span&gt;
ufw allow out on eth0 to 185.204.1.174 port 443 proto tcp
ufw allow &lt;span class="k"&gt;in &lt;/span&gt;on eth0 from 185.204.1.174 port 443 proto tcp
ufw allow out on eth0 to 185.204.1.176 port 443 proto tcp
ufw allow &lt;span class="k"&gt;in &lt;/span&gt;on eth0 from 185.204.1.176 port 443 proto tcp
ufw allow out on eth0 to 185.204.1.172 port 443 proto tcp
ufw allow &lt;span class="k"&gt;in &lt;/span&gt;on eth0 from 185.204.1.172 port 443 proto tcp
ufw allow out on eth0 to 185.204.1.171 port 443 proto tcp
ufw allow &lt;span class="k"&gt;in &lt;/span&gt;on eth0 from 185.204.1.171 port 443 proto tcp
ufw allow out on eth0 to 185.212.149.201 port 443 proto tcp
ufw allow &lt;span class="k"&gt;in &lt;/span&gt;on eth0 from 185.212.149.201 port 443 proto tcp
ufw allow out on eth0 to 185.204.1.173 port 443 proto tcp
ufw allow &lt;span class="k"&gt;in &lt;/span&gt;on eth0 from 185.204.1.173 port 443 proto tcp
ufw allow out on eth0 to 193.138.7.237 port 443 proto tcp
ufw allow &lt;span class="k"&gt;in &lt;/span&gt;on eth0 from 193.138.7.237 port 443 proto tcp
ufw allow out on eth0 to 193.138.7.217 port 443 proto tcp
ufw allow &lt;span class="k"&gt;in &lt;/span&gt;on eth0 from 193.138.7.217 port 443 proto tcp
ufw allow out on eth0 to 185.204.1.175 port 443 proto tcp
ufw allow &lt;span class="k"&gt;in &lt;/span&gt;on eth0 from 185.204.1.175 port 443 proto tcp
&lt;span class="nb"&gt;echo &lt;/span&gt;y | ufw &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we forcefully reset ufw. This makes sure we are starting from a known state.&lt;br&gt;&lt;br&gt;
Second, we disable all incoming and outgoing traffic. This makes sure our default policy for some unforseen scenario is to deny traffic leaving the VM.&lt;br&gt;&lt;br&gt;
Then we allow traffic through the VPN interface, tun0.&lt;br&gt;&lt;br&gt;
Finally, in my case and because of libvirt, we allow traffic to and from the libvirt bridge, which in my case in 192.168.121.1.&lt;br&gt;&lt;br&gt;
Then we add two rules for each VPN server. One for incoming and one for outgoing traffic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ufw allow out on eth0 to 185.204.1.174 port 443 proto tcp
ufw allow &lt;span class="k"&gt;in &lt;/span&gt;on eth0 from 185.204.1.174 port 443 proto tcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;eth0&lt;/code&gt; is the interface that originally had internet access. Now after denying it any access, we are allowing it to only talk to the VPN server on the server's port 443.&lt;br&gt;&lt;br&gt;
Needless to say, the IP addresses, the ports and the protocol(tcp/udp which we are not having ufw enforce) will depend on the VPN server and your provider.&lt;br&gt;&lt;br&gt;
Note: make sure you are not doing DNS request out-of-band in regards to your VPN. This seems to be a common mistake and some VPN providers don't enable sending the DNS requests through the VPN tunnel by default which means your actual traffic goes through the tunnel but you are kindly letting your ISP(if you have not changed your host's DNS servers) know where you are sending your traffic to.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;After setting the rules, we enable ufw.&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Sudo-less NTFS
&lt;/h3&gt;

&lt;p&gt;In order to make the process more streamlined and not mistakenly keep an instance alive we need to have a sudo-less NTFS mount for the VM.&lt;br&gt;&lt;br&gt;
Without sudo-less NTFS, we would have to type in the sudo password twice, once when the VM is being brought up and once when it is being destroyed. Imagine a scenario when you close the disposable firefox VM, thinking that is gone but in reality it needs you to type in the sudo password to destroy it, thus, keeping the instance alive.&lt;br&gt;&lt;br&gt;
The solution is simple. We add the following to &lt;code&gt;/etc/exports&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="s2"&gt;"/home/user/share/nfs"&lt;/span&gt; 192.168.121.0/24&lt;span class="o"&gt;(&lt;/span&gt;rw,no_subtree_check,all_squash,anonuid&lt;span class="o"&gt;=&lt;/span&gt;1000,anongid&lt;span class="o"&gt;=&lt;/span&gt;1000&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will enable the VM to access &lt;code&gt;/home/user/share/nfs&lt;/code&gt; without needing sudo.&lt;br&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Vagrantfile
&lt;/h2&gt;

&lt;p&gt;Here is the Vagrantfile that will be used to provision the VM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'VAGRANT_DEFAULT_PROVIDER'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'libvirt'&lt;/span&gt;
&lt;span class="no"&gt;Vagrant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;require_version&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;= 2.2.6'&lt;/span&gt;
&lt;span class="no"&gt;Vagrant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'generic/alpine319'&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;box_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'4.3.12'&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;box_check_update&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'virt-disposable'&lt;/span&gt;

  &lt;span class="c1"&gt;# ssh&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ssh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ssh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keep_alive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ssh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys_only&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="c1"&gt;# timeouts&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boot_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;graceful_halt_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ssh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provider&lt;/span&gt; &lt;span class="s1"&gt;'libvirt'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="c1"&gt;# name of the storage pool, mine is ramdisk.&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;storage_pool_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ramdisk'&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'disposable-'&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'kvm'&lt;/span&gt;
    &lt;span class="c1"&gt;# amount of memory to allocate to the VM&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'3076'&lt;/span&gt;
    &lt;span class="c1"&gt;# amount of logical CPU cores to allocate to the VM&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cpus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sound_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;qemuargs&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="s1"&gt;'-nographic'&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;qemuargs&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="s1"&gt;'-nodefaults'&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;qemuargs&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="s1"&gt;'-no-user-config'&lt;/span&gt;
    &lt;span class="c1"&gt;# enabling a serial console just in case&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;qemuargs&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="s1"&gt;'-serial'&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;qemuargs&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="s1"&gt;'pty'&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;qemuargs&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="s1"&gt;'-sandbox'&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;qemuargs&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="s1"&gt;'on'&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt; &lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="s1"&gt;'random'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provision&lt;/span&gt; &lt;span class="s1"&gt;'update-upgrade'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s1"&gt;'shell'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'update-upgrade'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;inline: &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SHELL&lt;/span&gt;&lt;span class="sh"&gt;
    set -ex
    sudo apk update &amp;amp;&amp;amp; \
      sudo apk upgrade
    sudo apk add firefox-esr xauth font-dejavu wget openvpn unzip iptables ufw nfs-utils haveged tzdata
    mkdir -p /vagrant &amp;amp;&amp;amp; \
      sudo mount -t nfs 192.168.121.1:/home/devi/share/nfs /vagrant
&lt;/span&gt;&lt;span class="no"&gt;  SHELL&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provision&lt;/span&gt; &lt;span class="s1"&gt;'update-upgrade-privileged'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s1"&gt;'shell'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'update-upgrade-privileged'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;privileged: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;inline: &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SHELL&lt;/span&gt;&lt;span class="sh"&gt;
    set -ex
    sed -i 's/^#X11DisplayOffset .*/X11DisplayOffset 0/' /etc/ssh/sshd_config
    sed -i 's/^X11Forwarding .*/X11Forwarding yes/' /etc/ssh/sshd_config
    rc-service sshd restart

    ln -fs /usr/share/zoneinfo/UTC /etc/localtime

    #rc-update add openvpn default
    mkdir -p /tmp/mullvad/ &amp;amp;&amp;amp; \
      cp /vagrant/mullvad_openvpn_linux_fi_hel.zip /tmp/mullvad/ &amp;amp;&amp;amp; \
      cd /tmp/mullvad &amp;amp;&amp;amp; \
      unzip mullvad_openvpn_linux_fi_hel.zip &amp;amp;&amp;amp; \
      mv mullvad_config_linux_fi_hel/mullvad_fi_hel.conf /etc/openvpn/openvpn.conf &amp;amp;&amp;amp; \
      mv mullvad_config_linux_fi_hel/mullvad_userpass.txt /etc/openvpn/ &amp;amp;&amp;amp; \
      mv mullvad_config_linux_fi_hel/mullvad_ca.crt /etc/openvpn/ &amp;amp;&amp;amp; \
      mv mullvad_config_linux_fi_hel/update-resolv-conf /etc/openvpn &amp;amp;&amp;amp; \
      chmod 755 /etc/openvpn/update-resolv-conf
    modprobe tun
    echo "net.ipv4.ip_forward = 1" &amp;gt;&amp;gt; /etc/sysctl.d/ipv4.conf
    sysctl -p /etc/sysctl.d/ipv4.conf
    rc-service openvpn start || true
    sleep 1
&lt;/span&gt;&lt;span class="no"&gt;  SHELL&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provision&lt;/span&gt; &lt;span class="s1"&gt;'kill-switch'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;communicator_required: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s1"&gt;'shell'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'kill-switch'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;privileged: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;inline: &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SHELL&lt;/span&gt;&lt;span class="sh"&gt;
    # http://o54hon2e2vj6c7m3aqqu6uyece65by3vgoxxhlqlsvkmacw6a7m7kiad.onion/en/help/linux-openvpn-installation
    set -ex
    ufw --force reset
    ufw default deny incoming
    ufw default deny outgoing
    ufw allow in on tun0
    ufw allow out on tun0
    # allow local traffic through the libvirt bridge
    ufw allow in on eth0 from 192.168.121.1 proto tcp
    ufw allow out on eth0 to 192.168.121.1 proto tcp
    # server block
    ufw allow out on eth0 to 185.204.1.174 port 443 proto tcp
    ufw allow in on eth0 from 185.204.1.174 port 443 proto tcp
    ufw allow out on eth0 to 185.204.1.176 port 443 proto tcp
    ufw allow in on eth0 from 185.204.1.176 port 443 proto tcp
    ufw allow out on eth0 to 185.204.1.172 port 443 proto tcp
    ufw allow in on eth0 from 185.204.1.172 port 443 proto tcp
    ufw allow out on eth0 to 185.204.1.171 port 443 proto tcp
    ufw allow in on eth0 from 185.204.1.171 port 443 proto tcp
    ufw allow out on eth0 to 185.212.149.201 port 443 proto tcp
    ufw allow in on eth0 from 185.212.149.201 port 443 proto tcp
    ufw allow out on eth0 to 185.204.1.173 port 443 proto tcp
    ufw allow in on eth0 from 185.204.1.173 port 443 proto tcp
    ufw allow out on eth0 to 193.138.7.237 port 443 proto tcp
    ufw allow in on eth0 from 193.138.7.237 port 443 proto tcp
    ufw allow out on eth0 to 193.138.7.217 port 443 proto tcp
    ufw allow in on eth0 from 193.138.7.217 port 443 proto tcp
    ufw allow out on eth0 to 185.204.1.175 port 443 proto tcp
    ufw allow in on eth0 from 185.204.1.175 port 443 proto tcp

    echo y | ufw enable
&lt;/span&gt;&lt;span class="no"&gt;  SHELL&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provision&lt;/span&gt; &lt;span class="s1"&gt;'mullvad-test'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s1"&gt;'shell'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'test'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;privileged: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;inline: &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SHELL&lt;/span&gt;&lt;span class="sh"&gt;
    set -ex
    curl --connect-timeout 10 https://am.i.mullvad.net/connected | grep -i "you&lt;/span&gt;&lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="sh"&gt;are&lt;/span&gt;&lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="sh"&gt;connected"
&lt;/span&gt;&lt;span class="no"&gt;  SHELL&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Provisioning
&lt;/h3&gt;

&lt;p&gt;We will be using the vagrant shell provisioner to prepare the VM.&lt;br&gt;&lt;br&gt;
The first provisioner names &lt;code&gt;update-upgrade&lt;/code&gt; does what the name implies. It installs the required packages.&lt;br&gt;&lt;br&gt;
The next provisioner, &lt;code&gt;update-upgrade-privileged&lt;/code&gt;, enables X11 forwarding on openssh, sets up openvpn as a service and starts it and finally sets the timezone to UTC.&lt;br&gt;&lt;br&gt;
The third provisioner, &lt;code&gt;kill-switch&lt;/code&gt;, sets up our kill-switch using ufw.&lt;br&gt;&lt;br&gt;
The final provisioner runs the mullvad test for their VPN. Since at this point we have set up the kill-switch we wont leak our IP address to the mullvad website but that's not important since we are using our own IP address to connect to the mullvad VPN servers.&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Interface
&lt;/h3&gt;

&lt;p&gt;how do we interface with our firefox instance. ssh or spice?&lt;br&gt;&lt;br&gt;
I have gone with ssh. In our case we use ssh's X11 forwarding feature. This choice is made purely out of convenience. You can go with spice.&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Timezone
&lt;/h3&gt;

&lt;p&gt;We set the VM's timezone to UTC because it's generic.&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  haveged
&lt;/h3&gt;

&lt;p&gt;haveged is a daemon that provides a source of randomness for our VM. Look &lt;a href="https://www.kicksecure.com/wiki/Dev/Entropy#haveged"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  QEMU Sandbox
&lt;/h4&gt;

&lt;p&gt;From &lt;code&gt;man 1 qemu&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-sandbox arg[,obsolete=string][,elevateprivileges=string][,spawn=string][,resourcecontrol=string]
    Enable Seccomp mode 2 system call filter. 'on' will enable syscall filtering and 'off' will disable it. The default is 'off'.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  CPU Pinning
&lt;/h4&gt;

&lt;p&gt;CPU pinning alone is not what we want. We want cpu pinning and then further isolating those cpu cores on the host so that only the VM runs on those cores. This will give us a better performance on the VM side but also provide better security and isolation since this will mitigate side-channel attacks based on the CPU(the spectre/metldown family, the gift that keeps on giving).&lt;br&gt;&lt;br&gt;
In my case, I've done what I can on the host-side to mitigate spectre/meltdown but I don't have enough resources to ping 6 logical cores to this VM. If you can spare the resources, by all means, please do.&lt;br&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  No Passthrough
&lt;/h3&gt;

&lt;p&gt;We will not be doing any passthroughs. It is not necessarily a choice made because of security, but merely out of a lack of need for the performance benefit that hardware-acceleration brings.&lt;br&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Launcher Script
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/dash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt;

sigint_handler&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;ipv4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  xhost -&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ipv4&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  vagrant destroy &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;trap &lt;/span&gt;sigint_handler INT
&lt;span class="nb"&gt;trap &lt;/span&gt;sigint_handler TERM

&lt;span class="nv"&gt;working_directory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/home/devi/devi/vagrantboxes.git/main/disposable/"&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;working_directory&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1

vagrant up
&lt;span class="nv"&gt;disposable_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;vagrant global-status | &lt;span class="nb"&gt;grep &lt;/span&gt;disposable | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;disposable_ipv4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;vagrant ssh &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;disposable_id&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"ip a show eth0 | grep inet | grep -v inet6 | awk '{print &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt;2}' | cut -d/ -f1 | tr -d '[:space:]'"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;trap&lt;/span&gt; &lt;span class="s1"&gt;'sigint_handler ${disposable_ipv4}'&lt;/span&gt; INT
&lt;span class="nb"&gt;trap&lt;/span&gt; &lt;span class="s1"&gt;'sigint_handler ${disposable_ipv4}'&lt;/span&gt; TERM

&lt;span class="nb"&gt;echo&lt;/span&gt;  &lt;span class="s2"&gt;"got IPv4 &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;disposable_ipv4&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
xhost +&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;disposable_ipv4&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
ssh &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;StrictHostKeyChecking&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;Compression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;UserKnownHostsFile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/null &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-X&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt;&lt;span class="s2"&gt;".vagrant/machines/default/libvirt/private_key"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  vagrant@&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;disposable_ipv4&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"XAUTHORITY=/home/vagrant/.Xauthority firefox-esr -no-remote"&lt;/span&gt; https://mullvad.net/en/check/
xhost -&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;disposable_ipv4&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
vagrant destroy &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script is straightforward. It brings up the VM, and destroys it when the disposable firefox instance is closed.&lt;br&gt;&lt;br&gt;
Let's look at a couple of things that we are doing here:&lt;br&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The shebang line: we are using &lt;code&gt;dash&lt;/code&gt;, the debian almquist shell. It has a smaller attack surface. It's small but we don't need all the features of bash or zsh here so we use something "more secure".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;we add and remove the IP of the VM from the xhost list. This allows the instance to display the firefox window on the host's X server and after it's done, we remove it so we don't end up whitelisting the entire IP range(least privilege principle, remember?).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;we use &lt;code&gt;-o UserKnownHostsFile=/dev/null&lt;/code&gt; to prevent the VM from adding to the host's known hosts file. There are two reasons why we do this here. One, the IP range is limited, we will eventually end up conflicting with another IP that lives on your hostsfile that was a live and well VM as some point but is now dead so libvirt will reassign its IP address to our disposable instance which will prompt ssh to tell you that it suspects there is something going on which will prevent the ssh command from completing successfully which will in turn result in the VM getting killed. Two, we will stop polluting the hostsfile by all the IPs of the disposable VM instances that we keep creating so that you won't have to deal with the same problem while running other VMs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;we register a signal handler for &lt;code&gt;SIGTERM&lt;/code&gt; and &lt;code&gt;SIGINT&lt;/code&gt; so that we can destroy the VM after we created it and we one of those signals. This helps ensure a higher rate of confidence in the VM getting destroyed. This does not guarantee that. A &lt;code&gt;SIGKILL&lt;/code&gt; will kill the script and that's that.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Notes Regarding the Host
&lt;/h2&gt;

&lt;p&gt;A good deal of security and isolation comes from the host specially in a scenario when you are running a VM on top of the host. This is an entirely different topic so we won't be getting into it but &lt;a href="https://kernsec.org/wiki/index.php/Kernel_Self_Protection_Project/Recommended_Settings"&gt;here&lt;/a&gt; is a good place to start. Just because it's only a single line at the end of some random blogpost doesn't mean its not important. Take this seriously.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;We are using somebody else's vagrant base image. Supply-chain attacks are a thing so it is very much better to use our own base image.&lt;br&gt;&lt;br&gt;
As a starting you can look &lt;a href="https://github.com/lavabit/robox/tree/master/scripts/alpine319"&gt;here&lt;/a&gt;. This is how the base image we are using is created.&lt;br&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Docker, Linux, Security. Kinda.</title>
      <dc:creator>Farzad Sadeghi</dc:creator>
      <pubDate>Mon, 20 May 2024 02:27:25 +0000</pubDate>
      <link>https://dev.to/terminaldweller/docker-linux-security-kinda-2l1f</link>
      <guid>https://dev.to/terminaldweller/docker-linux-security-kinda-2l1f</guid>
      <description>&lt;h2&gt;
  
  
  Docker, Linux, Security. Kinda.
&lt;/h2&gt;

&lt;p&gt;We will be exploring some Linux features in the context of a docker application container. Another way of explaining it would be to say we will talk about how to make more secure application containers.&lt;br&gt;
We will not talk about firewall and apparmor because they are tools that enhance security on the host in general and not specific to a docker application container. A secure host means a more secure application container but that is discussion for another post.&lt;br&gt;&lt;br&gt;
We will focus on Linux containers since FreeBSD containers are still experimental(see &lt;a href="https://wiki.freebsd.org/Docker"&gt;here&lt;/a&gt; and &lt;a href="https://github.com/samuelkarp/runj"&gt;here&lt;/a&gt;). Yes, windows containers exist.&lt;br&gt;&lt;br&gt;
We will not discuss performance. Here be performance penalties, but again that is not the focus of this post.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Before we begin, Linux docker containers are Linux. They are using most of the functionality that existed before application containers in the form of docker were a thing. Knowing Linux better means you know Linux Docker containers(application containers is a more correct term) better. We will see this point throughout this post.&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Base Image
&lt;/h2&gt;

&lt;p&gt;We start with the first building block of a new docker image, The base image. By far the most used base images are the Alpine docker base image, followed by Debian and Ubuntu docker base images.&lt;br&gt;
These distros have two major differences that we want to focus on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;C standard library implementation&lt;/li&gt;
&lt;li&gt;the userspace utility implementation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Debian and Ubuntu(we are not forgetting that Ubuntu itself is a Debian derivative) both use glibc, as in gnu's &lt;a href="https://www.gnu.org/software/libc/"&gt;libc&lt;/a&gt; implementation. Alpine uses &lt;a href="https://www.musl-libc.org/"&gt;musl-libc&lt;/a&gt; as its C standard library implementation.&lt;br&gt;&lt;br&gt;
The major difference here which will come into play later on again is glibc has been around for much longer, so it has to keep backwards compatibility for a much longer period of time and for far more many things. Also the general attitude with the glibc team is that they have to support everything since if they don't then who will?&lt;br&gt;&lt;br&gt;
Libmusl on the other hand, does not try to support everything under the sun, a relatively newer project, comparatively, and, they keep their codebase lean.&lt;br&gt;&lt;br&gt;
As a result not all applications are supported by libmusl but a good number of them are.&lt;br&gt;&lt;br&gt;
In simpler terms, libmusl has a far smaller attack surface compared to glibc.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;On to our second point, which is the cli utilities' implementation. Debian and Ubuntu use gnu's &lt;a href="https://www.gnu.org/software/coreutils/"&gt;Coreutils&lt;/a&gt; while Alpine uses &lt;a href="https://busybox.net/"&gt;Busybox&lt;/a&gt;(remember, we are talking about the most used application container bases. You can install a desktop version of Alpine with GNU coreutils).&lt;br&gt;&lt;br&gt;
Here we have the same situation as before, The GNU coreutils are bigger, do more and have a larger attack surface. Busybox is smaller, does not support as many features as GNU Coreutils but does support enough of them to make them useful. Needless to say, busybox is small and hence, it has a smaller attack surface.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;To get a feel for how this plays out in the real world, you can look at some of the popular images that come in both Debian and Alpine flavours on dockerhub. Take a look at the number of reported vulnerabilities for both bases. The theme we observe is simple. The bigger the attack surface the bigger the number of vulnerabilities.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Alpine images are small, lean and functional, just like libmusl and busybox but there are still quite a few things on an alpine image that are extraneous. We can take them out and have a perfectly functioning application container.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;That's how we get &lt;a href="https://github.com/GoogleContainerTools/distroless"&gt;distroless&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
Distroless base images follow the same pattern as alpine base docker images, as in, less functionality while still keeping enough functionality to be able to do the job and minimize the attack surface.&lt;br&gt;
Minimizing a base image like this means that the base images are very specialized so we have base images for golang, python, java and the like.&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Dokcer Runtimes
&lt;/h2&gt;

&lt;p&gt;By default docker uses containerd which in turn uses runc for the runtime. There are two additional runtimes that we want to focus on who try to provide a more secure runtime environment for docker.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gvisor&lt;/li&gt;
&lt;li&gt;kata&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  gvisor
&lt;/h3&gt;

&lt;p&gt;gVisor creates a sandbox environment. Containers interact with the host through this sandboxed environment.&lt;br&gt;&lt;br&gt;
gvisor has two components. Gofer and Sentry. Sentry is a kernel that runs the containers and intercepts and responds to system calls made by the application so as not to have an application directly control the syscalls that it makes.&lt;br&gt;&lt;br&gt;
Gofer handles filesystem access(not /proc) for the application.&lt;br&gt;&lt;br&gt;
The application is a regular application. gVisor aims to provide an environment equivalent to Linux 4.4. gvisor presently does not implement every system call, &lt;code&gt;/proc&lt;/code&gt; file or &lt;code&gt;/sys&lt;/code&gt; file.&lt;br&gt;&lt;br&gt;
Every sandbox environment gets its own instance of Sentry. Every container in the sandbox gets its own instance of Gofer.&lt;br&gt;&lt;br&gt;
gVisor currently does not support all system calls. You can find the list of supported system calls for amd64 &lt;a href="https://gvisor.dev/docs/user_guide/compatibility/linux/amd64/"&gt;here&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  -------------
  |Application|
  -------------
       |system calls
       |
    --------   9p    -------
    |Sentry|&amp;lt;-------&amp;gt;|Gofer|
    --------         -------
         | limited    |system
         | syscalls   |calls
        ---------------
        | Host Kernel |
        ---------------
               |
               |hardware
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  kata
&lt;/h3&gt;

&lt;p&gt;Kata creates a sandbox environment for containers to interact with as proxy, not too dissimilar to gvisor but the main point of difference is that kata uses a VM to achieve this.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;gVisor and katacontainers allow us to implement defense in depth when it comes to application containers and host system security.&lt;br&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Capabilites and Syscalls
&lt;/h2&gt;

&lt;p&gt;Let's talk about capabilities for a bit.&lt;/p&gt;

&lt;p&gt;From &lt;a href="https://manpages.debian.org/bookworm/manpages/capabilities.7.en.html"&gt;man 7 capabilities&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;For the purpose of performing permission checks, traditional UNIX implementations distinguish two
categories of processes: privileged processes (whose effective user ID is 0, referred to as
superuser or root), and unprivileged processes (whose effective UID is nonzero).  Privileged
processes bypass all kernel permission checks, while unprivileged processes are subject to full
permission checking based on the process's credentials (usually: effective UID, effective GID, and
supplementary group list).

Starting with Linux 2.2, Linux divides the privileges traditionally associated with superuser into
distinct units, known as capabilities, which can be independently enabled and disabled.
Capabilities are a per-thread attribute.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Capabilities give you a more granular control over which privileges to give instead of just root and non-root.&lt;br&gt;&lt;br&gt;
Docker let's us choose which capabilities to give to a container. So we can for example allow a non-privileged process to bind to privileged ports using capabilities.&lt;br&gt;&lt;br&gt;
As an example, a simple application making calls to API endpoints and writing results back to a database does not require any capabilities. It can run under a non-privileged user with no capabilities and do all the tasks that it needs to do.&lt;br&gt;
That being said, determining which capabilities are required can be a bit challenging when it comes to certain applications since there is no straightforward way of achieving this. In certain cases we can get away with dropping all capabilities, running our application and then trying to figure out, based on the received error messages, which capability is missing and needs to be given to the application. But in certain cases this may not be feasible or practical.&lt;/p&gt;

&lt;p&gt;From &lt;a href="https://manpages.debian.org/bookworm/manpages-dev/syscalls.2.en.html"&gt;man 2 sycalls&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The system call is the fundamental interface between an application and the Linux kernel.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Linux kernel lets us choose which ones of these interface calls can be allowed to be made by an application. We can essentially filter which syscalls are allowed and which ones are not on a per application basis. Docker enables this functionality with an arguably more friendly approach.&lt;br&gt;&lt;br&gt;
Capabilities and syscall filtering are tools to implement principle of least privilege. Ideally, we would like to allow a container to only have access to what it needs and just that. Not more, and obviously not less.&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  capabilities in the wild
&lt;/h3&gt;

&lt;p&gt;Capabilities are a Linux feature, docker allows us to use that with application containers. We'll look at a very simple example of how one can set capabilities for a regular executable on Linux.&lt;br&gt;&lt;br&gt;
&lt;a href="https://manpages.debian.org/bookworm/libcap2-bin/setcap.8.en.html"&gt;man 8 setcap&lt;/a&gt; lets us set capabilities for a file.&lt;/p&gt;
&lt;h3&gt;
  
  
  syscall Filtering in the wild
&lt;/h3&gt;

&lt;p&gt;As an example we will look at &lt;a href="https://manpages.debian.org/bookworm/bubblewrap/bwrap.1.en.html"&gt;man 1 bwrap&lt;/a&gt;.&lt;br&gt;
&lt;a href="https://github.com/containers/bubblewrap"&gt;Bubblewrap&lt;/a&gt; allows us to sandbox an application, not too dissimilar to docker. Flatpaks use bubblewrap as part of their sandbox.&lt;br&gt;&lt;br&gt;
Bubblewrap can optionally take in a list of syscalls to &lt;a href="https://www.kernel.org/doc/html/v4.19/userspace-api/seccomp_filter.html"&gt;filter&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
The filter is expressed as a BPF(Berkley Packet Filter program - remember when I said docker gives you a &lt;a href="https://docs.docker.com/engine/security/seccomp/"&gt;friendlier&lt;/a&gt; interface to seccomp?) program.&lt;br&gt;&lt;br&gt;
Below is a short program that defines a BPF program that can be passed to an application using bwrap that lets us log all the sycalls the application makes to syslog.&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;fcntl.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;seccomp.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdbool.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;unistd.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;log_all_syscalls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;scmp_filter_ctx&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;seccomp_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SCMP_ACT_LOG&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;seccomp_arch_add&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;SCMP_ARCH_X86_64&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;seccomp_export_bpf&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;seccomp_export_pfc&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;seccomp_release&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;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;argc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;log_all_syscalls&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;Building is straightforward. Just remember to link against &lt;code&gt;libseccomp&lt;/code&gt; with &lt;code&gt;-lseccomp&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcc main.c &lt;span class="nt"&gt;-lseccomp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running the above code we get this:&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; &amp;gt; 5@#
# pseudo filter code start
#
# filter for arch x86_64 (3221225534)
if ($arch == 3221225534)
  # default action
  action LOG;
# invalid architecture action
action KILL;
#
# pseudo filter code end
#
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/dash&lt;/span&gt;

&lt;span class="nv"&gt;TEMP_LOG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/seccomp_logging_filter.bpf

./a.out &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEMP_LOG&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

bwrap &lt;span class="nt"&gt;--seccomp&lt;/span&gt; 9 9&amp;lt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEMP_LOG&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can go and see where the logs end up. On my host, they are logged under &lt;code&gt;/var/log/audit/audit.log&lt;/code&gt; and they look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type=SECCOMP msg=audit(1716144132.339:4036728): auid=1000 uid=1000 gid=1000 ses=1 subj=unconfined pid=19633 comm="bash" exe="/usr/bin/bash" sig=0 arch=c000003e syscall=13 compat=0 ip=0x7fa58591298f code=0x7ffc0000AUID="devi" UID="devi" GID="devi" ARCH=x86_64 SYSCALL=rt_sigaction
type=SECCOMP msg=audit(1716144132.339:4036729): auid=1000 uid=1000 gid=1000 ses=1 subj=unconfined pid=19633 comm="bash" exe="/usr/bin/bash" sig=0 arch=c000003e syscall=13 compat=0 ip=0x7fa58591298f code=0x7ffc0000AUID="devi" UID="devi" GID="devi" ARCH=x86_64 SYSCALL=rt_sigaction
type=SECCOMP msg=audit(1716144132.339:4036730): auid=1000 uid=1000 gid=1000 ses=1 subj=unconfined pid=19633 comm="bash" exe="/usr/bin/bash" sig=0 arch=c000003e syscall=13 compat=0 ip=0x7fa58591298f code=0x7ffc0000AUID="devi" UID="devi" GID="devi" ARCH=x86_64 SYSCALL=rt_sigaction
type=SECCOMP msg=audit(1716144132.339:4036731): auid=1000 uid=1000 gid=1000 ses=1 subj=unconfined pid=19633 comm="bash" exe="/usr/bin/bash" sig=0 arch=c000003e syscall=13 compat=0 ip=0x7fa58591298f code=0x7ffc0000AUID="devi" UID="devi" GID="devi" ARCH=x86_64 SYSCALL=rt_sigaction
type=SECCOMP msg=audit(1716144132.339:4036732): auid=1000 uid=1000 gid=1000 ses=1 subj=unconfined pid=19633 comm="bash" exe="/usr/bin/bash" sig=0 arch=c000003e syscall=13 compat=0 ip=0x7fa58591298f code=0x7ffc0000AUID="devi" UID="devi" GID="devi" ARCH=x86_64 SYSCALL=rt_sigaction
type=SECCOMP msg=audit(1716144132.339:4036733): auid=1000 uid=1000 gid=1000 ses=1 subj=unconfined pid=19633 comm="bash" exe="/usr/bin/bash" sig=0 arch=c000003e syscall=14 compat=0 ip=0x7fa5859664f4 code=0x7ffc0000AUID="devi" UID="devi" GID="devi" ARCH=x86_64 SYSCALL=rt_sigprocmask
type=SECCOMP msg=audit(1716144132.339:4036734): auid=1000 uid=1000 gid=1000 ses=1 subj=unconfined pid=19633 comm="bash" exe="/usr/bin/bash" sig=0 arch=c000003e syscall=13 compat=0 ip=0x7fa58591298f code=0x7ffc0000AUID="devi" UID="devi" GID="devi" ARCH=x86_64 SYSCALL=rt_sigaction
type=SECCOMP msg=audit(1716144132.339:4036735): auid=1000 uid=1000 gid=1000 ses=1 subj=unconfined pid=19633 comm="bash" exe="/usr/bin/bash" sig=0 arch=c000003e syscall=1 compat=0 ip=0x7fa5859ce5d0 code=0x7ffc0000AUID="devi" UID="devi" GID="devi" ARCH=x86_64 SYSCALL=write
type=SECCOMP msg=audit(1716144132.339:4036736): auid=1000 uid=1000 gid=1000 ses=1 subj=unconfined pid=19633 comm="bash" exe="/usr/bin/bash" sig=0 arch=c000003e syscall=1 compat=0 ip=0x7fa5859ce5d0 code=0x7ffc0000AUID="devi" UID="devi" GID="devi" ARCH=x86_64 SYSCALL=write
type=SECCOMP msg=audit(1716144132.339:4036737): auid=1000 uid=1000 gid=1000 ses=1 subj=unconfined pid=19633 comm="bash" exe="/usr/bin/bash" sig=0 arch=c000003e syscall=270 compat=0 ip=0x7fa5859d77bc code=0x7ffc0000AUID="devi" UID="devi" GID="devi" ARCH=x86_64 SYSCALL=pselect6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker allows us to do the &lt;a href="https://docs.docker.com/engine/security/seccomp/"&gt;same&lt;/a&gt;. We can give docker a seccomp profile to filter out the syscalls that are not required for a specific container.&lt;br&gt;&lt;br&gt;
You can find the default docker seccomp profile &lt;a href="https://github.com/moby/moby/blob/master/profiles/seccomp/default.json"&gt;here&lt;/a&gt;.&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Namespaces
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A namespace wraps a global system resource in an abstraction that makes it appear to the processes
within the namespace that they have their own isolated instance of the global resource.  Changes
to the global resource are visible to other processes that are members of the namespace, but are
invisible to other processes.  One use of namespaces is to implement containers.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;From &lt;a href="https://manpages.debian.org/bookworm/manpages/namespaces.7.en.html"&gt;man 7 namespaces&lt;/a&gt;.&lt;br&gt;
You can think of namespaces as almost the same thing as a namespace does in some programming languages.&lt;br&gt;&lt;br&gt;
Docker uses its own namespaces for the containers so as to further isolate the application containers from the host system.&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Namespaces in the Wild
&lt;/h3&gt;

&lt;p&gt;As an example let's look at the script provided below. Here we are creating a new network namespace. The new interface is provided by simply connecting an android phone for USB tethering. Depending on the situation you have going on and the &lt;code&gt;udev&lt;/code&gt; naming rules the interface name will differ but the concept is the same. We are creating a new network namespace for a second internet provider, which in this case, is our android phone. We then use this network namespace to execute commands in the context of this specific network namespace. Essentially, we can choose which applications get to use our phone internet and which ones use whatever it is we were previously connected to.&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env sh&lt;/span&gt;
&lt;span class="nv"&gt;PHONE_NS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;phone_ns
&lt;span class="nv"&gt;IF&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;enp0s20f0u6

&lt;span class="nb"&gt;sudo &lt;/span&gt;ip netns add &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PHONE_NS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ip &lt;span class="nb"&gt;link set&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IF&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; netns &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PHONE_NS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ip netns &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PHONE_NS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; ip &lt;span class="nb"&gt;link set&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IF&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; up
&lt;span class="nb"&gt;sudo &lt;/span&gt;ip netns &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PHONE_NS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; ip &lt;span class="nb"&gt;link set &lt;/span&gt;dev lo up
&lt;span class="nb"&gt;sudo &lt;/span&gt;ip netns &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PHONE_NS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; dhclient &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IF&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ip netns &lt;span class="nb"&gt;exec &lt;/span&gt;home_ns curl &lt;span class="nt"&gt;-4&lt;/span&gt; icanhaveip.com
113.158.237.102
&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-4&lt;/span&gt; icanhasip.com
114.201.132.98
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;HINT&lt;/em&gt;&lt;/strong&gt;: The IP addresses are made up. The only thing that matters is that they are different.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Since we have the android phone's interface on another namespace the two cannot interfere with each other. This is pretty much how docker uses namespaces.&lt;br&gt;&lt;br&gt;
Without a network namespace we would have to make a small VM, run a VPN on the VM and then make a socks5 proxy to the VM from the host and then have applications pass their traffic through a socks5 proxy with varying degrees of success.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;&lt;em&gt;NOTE&lt;/em&gt;&lt;/strong&gt;: since we are not running the script on a hook, you might blow out your net having two upstreams at the same time. In which case, run the script, then restart NetworkManager or whatever you have.&lt;/p&gt;
&lt;h2&gt;
  
  
  SBOM and Provenance Attestation
&lt;/h2&gt;

&lt;p&gt;What is SBOM?&lt;br&gt;
NIST defines SBOM as a “formal record containing the details and supply chain relationships of various components used in building software.".&lt;br&gt;&lt;br&gt;
It contains details about the components used to create a certain piece of software.&lt;br&gt;&lt;br&gt;
SBOM is meant to help mitigate the threat of supply chain attacks(remember xz?).&lt;br&gt;&lt;/p&gt;

&lt;p&gt;What is provenance?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The provenance attestations include facts about the build process, including details such as:

    Build timestamps
    Build parameters and environment
    Version control metadata
    Source code details
    Materials (files, scripts) consumed during the build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.docker.com/build/attestations/sbom/"&gt;source&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;Let's review all that we learned about in the form of a light exercise.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;For the first build, we use a non-vendored version.&lt;br&gt;
Vendoring means that you store your dependencies locally. This means you are in control of your dependencies. You don't need to pull them from a remote. Even if one or more of your dependencies One of the more famous examples is Lua. The Lua foundation actually recommend vendoring your Lua dependency.&lt;br&gt;&lt;br&gt;
Vendoring helps with build reproducability.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;We will use &lt;a href="https://github.com/terminaldweller/milla"&gt;milla&lt;/a&gt; as an exmaple. It's a simple go codebase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;alpine:3.19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;      apk upgrade &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;      apk add go git
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /milla&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; go.sum go.mod /milla/&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go mod download
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; *.go /milla/&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine:3.19&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; HOME /home/user&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-eux&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  adduser &lt;span class="nt"&gt;-u&lt;/span&gt; 1001 &lt;span class="nt"&gt;-D&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; user&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.irssi"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; user:user &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /milla/milla "$HOME/milla"&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown &lt;/span&gt;user:user &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/milla"&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["home/user/milla"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first docker image build is fairly simple. We copy the source code in, get our dependencies and build a static executable. As for the second stage of the build, we simply put the executable into a new base image and we are done.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;The second build which is a vendored build with a golang distroless base. We copy over the source code for the project and all its dependencies and then do the same as before.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;golang:1.21&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /milla&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; go.sum go.mod /milla/&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go mod download
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; *.go /milla/&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 go build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; gcr.io/distroless/static-debian12&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /milla/milla "/usr/bin/milla"&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["milla"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Below You can see an example docker compose file. Milla can optionally use a postgres database to store messages. We also include a pgadmin instance. Now let's talk about the docker compose file.&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;terra&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;milla_distroless_vendored&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./Dockerfile_distroless_vendored&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;128M&lt;/span&gt;
    &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json-file"&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;max-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;100m"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;terranet&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1000:1000&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/milla"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--config"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/config.toml"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./config.toml:/config.toml&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/etc/localtime:/etc/localtime:ro&lt;/span&gt;
    &lt;span class="na"&gt;cap_drop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ALL&lt;/span&gt;
    &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;runsc&lt;/span&gt;
  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16-alpine3.19&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;4096M&lt;/span&gt;
    &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json-file"&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;max-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200m"&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:5455:5432/tcp"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;terra_postgres_vault:/var/lib/postgresql/data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./scripts/:/docker-entrypoint-initdb.d/:ro&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD_FILE=/run/secrets/pg_pass_secret&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER_FILE=/run/secrets/pg_user_secret&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_INITDB_ARGS_FILE=/run/secrets/pg_initdb_args_secret&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB_FILE=/run/secrets/pg_db_secret&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;terranet&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dbnet&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pg_pass_secret&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pg_user_secret&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pg_initdb_args_secret&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pg_db_secret&lt;/span&gt;
    &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;runsc&lt;/span&gt;
  &lt;span class="na"&gt;pgadmin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dpage/pgadmin4:8.6&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1024M&lt;/span&gt;
    &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json-file"&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;max-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;100m"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PGADMIN_LISTEN_PORT=${PGADMIN_LISTEN_PORT:-5050}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PGADMIN_DEFAULT_EMAIL=${PGADMIN_DEFAULT_EMAIL:-devi@terminaldweller.com}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PGADMIN_DEFAULT_PASSWORD_FILE=/run/secrets/pgadmin_pass&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PGADMIN_DISABLE_POSTFIX=${PGADMIN_DISABLE_POSTFIX:-YES}&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:5050:5050/tcp"&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;terra_pgadmin_vault:/var/lib/pgadmin&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dbnet&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pgadmin_pass&lt;/span&gt;
&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;terranet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
  &lt;span class="na"&gt;dbnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;terra_postgres_vault&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;terra_pgadmin_vault&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pg_pass_secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./pg/pg_pass_secret&lt;/span&gt;
  &lt;span class="na"&gt;pg_user_secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./pg/pg_user_secret&lt;/span&gt;
  &lt;span class="na"&gt;pg_initdb_args_secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./pg/pg_initdb_args_secret&lt;/span&gt;
  &lt;span class="na"&gt;pg_db_secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./pg/pg_db_secret&lt;/span&gt;
  &lt;span class="na"&gt;pgadmin_pass&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./pgadmin/pgadmin_pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are assigning memory usage limits for the containers. We are also limiting the size of the logs we are keeping on disk.&lt;br&gt;&lt;br&gt;
One thing that we did not talk about before is the networking side of compose.&lt;br&gt;&lt;br&gt;
As can be seen, the postgres and pgadmin container share one network while the postgres container and milla share another network. This makes it so that milla and pgadmin do not have access to each other. This is inline with principle of least privilege. Milla and pgadmin don't need to talk to each other so they can't do that.&lt;br&gt;&lt;br&gt;
Also we refrain from using host networking.&lt;br&gt;&lt;br&gt;
We are also binding the open ports to the host's localhost interface. This does not let us connect to the endpoints directly. In our example we don't need the ports to be exposed to the internet but we will need access to them. What we can do is bind the open ports to the host's localhost and then use ssh to forward the ports onto our own machine, assuming the docker host is a remote.&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-L&lt;/span&gt; 127.0.0.1:5460:127.0.0.1:5455 user@remotehost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While building milla, for the second stage of the build, we made a non-privileged user and our now mapping a non-privileged user on the host to that user. We are removing all capabilities from milla since milla will be making requests and has no server functionality. Milla will only need to bind to high-numbered ports which does not require a special privileges.&lt;br&gt;&lt;br&gt;
We run both postgres and milla with gvisor's runsc runtime since it's possible to do so.&lt;br&gt;&lt;br&gt;
Finally we use docker secrets to put the secrets into the container's runtime environment.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Now onto the attestations.&lt;br&gt;&lt;br&gt;
In order to view the SBOM for the image we will use docker &lt;a href="https://docs.docker.com/scout/install/"&gt;scout&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker scout sbom milla
docker scout sbom milla_distroless_vendored
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SBOMs can be viewed &lt;a href="https://gist.github.com/terminaldweller/8e8ecdcb68d4052aecb6804823648b4d"&gt;here&lt;/a&gt; and &lt;a href="https://gist.github.com/terminaldweller/f4ede7122f159506f8e6e6be2bfd6a8b"&gt;here&lt;/a&gt; respectively.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Now lets look at the provenance attestations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker buildx imagetools inspect terminaldweller/milla:main &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s2"&gt;"{{ json .Provenance.SLSA }}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;a href="https://gist.github.com/terminaldweller/033ae07a9e685db85b18eb822dea4be3"&gt;here&lt;/a&gt; you can look at the result.&lt;br&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://manpages.debian.org/bookworm/manpages/cgroups.7.en.html"&gt;man 7 cgroups&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;system containers using &lt;a href="https://github.com/lxc/incus"&gt;lxc/incus&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://katacontainers.io/"&gt;katacontainers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>After NTP Comes NTS. After NTS comes sdwdate.</title>
      <dc:creator>Farzad Sadeghi</dc:creator>
      <pubDate>Mon, 29 Apr 2024 20:29:20 +0000</pubDate>
      <link>https://dev.to/terminaldweller/after-ntp-comes-nts-after-nts-comes-sdwdate-44nm</link>
      <guid>https://dev.to/terminaldweller/after-ntp-comes-nts-after-nts-comes-sdwdate-44nm</guid>
      <description>&lt;h1&gt;
  
  
  After NTP Comes NTS. After NTS comes sdwdate.
&lt;/h1&gt;

&lt;p&gt;Well for this one I will be talking a bit about NTP and NTS.&lt;br&gt;&lt;br&gt;
Unlike the DNS post there isn't much going on here.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;NTP is plain-text, NTS uses TLS so if our requests are tampered with, we can know.&lt;br&gt;&lt;br&gt;
There is the "oooh, you cant see what I'm sending now" but in this case its NTP so the content being secret is not necessarily more important than making sure the content has not been modified(guarantee of integrity).&lt;br&gt;&lt;/p&gt;

&lt;p&gt;So far so good.&lt;br&gt;&lt;br&gt;
But before we go any further, lets talk about what we are trying to achieve here, in other works, what requirements are we trying to satisfy here:&lt;br&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;REQ-001: The NTP(NTS) requests shall be anonymous&lt;/li&gt;
&lt;li&gt;REQ-002: It shall be evient when an NTP(NTS) requests has been tampered with&lt;/li&gt;
&lt;li&gt;REQ-003: It should not be known which time servers are being used upstream by the client&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are wondering why any of this even matters you can have a look &lt;a href="https://www.whonix.org/wiki/Time_Attacks"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now talk about the problem. The protocol is fine. We are sending TCP with TLS here. That's brilliant. We get all this:&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* Identity: Through the use of a X.509 public key infrastructure, implementations can cryptographically establish the identity of the parties they are communicating with.
* Authentication: Implementations can cryptographically verify that any time synchronization packets are authentic, i.e., that they were produced by an identified party and have not been modified in transit.
* Confidentiality: Although basic time synchronization data is considered nonconfidential and sent in the clear, NTS includes support for encrypting NTP extension fields.
* Replay prevention: Client implementations can detect when a received time synchronization packet is a replay of a previous packet.
* Request-response consistency: Client implementations can verify that a time synchronization packet received from a server was sent in response to a particular request from the client.
* Unlinkability: For mobile clients, NTS will not leak any information additional to NTP which would permit a passive adversary to determine that two packets sent over different networks came from the same client.
* Non-amplification: Implementations (especially server implementations) can avoid acting as distributed denial-of-service (DDoS) amplifiers by never responding to a request with a packet larger than the request packet.
* Scalability: Server implementations can serve large numbers of clients without having to retain any client-specific state.
* Performance: NTS must not significantly degrade the quality of the time transfer. The encryption and authentication used when actually transferring time should be lightweight.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Excerpt from &lt;a href="https://www.rfc-editor.org/rfc/rfc8915"&gt;RFC 8915&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we find a client that lets us use a SOCKS5 proxy, then we can send our NTS requests over Tor and then call it a day.&lt;br&gt;&lt;br&gt;
REQ-002 and REQ-003 are being satisfied by using TLS. The missing piece is REQ-001, anonymizing the requests.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;This is not something for the protocol to handle so then we have to look for a client that support a SOCKS5 proxy.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately &lt;a href="https://gitlab.com/chrony/chrony"&gt;chrony&lt;/a&gt; and &lt;a href="https://github.com/pendulum-project/ntpd-rs"&gt;ntpd-rs&lt;/a&gt; do not support SOCKS5 proxies.&lt;br&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;for ntpd-rs look &lt;a href="https://github.com/pendulum-project/ntpd-rs/discussions/1365"&gt;here&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which means our setup is not complete.&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;We will be using ntpd-rs as the client.&lt;br&gt;&lt;br&gt;
We will also setup one NTS server using &lt;a href="https://gitlab.com/NTPsec/ntpsec"&gt;ntpsec&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[observability]&lt;/span&gt;
&lt;span class="py"&gt;log-level&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"info"&lt;/span&gt;
&lt;span class="py"&gt;observation-path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/var/run/ntpd-rs/observe"&lt;/span&gt;

&lt;span class="nn"&gt;[[source]]&lt;/span&gt;
&lt;span class="py"&gt;mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"nts"&lt;/span&gt;
&lt;span class="py"&gt;address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"virginia.time.system76.com"&lt;/span&gt;

&lt;span class="nn"&gt;[[source]]&lt;/span&gt;
&lt;span class="py"&gt;mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"nts"&lt;/span&gt;
&lt;span class="py"&gt;address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"mmo1.nts.netnod.se"&lt;/span&gt;

&lt;span class="nn"&gt;[[source]]&lt;/span&gt;
&lt;span class="py"&gt;mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"nts"&lt;/span&gt;
&lt;span class="py"&gt;address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ntppool1.time.nl"&lt;/span&gt;

&lt;span class="nn"&gt;[[source]]&lt;/span&gt;
&lt;span class="py"&gt;mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"nts"&lt;/span&gt;
&lt;span class="py"&gt;address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ntp1.glypnod.com"&lt;/span&gt;

&lt;span class="nn"&gt;[[source]]&lt;/span&gt;
&lt;span class="py"&gt;mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"nts"&lt;/span&gt;
&lt;span class="py"&gt;address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ntp3.fau.de"&lt;/span&gt;

&lt;span class="nn"&gt;[synchronization]&lt;/span&gt;
&lt;span class="py"&gt;single-step-panic-threshold&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1800&lt;/span&gt;
&lt;span class="py"&gt;startup-step-panic-threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;forward&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"inf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;backward&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1800&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;minimum-agreeing-sources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="py"&gt;accumulated-step-panic-threshold&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1800&lt;/span&gt;

&lt;span class="nn"&gt;[[server]]&lt;/span&gt;
&lt;span class="py"&gt;listen&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"127.0.0.1:123"&lt;/span&gt;

&lt;span class="nn"&gt;[[server]]&lt;/span&gt;
&lt;span class="py"&gt;listen&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"172.17.0.1:123"&lt;/span&gt;

&lt;span class="nn"&gt;[[server]]&lt;/span&gt;
&lt;span class="py"&gt;listen&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"192.168.121.1:123"&lt;/span&gt;

&lt;span class="nn"&gt;[[server]]&lt;/span&gt;
&lt;span class="py"&gt;listen&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"10.167.131.1:123"&lt;/span&gt;

&lt;span class="nn"&gt;[[server]]&lt;/span&gt;
&lt;span class="py"&gt;listen&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"[::1]:123"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;nts&lt;/span&gt; &lt;span class="n"&gt;enable&lt;/span&gt;
&lt;span class="n"&gt;nts&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;letsencrypt&lt;/span&gt;/&lt;span class="n"&gt;live&lt;/span&gt;/&lt;span class="n"&gt;nts&lt;/span&gt;.&lt;span class="n"&gt;dehein&lt;/span&gt;.&lt;span class="n"&gt;org&lt;/span&gt;/&lt;span class="n"&gt;privkey&lt;/span&gt;.&lt;span class="n"&gt;pem&lt;/span&gt;
&lt;span class="n"&gt;nts&lt;/span&gt; &lt;span class="n"&gt;cert&lt;/span&gt; /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;letsencrypt&lt;/span&gt;/&lt;span class="n"&gt;live&lt;/span&gt;/&lt;span class="n"&gt;nts&lt;/span&gt;.&lt;span class="n"&gt;dehein&lt;/span&gt;.&lt;span class="n"&gt;org&lt;/span&gt;/&lt;span class="n"&gt;fullchain&lt;/span&gt;.&lt;span class="n"&gt;pem&lt;/span&gt; &lt;span class="n"&gt;mintls&lt;/span&gt; &lt;span class="n"&gt;TLS1&lt;/span&gt;.&lt;span class="m"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;nts&lt;/span&gt; &lt;span class="n"&gt;cookie&lt;/span&gt; /&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;lib&lt;/span&gt;/&lt;span class="n"&gt;ntp&lt;/span&gt;/&lt;span class="n"&gt;nts&lt;/span&gt;-&lt;span class="n"&gt;keys&lt;/span&gt;
&lt;span class="n"&gt;nts&lt;/span&gt;-&lt;span class="n"&gt;listen&lt;/span&gt;-&lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="m"&gt;4460&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;prefer&lt;/span&gt;

&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="n"&gt;ntpmon&lt;/span&gt;.&lt;span class="n"&gt;dcs1&lt;/span&gt;.&lt;span class="n"&gt;biz&lt;/span&gt; &lt;span class="n"&gt;nts&lt;/span&gt;  &lt;span class="c"&gt;# Singapore
&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="n"&gt;ntp1&lt;/span&gt;.&lt;span class="n"&gt;glypnod&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt; &lt;span class="n"&gt;nts&lt;/span&gt; &lt;span class="c"&gt;# San Francisco
&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="n"&gt;ntp2&lt;/span&gt;.&lt;span class="n"&gt;glypnod&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt; &lt;span class="n"&gt;nts&lt;/span&gt; &lt;span class="c"&gt;# London
&lt;/span&gt;
&lt;span class="n"&gt;tos&lt;/span&gt; &lt;span class="n"&gt;maxclock&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;

&lt;span class="n"&gt;restrict&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="n"&gt;kod&lt;/span&gt; &lt;span class="n"&gt;limited&lt;/span&gt; &lt;span class="n"&gt;nomodify&lt;/span&gt; &lt;span class="n"&gt;noquery&lt;/span&gt;
&lt;span class="n"&gt;restrict&lt;/span&gt; -&lt;span class="m"&gt;6&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="n"&gt;kod&lt;/span&gt; &lt;span class="n"&gt;limited&lt;/span&gt; &lt;span class="n"&gt;nomodify&lt;/span&gt; &lt;span class="n"&gt;noquery&lt;/span&gt;

&lt;span class="n"&gt;driftfile&lt;/span&gt; /&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;lib&lt;/span&gt;/&lt;span class="n"&gt;ntp&lt;/span&gt;/&lt;span class="n"&gt;ntp&lt;/span&gt;.&lt;span class="n"&gt;drift&lt;/span&gt;

&lt;span class="n"&gt;statsdir&lt;/span&gt; /&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;log&lt;/span&gt;/&lt;span class="n"&gt;ntpstats&lt;/span&gt;/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.9"&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;filebrowser&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ntpsec&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;128M&lt;/span&gt;
    &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json-file"&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;max-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;50m"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ntsnet&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4460:4460/tcp"&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ntpd"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-n"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-I"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-d"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./ntp.conf:/etc/ntp.conf:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/live/nts.dehein.org/fullchain.pem:/etc/letsencrypt/live/nts.dehein.org/fullchain.pem:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/live/nts.dehein.org/privkey.pem:/etc/letsencrypt/live/nts.dehein.org/privkey.pem:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;vault:/var/lib/ntp&lt;/span&gt;
    &lt;span class="na"&gt;cap_drop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ALL&lt;/span&gt;
    &lt;span class="na"&gt;cap_add&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SYS_NICE&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SYS_RESOURCE&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SYS_TIME&lt;/span&gt;
&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ntsnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vault&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What comes after NTS
&lt;/h2&gt;

&lt;p&gt;Above we looked at NTP and NTS. We failed to find a client that supports SOCKS5 but that's a trivial matter.&lt;br&gt;&lt;br&gt;
What is not trivial, however, is how NTS and NTP work, and by that I mean you will still have to ask a server to tell you the time.&lt;br&gt;&lt;br&gt;
Doing so over Tor or other anonymizing networks should be fine but we can choose to try out another method of doing things.&lt;br&gt;&lt;br&gt;
Enter &lt;code&gt;sdwdate&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  sdwdate
&lt;/h3&gt;

&lt;p&gt;It still has the same flaw as NTP/NTS as in we still have to trust a server not to lie &lt;a href="https://www.kicksecure.com/wiki/Sdwdate#sdwdate_Source_Pools"&gt;please look here&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
Personally, It is a bit of a disappointment that the protocol that's supposed to be oh-so-much-shinier and newer than NTP has the same flawed mechanism as NTP.&lt;br&gt;&lt;br&gt;
Now granted having hardware that tells you the time so that you can share that with everyone else is not something trivial or readily-available but this only makes sdwdate desirable in the absence of an NTS client that support SOCKS5 proxy.&lt;br&gt;&lt;br&gt;
Once that is done, the larger user pool of NTS/NTP will offer more protection against the smaller userbase of sdwdate.&lt;br&gt;&lt;br&gt;
sdwdate gives a table of comparison between itself and NTP. Let's take at look at that:&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Let's take a look at &lt;code&gt;sdwdate&lt;/code&gt;. It is a roller-coaster. And I do mean that. So don't make up your mind until the very end.&lt;br&gt;
There is a comparison between NTP and sdwdate made &lt;a href="https://www.kicksecure.com/wiki/Sdwdate#Sdwdate_vs_NTP"&gt;here&lt;/a&gt; by kicksecure themselves.&lt;br&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;category&lt;/th&gt;
&lt;th&gt;sdwdate&lt;/th&gt;
&lt;th&gt;ntp&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;written in memory-safe language&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;distributed trust&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;secure connection by default&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gradual clock adjustments&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;daemon&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;functional over tor&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tor not required&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;client, time fetcher&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server, time provider&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AppArmor profile&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;systemd security hardening,seccomp&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;drop-in config folder&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;proxy support&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;possible to secure by default on GNU/Linux distribution level&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;secure&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;optional GUI&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;memory-safety: I mean its good and all that sdwdate uses a memory-safe language(python) but NTP is a protocol. Not sure how NTP is bound to a single programming language. The one client we mentioned before uses rust which guarantees memory safety.&lt;/li&gt;
&lt;li&gt;secure connection by default: NTS uses TLS v1.3 . Not sure why sdwdate is being compared against NTP and not NTS.&lt;/li&gt;
&lt;li&gt;functional over Tor: again, NTS uses TCP which can pass through a SOCKS5 proxy as is implemented by the current incarnation of Tor. Also, not sure, but are we comparing against the NTP protocol or a specific implementation?&lt;/li&gt;
&lt;li&gt;Tor not required: what if I want to use &lt;a href="https://github.com/PurpleI2P/i2pd"&gt;i2p&lt;/a&gt; or &lt;a href="https://github.com/yggdrasil-network/yggdrasil-go"&gt;yggdrasil&lt;/a&gt; to sync time over? Why does it have to be Tor?&lt;/li&gt;
&lt;li&gt;apparmor profile: not sure why this is even included. You can write one for NTP implementations.&lt;/li&gt;
&lt;li&gt;systemd security hardening, seccomp: same as above. You can do it for NTP/NTS implementations as well.&lt;/li&gt;
&lt;li&gt;drop-in config folder: what's a folder? Is that supposed to be a directory? Second, what does that even mean? And third, who is writing these? The only kind of people who make this sort of mistake are people who use MS Windows more than Linux. This is official kicksecure documentation. You have Windows users writing these for the ultra secure and hardened "Linux", I'll say it again, "Linux", distro?&lt;/li&gt;
&lt;li&gt;proxy support: again, NTS uses TCP so it supports SOCKS5 proxies as well but for whatever reason we are comparing against NTP(though whether we are comparing against the protocol or an implementation is something left to be decided by the next generation of humans)&lt;/li&gt;
&lt;li&gt;possible to secure by default on GNU/Linux distribution level: whats the GNU/Linux distribution level? What does this even mean? You can secure it on the OS level? I mean it's software so I would hope that it would be possible to secure it on the software level.&lt;/li&gt;
&lt;li&gt;secure: what are the criteria? Secure against what? And again, why are we comparing to NTP and not NTS?&lt;/li&gt;
&lt;li&gt;optional GUI: again not sure why we keep zig-zagging between comparing implementations and the protocols.
In conclusion, why is that table even there? What purpose does it even serve?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we were going to base our judgement on the documentation provided on kicksecure's website, I am sorry to say that &lt;code&gt;sdwdate&lt;/code&gt; does a very poor job but fortunately that's not all there is to it.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Now let's go take a look at the github README for the project:&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;At randomized intervals, sdwdate connects to a variety of webservers and extracts the time stamps from http headers (RFC 2616))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is our first spark of brilliance.&lt;br&gt;&lt;br&gt;
The second spark is when we consider the practical meaning of only being able to use Tor v3 addresses. Like a wise man once said:&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;amateurs practice something until they can get it right. pros practice something until they can't get it wrong.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result of using only Tor v3 addresses is that you cannot leak your real IP address no matter what happens. You either have a working Tor proxy in which case the IP address will be that of the exit node or none at all.&lt;/p&gt;

&lt;p&gt;Now we know we definitely are dealing with a very promising solution.&lt;br&gt;
'sdwdate' extracts the time stamp in the http header so we are not asking a known NTP server about the time, we are just doing a normal http request.&lt;/p&gt;

&lt;h2&gt;
  
  
  DISCLAIMER
&lt;/h2&gt;

&lt;p&gt;Although unrelated, it is worth noting that the kicksecure docs are pretty good even if you are not planning on using kicksecure.&lt;br&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8915"&gt;RFC 8915&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/jauderho/nts-servers"&gt;Here&lt;/a&gt; you can find a list of publicly available servers that support NTS&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Kicksecure/sdwdate"&gt;sdwdate's github page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.kicksecure.com/wiki/Sdwdate"&gt;sdwdate doc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc2616"&gt;RFC 2616&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

timestamp:1713478033
  version:1.1.0
  https://blog.terminaldweller.com/rss/feed
  https://raw.githubusercontent.com/terminaldweller/blog/main/mds/NTP.md




</description>
    </item>
    <item>
      <title>What to do with your DNS when ODoH's Trust-Me-Bruh Model doesn't work for you</title>
      <dc:creator>Farzad Sadeghi</dc:creator>
      <pubDate>Thu, 29 Feb 2024 17:45:56 +0000</pubDate>
      <link>https://dev.to/terminaldweller/what-to-do-with-your-dns-when-odohs-trust-me-bruh-model-doesnt-work-for-you-4d16</link>
      <guid>https://dev.to/terminaldweller/what-to-do-with-your-dns-when-odohs-trust-me-bruh-model-doesnt-work-for-you-4d16</guid>
      <description>&lt;p&gt;DNS.&lt;br&gt;&lt;br&gt;
Domain Name System.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;We all use it. We all need it. But most people are still using it like its the early 2000s.&lt;br&gt;&lt;br&gt;
What do I mean by that?&lt;br&gt;&lt;br&gt;
Ye good ole UDP on port 53.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;And your ISP will tell ya you don't need to worry about your privacy because they swear on boy scout honor that they don't log your DNS queries. Right .... &lt;br&gt;&lt;/p&gt;

&lt;p&gt;It's 2024. We have come a long way. We have DoH, DoT, ODoH, DNSCrypt and more.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;We're going to talk about all of these for a little bit and then finally I'm going to share what I am doing right now.&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Problem Statement
&lt;/h2&gt;

&lt;p&gt;Plain jane DNS, i.e., sending your request using UDP without any sort of encryption, has been the norm for almost ever. Even right now that is what most people are doing.&lt;br&gt;
That might have been oh-so-cool in the 80s but It doesn't fly anymore.&lt;br&gt;
So we ended up with DoH and DoT. DNS-over-HTTPS and DNS-over-TLS. They are both self-explanatory. Instead of doing unencrypted requests over UDP, we do a TCP request using HTTPS or TLS.&lt;br&gt;&lt;br&gt;
So far so good. DoH and DoT are definitely improvements over &lt;a href="https://www.rfc-editor.org/rfc/rfc1035"&gt;RFC 1035&lt;/a&gt; but let's take a step back and see what we are trying to defend against. Without a structure, we are not doing much more than just magic granted to us by the flying spaghetti monster.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Let's review our threat model.What are we trying to achieve here? What are the threats and who are the threat actors?&lt;br&gt;
Who are we safeguarding our DNS queries against? Men-in-the-middle? Our internet provider? The authoritative DNS server that we use?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Statement&lt;/em&gt;&lt;/strong&gt;: We want to have a &lt;strong&gt;&lt;em&gt;private&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;anonymous&lt;/em&gt;&lt;/strong&gt; DNS solution. That means:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Requirement 001&lt;/em&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The DNS queries shall only be viewed by the authoritative DNS server(We can up this requirement later by running our own authoritative DNS server but for now we are going to stick with our current requirement).
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This naturally means that your internet provider and other men-in-the-middle are not allowed to snoop on what we are querying.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Requirement 002&lt;/em&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The DNS queries shall be anonymous. This means the authoritative DNS server that is getting our DNS queries shall not be able to identify the source of the query.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is more than one way to "identify" the source of the query. We only mean the source as in the IP address that made the DNS query.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;This second requirement is what ODoH is trying to solve. ODoH tries to separate the identity of the source of the DNS query from the query itself.&lt;br&gt;&lt;br&gt;
ODoH stands for oblivous DoH. It add an "oblivious" proxy in middle of the source of the DNS query and the server. This way the proxy can send the queries in bulk for example to try to mask who sent what when. I'm summarizing here but what ODoH is trying to do can be summarized by this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ODoH tries to separate the identity of the source of the query from the query itself by adding a proxy in the middle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below you can see&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        --- [ Request encrypted with Target public key ] --&amp;gt;
   +---------+             +-----------+             +-----------+
   | Client  +-------------&amp;gt; Oblivious +-------------&amp;gt; Oblivious |
   |         &amp;lt;-------------+   Proxy   &amp;lt;-------------+  Target   |
   +---------+             +-----------+             +-----------+
       &amp;lt;-- [   Response encrypted with symmetric key   ] ---
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://datatracker.ietf.org/doc/rfc9230/"&gt;ripped straight from RFC 9230&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main problem with this sort of a solution is that there is always an element of "trust-me-bruh" to the whole situation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How can we trust that the proxy provider and the server are not colluding?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We could run our own oblivious proxy but then if it's just you and your friends using the proxy, then your proxy is not obfuscating much, is it now?&lt;br&gt;&lt;br&gt;
And then there is the "oblivious" aspect of the solution. How can we enforce that? How can you verify that?&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trust Me Bruh. We don't Log anything ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have cryptography, We have zk. I think we can do better than just blind trust.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Objectively speaking, and I'm not accusing anyone of anything so it's just a hypothetical but if someone would give me some money and they asked me to come up with a system which let's them practically monopolize access to DNS queries, I would propose ODoH.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;It has enough mumbo jumbo tech jargon(end-to-end-encrypted, ...) to throw off your average layman and lul them into a false sense of security and privacy but it doesnt prevent the proxy and server provider from colluding. After all the technnical jargon, you end up with "it's safe" and "it's private" because "you can trust us". &lt;br&gt;&lt;/p&gt;

&lt;p&gt;Now we can see that DoH, DoT and ODoH are all better than baseline DNS queries over UDP without encryption but they can't satisfy both of our requirements.&lt;br&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;Now let's talk about the solution I at the time of writing this blog post.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;DoH or DoT is good enough to satisfy &lt;code&gt;Requirement001&lt;/code&gt; but they need something a little extra to be able to satisfy &lt;code&gt;Requirement002&lt;/code&gt;.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;For that, we use an anonymizing network like tor. DoT and DoH both work over TCP so we can use any SOCKS5 proxy here that ends up being a Tor proxy.&lt;br&gt;&lt;br&gt;
What I mean is you can use a the Tor running on your host or you can use &lt;code&gt;ssh -L&lt;/code&gt; to use Tor running on a VPS. That way, your internet proviedr can't know you're using Tor at all.&lt;br&gt;&lt;br&gt;
With your DNS queries going over Tor, we can satisfy &lt;code&gt;Requirement002&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
Tor is not the only solution here but I use Tor. There is more than one anonimyzing network out there and there are protocols that do this also.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Right now we have an outline in our head:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We need to only use TCP for DNS and send everything over a Tor SOCKS5 proxy.&lt;/li&gt;
&lt;li&gt;we will be using DoT or DoH. This will be useful in two ways. One we ensure we are using TCP for DNS which is what most SOCKS5 implementations support(even though they should support UDP because it's SOCKS5 and not SOCKS4 but that's another can of worms)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is more than one way to do this but I have decided to use &lt;a href="https://github.com/DNSCrypt/dnscrypt-proxy"&gt;dnscrypt-proxy&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
We will not be using dnscrypt for the dnscrypt protocol though you could elect to use that as the underlying DNS protocol.&lt;br&gt;&lt;br&gt;
&lt;code&gt;dnscrypt-proxy&lt;/code&gt; lets's us use a SOCKS5 proxy through which the DNS queries will be sent. We will use a Tor SOCKS5 proxy here. You can choose which protocols should be enabled and which ones should be disabled.&lt;br&gt;&lt;br&gt;
There are two points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one, enable the tcp only option, since we dont want to use plain jane UDP queries.&lt;/li&gt;
&lt;li&gt;two, I have asked &lt;code&gt;dnscrypt-proxy&lt;/code&gt; to only use DNS servers that support DNSSEC.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I recommend going through all the available options in the &lt;code&gt;dnscrypt-proxy.toml&lt;/code&gt; file. It is one of those config files with comments so it's pretty sweet. There are quite a few useful options in there that you might care about depending on your needs.&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;Right now I run &lt;code&gt;dnscrypt-proxy&lt;/code&gt; on a small alpine linux VM. I made it fancier by running the VM on a tmpfs storage pool. Basically mine is running entirely on RAM.&lt;br&gt;&lt;br&gt;
I used to have &lt;code&gt;dnscrypt-proxy&lt;/code&gt; running on a raspberry pi and had my openwrt router forward DNS queries to that raspberry pi.&lt;br&gt;&lt;br&gt;
There is obviously no best solution here. Just pick one that works for you.&lt;br&gt;&lt;br&gt;
Here you can find the vagrantfile I use for the DNS VM I use:&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'VAGRANT_DEFAULT_PROVIDER'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'libvirt'&lt;/span&gt;
&lt;span class="no"&gt;Vagrant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;require_version&lt;/span&gt; &lt;span class="s1"&gt;'&amp;gt;= 2.2.6'&lt;/span&gt;
&lt;span class="no"&gt;Vagrant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'generic/alpine319'&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;box_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'4.3.12'&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;box_check_update&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'virt-dns'&lt;/span&gt;

  &lt;span class="c1"&gt;# ssh&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ssh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ssh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keep_alive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ssh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys_only&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

  &lt;span class="c1"&gt;# timeouts&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boot_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;graceful_halt_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ssh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;

  &lt;span class="c1"&gt;# shares&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;synced_folder&lt;/span&gt; &lt;span class="s1"&gt;'.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/vagrant'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s1"&gt;'nfs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;nfs_version: &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;nfs_udp: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="ss"&gt;:private_network&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:ip&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'192.168.121.93'&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:libvirt__domain_name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'devidns.local'&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provider&lt;/span&gt; &lt;span class="s1"&gt;'libvirt'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;storage_pool_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ramdisk'&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'dns-'&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'kvm'&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'256'&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cpus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sound_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;qemuargs&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="s1"&gt;'-nographic'&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;qemuargs&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="s1"&gt;'-nodefaults'&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;qemuargs&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="s1"&gt;'-no-user-config'&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;qemuargs&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="s1"&gt;'-serial'&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;qemuargs&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="s1"&gt;'pty'&lt;/span&gt;
    &lt;span class="n"&gt;libvirt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt; &lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="s1"&gt;'random'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provision&lt;/span&gt; &lt;span class="s1"&gt;'reqs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s1"&gt;'shell'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'reqs-install'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;inline: &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SHELL&lt;/span&gt;&lt;span class="sh"&gt;
    sudo apk update &amp;amp;&amp;amp;\
      sudo apk upgrade &amp;amp;&amp;amp;\
      sudo apk add tor dnscrypt-proxy privoxy tmux
&lt;/span&gt;&lt;span class="no"&gt;  SHELL&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provision&lt;/span&gt; &lt;span class="s1"&gt;'reqs-priv'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s1"&gt;'shell'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s1"&gt;'reqs-priv-install'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;privileged: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;inline: &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SHELL&lt;/span&gt;&lt;span class="sh"&gt;
    cp /vagrant/torrc /etc/tor/torrc
    cp /vagrant/dnscrypt-proxy.toml /etc/dnscrypt-proxy/dnscrypt-proxy.toml
    #cp /vagrant/config /etc/privoxy/config
    rc-service tor start
    sleep 1
    #rc-service privoxy start
    #sleep 1
    rc-service dnscrypt-proxy start
&lt;/span&gt;&lt;span class="no"&gt;  SHELL&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's pretty straightforward. We use an alpine linux VM as base. Make a new interface on the VM with a static IP and have &lt;code&gt;dnscrypt-proxy&lt;/code&gt; receive DNS queries through that interface and IP only. I don't change the port number(53) because of certain applications(you know who you are) refusing to accept port for a DNS server's address.&lt;br&gt;&lt;br&gt;
You could also make it spicier by using &lt;code&gt;privoxy&lt;/code&gt;. Maybe I make a post about that later.&lt;br&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to get your SMS on IRC</title>
      <dc:creator>Farzad Sadeghi</dc:creator>
      <pubDate>Wed, 31 Jan 2024 13:11:14 +0000</pubDate>
      <link>https://dev.to/terminaldweller/how-to-get-your-sms-on-irc-3fhi</link>
      <guid>https://dev.to/terminaldweller/how-to-get-your-sms-on-irc-3fhi</guid>
      <description>&lt;h1&gt;
  
  
  How to get your SMS on IRC
&lt;/h1&gt;

&lt;p&gt;It's not really a continuation of the "one client for everything" post but it is in the same vein. Basically, in this post we are going to make it so that we receive our SMS messages on IRC. More specifically, it will send it to a IRC channel.&lt;br&gt;&lt;br&gt;
In my case this works and is actually secure, since the channel I have the SMS going to is on my own IRC network which only allows users in after they do a successful SASL authentication.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;The general idea is this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We run an app on our phone that will send the SMS to a web hook server&lt;/li&gt;
&lt;li&gt;The web hook server has an IRC client that will send the message to the IRC channel&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  security considerations
&lt;/h3&gt;
&lt;h4&gt;
  
  
  SMS vs &lt;a href="https://en.wikipedia.org/wiki/Rich_Communication_Services"&gt;RCS&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;For forwarding the SMS I get on my cellphone from my cellphone to the web hook server, i use &lt;a href="https://github.com/bogkonstantin/android_income_sms_gateway_webhook"&gt;android_income_sms_gateway_webhook&lt;/a&gt;. This app does not support RCS(see &lt;a href="https://github.com/bogkonstantin/android_income_sms_gateway_webhook/issues/46"&gt;#46&lt;/a&gt;).&lt;br&gt;&lt;br&gt;
For this to work, make sure your phone has RCS disabled unless you use another app that supports RCS.&lt;br&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  web hook server connection
&lt;/h4&gt;

&lt;p&gt;The app will be connecting to our web hook server. The ideal way I wanted to do this would be to connect to a VPN, only through which we can access the web hook server. But its android not linux. I dont know how I can do that on android so that's a no go.&lt;br&gt;&lt;br&gt;
Next idea is to use local port mapping using openssh to send the SMS through the ssh tunnel. While that is very feasible without rooting the phone, a one-liner in termux can take care of it but automating it is a bit of a hassle.&lt;br&gt;&lt;br&gt;
Currently the only measure I am taking is to just use https instead of http.&lt;br&gt;&lt;br&gt;
Since we are using only tls we can use the normal TLS hardening measures, server-side. We are using nginx as the reverse proxy. We will also terminate the tls connection on nginx.&lt;br&gt;&lt;br&gt;
We will be using &lt;a href="https://github.com/pocketbase/pocketbase"&gt;pocketbase&lt;/a&gt; for the record storage and authentication. We can extend pocketbase which is exactly how we will be making our sms web hook.&lt;br&gt;&lt;br&gt;
Pocketbase will give us the record storage and authentication/registration we need. We will use &lt;a href="https://github.com/lrstanley/girc"&gt;girc&lt;/a&gt; for our IRC library. My personal IRC network wll require successful SASL authentication before letting anyone into the network so supporting SASL auth(PLAIN) is a requirement.&lt;/p&gt;

&lt;p&gt;We can use basic http authentication using our chosen app. We can configure the JSON body of the POST request our web hook server will receive.&lt;br&gt;&lt;br&gt;
The default POST request the app will send looks like this:&lt;br&gt;&lt;br&gt;
For the body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"%from%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"%text%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sentStamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"%sentStamp%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"receivedStamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"%receivedStamp%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sim"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"%sim%"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And for the header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"User-Agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SMS Forwarder App"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We get static cerdentials so we can only do basic http auth. We dont need to encode the client information into the security token so we'll just rely on a bearer-token in the header for both authentication and authorization.&lt;br&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Authentication and Authorization
&lt;/h4&gt;

&lt;p&gt;In our case, the only resource we have is to be able to post anything on the endpoint so in our case, authentication and authorization will be synonimous.&lt;br&gt;&lt;br&gt;
We can put the basic auth cerdentials in the url:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://user:pass@sms.mywebhook.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also do please remember that on the app side we need to add the authorization header like so:&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Basic base64-encoded-username:password"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As for the url, use your endpoint without using the username and passwor in the URI.&lt;br&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dev works
&lt;/h3&gt;

&lt;p&gt;You can find the finished code &lt;a href="https://github.com/terminaldweller/sms-webhook"&gt;here&lt;/a&gt;.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Here's a brief explanation of what the code does:&lt;br&gt;&lt;br&gt;
We launch the irc bot in a goroutine. The web hook server will only respond to POST requests on &lt;code&gt;/sms&lt;/code&gt; after a successful basic http authentication.&lt;br&gt;&lt;br&gt;
In our case there is no reason not to use a randomized username as well. So effectively we will have two secrets this way. You can create a new user in the pocketbase admin panel. Pocketbase comes with a default collection for users so just create a new entry in there.&lt;br&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The code will respond with a 401 for all failed authentication attempts.
&lt;/li&gt;
&lt;li&gt;We dont fill out missing credentials for non-existant users to make timing attacks harder. Thats something we can do later.
&lt;/li&gt;
&lt;/ul&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;events&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kn"&gt;worker_connections&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;http&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="n"&gt;/etc/nginx/mime.types&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;server_tokens&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;limit_req_zone&lt;/span&gt; &lt;span class="nv"&gt;$binary_remote_addr&lt;/span&gt; &lt;span class="s"&gt;zone=one:10m&lt;/span&gt; &lt;span class="s"&gt;rate=30r/m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;keepalive_timeout&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;charset&lt;/span&gt; &lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt; &lt;span class="n"&gt;/etc/letsencrypt/live/sms.terminaldweller.com/fullchain.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="n"&gt;/etc/letsencrypt/live/sms.terminaldweller.com/privkey.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_ciphers&lt;/span&gt; &lt;span class="s"&gt;HIGH:!aNULL:!MD5:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_protocols&lt;/span&gt; &lt;span class="s"&gt;TLSv1.2&lt;/span&gt; &lt;span class="s"&gt;TLSv1.3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_session_cache&lt;/span&gt; &lt;span class="s"&gt;shared:SSL:50m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_session_timeout&lt;/span&gt; &lt;span class="s"&gt;1d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_session_tickets&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_prefer_server_ciphers&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;tcp_nopush&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Content-Type-Options&lt;/span&gt; &lt;span class="s"&gt;"nosniff"&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Strict-Transport-Security&lt;/span&gt; &lt;span class="s"&gt;"max-age=31536000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;includeSubDomains"&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Frame-Options&lt;/span&gt; &lt;span class="s"&gt;SAMEORIGIN&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-XSS-Protection&lt;/span&gt; &lt;span class="s"&gt;"1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;mode=block"&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Referrer-Policy&lt;/span&gt; &lt;span class="s"&gt;"no-referrer"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;fastcgi_hide_header&lt;/span&gt; &lt;span class="s"&gt;X-Powered-By&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;error_page&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt; &lt;span class="n"&gt;/404.html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://sms-webhook:8090&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.9"&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;sms-webhook&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sms-webhook&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;256M&lt;/span&gt;
    &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json-file"&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;max-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;100m"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;smsnet&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pb-vault:/sms-webhook/pb_data&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./config.toml:/opt/smswebhook/config.toml&lt;/span&gt;
    &lt;span class="na"&gt;cap_drop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ALL&lt;/span&gt;
    &lt;span class="na"&gt;dns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;9.9.9.9&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SERVER_DEPLOYMENT_TYPE=deployment&lt;/span&gt;
    &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/sms-webhook/sms-webhook"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;serve"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--http=0.0.0.0:8090"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;nginx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;128M&lt;/span&gt;
    &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json-file"&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;max-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;100m"&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:stable&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8090:443"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;smsnet&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;cap_drop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ALL&lt;/span&gt;
    &lt;span class="na"&gt;cap_add&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CHOWN&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DAC_OVERRIDE&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SETGID&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SETUID&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NET_BIND_SERVICE&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx.conf:/etc/nginx/nginx.conf:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/live/sms.terminaldweller.com/fullchain.pem:/etc/letsencrypt/live/sms.terminaldweller.com/fullchain.pem:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/live/sms.terminaldweller.com/privkey.pem:/etc/letsencrypt/live/sms.terminaldweller.com/privkey.pem:ro&lt;/span&gt;
&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;smsnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bridge&lt;/span&gt;
&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pb-vault&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



timestamp:1706042815
  version:1.1.0
  https://blog.terminaldweller.com/rss/feed
  https://raw.githubusercontent.com/terminaldweller/blog/main/mds/lazymakefiles.md




</description>
    </item>
    <item>
      <title>One Chat Client for Everything</title>
      <dc:creator>Farzad Sadeghi</dc:creator>
      <pubDate>Fri, 10 Nov 2023 16:25:40 +0000</pubDate>
      <link>https://dev.to/terminaldweller/one-chat-client-for-everything-3i8b</link>
      <guid>https://dev.to/terminaldweller/one-chat-client-for-everything-3i8b</guid>
      <description>&lt;h2&gt;
  
  
  One Client for Everything
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Foreword&lt;/li&gt;
&lt;li&gt;Two ways of solving this&lt;/li&gt;
&lt;li&gt;The web app way&lt;/li&gt;
&lt;li&gt;gui or terminal client&lt;/li&gt;
&lt;li&gt;Matrix or IRC&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Foreword
&lt;/h2&gt;

&lt;p&gt;First let's talk about the problem we're trying to solve here. I want to have a unified interface into all the communication forms that I use.&lt;br&gt;&lt;br&gt;
I can't be bothered to have different clients open all the time. I want to have one client that takes care of all things mostly well.&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Two ways of solving this
&lt;/h2&gt;

&lt;p&gt;There is generally two ways one can try to solve this. number one is to just use a browser. Almost all forms of comm nowadays have a web client so basically one way of solving our problem is to a dedicated browser that has all the clients open. Mind you, there are even specialized and more lightweight browser offerings specifically geared towards this use-case but still this option is not ideal in terms of resources and the interface you're getting is not really unified.&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The web app way
&lt;/h3&gt;

&lt;p&gt;An example that comes to mind for this sort of solution is &lt;code&gt;rambox&lt;/code&gt; though they are no longer offering a FOSS solution. I'm just mentioning them as an example of what's being offered out there as a ready-to-use solution.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Although this way of doing things is very resource-intensive, this is the &lt;strong&gt;complete&lt;/strong&gt; way of doing things. What I mean by that is that by using the official web apps, you will not be compromising on any features that the clients offer since you will be using the official clients.&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  gui or terminal client
&lt;/h3&gt;

&lt;p&gt;The second way of going about and solving this is to pick a very good client that supports a protocol with a lot of bridges and then bridge everything through to the app of that one protocol.&lt;br&gt;&lt;br&gt;
Currently there are only three protocols that have enough facilities for bridging to make this feasible. IRC, Matrix and XMPP.&lt;br&gt;&lt;br&gt;
I'm adding XMPP for the sake of completion but in terms of practicality XMPP doesn't have nearly as many bridges as IRC and Matrix.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;So this basically narrows down our choice to either IRC or Matrix.&lt;br&gt;&lt;br&gt;
Now lets look at the clients that are available for these two protocols.&lt;br&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Matrix or IRC
&lt;/h3&gt;

&lt;p&gt;The last requirement on my side is that i would rather use a unified terminal keyboard-based client than a web application client. That being said, i definitely expect to use a web client since using a terminal client on a smart phone is pretty much just pain. A lot of pain.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately at the time of writing this post, Matrix has no terminal client that comes close to either &lt;a href="https://github.com/irssi/irssi"&gt;irssi&lt;/a&gt; or &lt;a href="https://github.com/weechat/weechat"&gt;weechat&lt;/a&gt;, both terminal clients originally only supporting IRC but later advertising themselves as multi-chat clients.&lt;br&gt;&lt;br&gt;
Also as an added bonus, starting from the next irssi release which should be irssi v1.5 one can elect not to build the IRC module at all while building irssi.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Matrix and IRC both have a rich ecosystem of bridges. Matrix has a growing fan base which means more and more bridges or tools with similar functionality will be releases for it. Contrast that with IRC where that number seems to be smaller than Matrix but still is very much alive and well.&lt;br&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;a href="https://github.com/bitlbee/bitlbee"&gt;bitlbee-libpurple&lt;/a&gt;
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;it'll be bitlbee
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;bitlbee is a bridge software for IRC. The distinguishing feature for bitlbee is that the way it bridges other protocols to IRC is by masquerading as an ircd.&lt;br&gt;&lt;br&gt;
You could also use libpurple as the backend for bitlbee (&lt;a href="https://wiki.bitlbee.org/HowtoPurple"&gt;link&lt;/a&gt;).&lt;br&gt;&lt;br&gt;
libpurple has an origin story similar to libreadline. Basically it used to live inside pidgin, but later on it was turned into a library so that other applications could use it as well.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;List of protocols supported by libpurple:&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aim
bitlbee-discord
bitlbee-mastodon
bonjour
eionrobb-icyque
eionrobb-mattermost
eionrobb-rocketchat
facebook
gg
hangouts
hehoe-signald
hehoe-whatsmeow
icq
irc
jabber
matrix
meanwhile
novell
otr
simple
sipe
skypeweb
slack
steam
telegram-tdlib
zephyr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;a href="https://github.com/42wim/matterbridge"&gt;matterbridge&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;matterbridge is an everything-to-everything bridge.&lt;br&gt;&lt;/p&gt;

&lt;p&gt;Please keep in mind that with matterbridge, you don't get the full functionality of a protocol as in you get no private messages and such. You get the ability to join public chat rooms or whatever they call it in that protocol.&lt;br&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  bridge ircds
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/42wim/matterircd"&gt;matterircd&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;a mattermost bridge that emulates an ircd as the name implies.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/progval/matrix2051"&gt;matrix2051&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;another bridge that emulates an ircd, but for matrix.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/adsr/irslackd"&gt;irslackd&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;a bridge to slack that emulates an ircd.&lt;/p&gt;

&lt;h3&gt;
  
  
  docker compose
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/ezkrg/docker-bitlbee-libpurple"&gt;Here&lt;/a&gt;'s the original Dockerfile. You can find mine &lt;a href="https://github.com/terminaldweller/docker-bitlbee-libpurple"&gt;here&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
And here's the docker compose file I use that goes with that:&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.8"&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;bitlbee&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;devi_bitlbee&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;384M&lt;/span&gt;
    &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json-file"&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;max-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;100m"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bitlbeenet&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:8667:6667"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;172.17.0.1:8667:6667"&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bitlbee:bitlbee"&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/usr/sbin/bitlbee"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-F"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-n"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-u"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bitlbee"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/var/lib/bitlbee/bitlbee.conf"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-d"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/var/lib/bitlbee"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;dns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;9.9.9.9&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./conf/bitlbee.conf:/var/lib/bitlbee/bitlbee.conf:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;userdata:/var/lib/bitlbee&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/home/devi/.cache/docker-bitlbee/signald/run:/var/run/signald&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/etc/ssl/certs:/etc/ssl/certs:ro&lt;/span&gt;
  &lt;span class="na"&gt;signald&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;signald/signald:stable&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;384M&lt;/span&gt;
    &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json-file"&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;max-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;100m"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;signalnet&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:7775:7775"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;172.17.0.1:7775:7775"&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;dns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;9.9.9.9&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/home/devi/.cache/docker-bitlbee/signald/run:/signald&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/etc/ssl/certs:/etc/ssl/certs:ro&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SIGNALD_ENABLE_METRICS=false&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SIGNALD_HTTP_LOGGING=true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SIGNALD_VERBOSE_LOGGING=true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SIGNALD_METRICS_PORT=7775&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SIGNALD_LOG_DB_TRANSACTIONS=true&lt;/span&gt;
  &lt;span class="na"&gt;matterircd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;42wim/matterircd:latest&lt;/span&gt;
    &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;384M&lt;/span&gt;
    &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json-file"&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;max-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;100m"&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;matterircdnet&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:7667:7667"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;172.17.0.1:7667:7667"&lt;/span&gt;
    &lt;span class="na"&gt;dns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;9.9.9.9&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--conf"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/matterircd.toml"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./matterircd.toml:/matterircd.toml:ro&lt;/span&gt;
&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;bitlbeenet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;signalnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;matterircdnet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;userdata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;matterircddb&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



timestamp:1699398469
  &lt;br&gt;
  version:0.1.0
  &lt;br&gt;
  https://blog.terminaldweller.com/rss/feed




</description>
    </item>
    <item>
      <title>colo - 256 terminal colors in almost all formats</title>
      <dc:creator>Farzad Sadeghi</dc:creator>
      <pubDate>Mon, 30 Jan 2023 18:22:17 +0000</pubDate>
      <link>https://dev.to/terminaldweller/colo-256-terminal-colors-in-almost-all-formats-1occ</link>
      <guid>https://dev.to/terminaldweller/colo-256-terminal-colors-in-almost-all-formats-1occ</guid>
      <description>&lt;p&gt;I spend most of my time in the terminal. Colorizing things makes detecting important pieces of information faster and more efficient in the terminal.&lt;br&gt;&lt;br&gt;
Colo is a little script that helps with choosing colors for the terminal applications. It can output the colors in different formats, rgb,numbers, hex, hsi so one can quickly choose the colors that one wants to use.&lt;br&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/terminaldweller" rel="noopener noreferrer"&gt;
        terminaldweller
      &lt;/a&gt; / &lt;a href="https://github.com/terminaldweller/colo" rel="noopener noreferrer"&gt;
        colo
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A simple color script which print the 256 terminal  colors in rgb,hex,numbered, ...
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;colo&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;A simple script that prints out the 256 terminal colors in different formats.&lt;br&gt;
It can print the numbers, the hex value, rgb,hsi and the ansi escape sequence.&lt;br&gt;&lt;/p&gt;
&lt;div class="highlight highlight-text-adblock notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;colo --help
usage: colo [-h] [--ansi] [--hsi] [--rgb] [--number] [--name] [--hex]

optional arguments:
  -h, --help  show this help message and exit
  --ansi      bool
  --hsi       bool
  --rgb       bool
  --number    bool
  --name      bool
  --hex       bool&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/terminaldweller/colo./img/ansi.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fterminaldweller%2Fcolo.%2Fimg%2Fansi.png" alt="Image"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How to get&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;pipx install colo&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Also since this is a very small package without any dependencies you could always use &lt;code&gt;pipx run&lt;/code&gt; without "installing the package":&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;pipx run colo&lt;/pre&gt;

&lt;/div&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/terminaldweller/colo" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;&lt;br&gt;
I wrote it to make my life in the terminal easier.&lt;br&gt;&lt;br&gt;&lt;br&gt;
I guess the "feature" that colo has is that supports printing the colors in different formats and I can get it with a simple &lt;code&gt;pip install colo&lt;/code&gt; on a new machine/vm/container.&lt;br&gt;&lt;br&gt;&lt;br&gt;
Hope someone else finds it useful.&lt;br&gt;

</description>
      <category>discuss</category>
      <category>community</category>
      <category>devrel</category>
    </item>
    <item>
      <title>keyoxide</title>
      <dc:creator>Farzad Sadeghi</dc:creator>
      <pubDate>Mon, 30 Jan 2023 14:52:42 +0000</pubDate>
      <link>https://dev.to/terminaldweller/keyoxide-4koh</link>
      <guid>https://dev.to/terminaldweller/keyoxide-4koh</guid>
      <description>&lt;p&gt;openpgp4fpr:9E20464F1CCF3B103249FA93A6A0F5158B3881DF&lt;/p&gt;

</description>
      <category>crypto</category>
      <category>web3</category>
      <category>blockchain</category>
      <category>offers</category>
    </item>
  </channel>
</rss>
