<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Konst's Blog – Site Reliability Engineering and Technology]]></title><description><![CDATA[Read Konst's Blog about about Software Development, SRE, AWS, DevOps/DevSecOps, Linux. Konst is a software developer based in Auckland, New Zealand.]]></description><link>https://blog.konst.kiwi</link><generator>RSS for Node</generator><lastBuildDate>Sat, 18 Apr 2026 13:35:51 GMT</lastBuildDate><atom:link href="https://blog.konst.kiwi/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Neovim with the TypeScript language server (LSP)]]></title><description><![CDATA[I’m trying a few new things.
I’ve got a project that I’ve been experimenting with and I plan to publish as open source at some point. For this project I decided to revisit my old friend TypeScript. It’s a language I’ve used in the past, but I haven’t...]]></description><link>https://blog.konst.kiwi/neovim-with-the-typescript-language-server-lsp</link><guid isPermaLink="true">https://blog.konst.kiwi/neovim-with-the-typescript-language-server-lsp</guid><category><![CDATA[neovim]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[experimentation]]></category><category><![CDATA[lsp]]></category><category><![CDATA[vim]]></category><category><![CDATA[cli]]></category><dc:creator><![CDATA[Konstantin Tchernov]]></dc:creator><pubDate>Tue, 14 Oct 2025 09:45:16 GMT</pubDate><content:encoded><![CDATA[<p>I’m trying a few new things.</p>
<p>I’ve got a project that I’ve been experimenting with and I plan to publish as open source at some point. For this project I decided to revisit my old friend TypeScript. It’s a language I’ve used in the past, but I haven’t touched it professionally.</p>
<p>I’ve also been using Neovim more and more as my IDE instead of my old default Visual Studio Code. Especially for the smaller hobby projects. Switching it up a bit.</p>
<p>If you’re unfamiliar, definitely check it out, it is Vim but easier to customise and extend with Lua scripting. One big benefit is the variety of third party plugins you can find for it. Someone will burn me for this comparison, but Neovim reminds me of Emacs and its extensibility with Elisp, which is something I dabbled with earlier on in my career.</p>
<p>One thing that has been tricky though is that, while popular, Neovim is fairly new and at the time of writing is on v0.11.x. Some things are changing quickly and some things don’t have a lot of examples online.</p>
<p>Today I figured out TypeScript LSP and I thought I’d share it.</p>
<h2 id="heading-dependencies">Dependencies</h2>
<p>You may be running npm that pulls in some dependencies for you. For example I have versioned and local <code>typescript</code> per <code>package.json</code> that I pull in. But because nvim is global, you should set up some global dependencies.</p>
<p>You’ll need to install <code>npm install -g typescript typescript-language-server</code></p>
<p>The former you already know, the latter comes from <a target="_blank" href="https://github.com/typescript-language-server/typescript-language-server">https://github.com/typescript-language-server/typescript-language-server</a> and is a standalone binary.</p>
<p>If you haven’t already, installing <code>nvim</code> I will leave it as an exercise for the reader. I also alias <code>vi</code> to <code>nvim</code> because I have a few decades of habit of typing <code>vi</code> to open files.</p>
<h3 id="heading-initlua">init.lua</h3>
<p>nvim is configured by init.lua, which is not there by default if you haven’t already started configuring it.</p>
<p>Make sure <code>~/.config/nvim/</code> exists, create it if not.</p>
<p><code>nvim ~/.config/nvim/init.lua</code></p>
<p>And this is where all the docs I’ve found get out of date. From nvim v0.11.x, the LSP config is:</p>
<pre><code class="lang-plaintext">" Enable TypeScript via the Language Server Protocol (LSP)
vim.lsp.enable('tsserver')

" Set the TS config for the LSP
vim.lsp.config('tsserver', {
  " Make sure this is on your path
  cmd = {'typescript-language-server', '--stdio'},
  filetypes = { 'typescript' },
  " This is a hint to tell nvim to find your project root from a file within the tree
  root_dir = vim.fs.root(0, {'package.json', '.git'}),
  on_attach = on_attach,
  capabilities = capabilities,
  " optional settings = {...} go here, refer to language server code: https://github.com/typescript-language-server/typescript-language-server/blob/5c483349b7b4b6f79d523f8f4d854cbc5cec7ecd/src/ts-protocol.ts#L379
})
</code></pre>
<p>Then you can set some key bindings, this is what I’m starting with, I’ll adapt and add some more later. Note that these are not specific to the TypeScript LSP, you can find more examples online for LSP keybindings:</p>
<pre><code class="lang-plaintext">-- lsp keymaps
vim.keymap.set('n', '&lt;leader&gt;rn', vim.lsp.buf.rename, {})
vim.keymap.set('n', 'gd', vim.lsp.buf.definition, {})
vim.keymap.set('n', '&lt;leader&gt;f', function()
  vim.lsp.buf.format { async = true }
end, {})
</code></pre>
<p>The <code>’n’</code> above tells it to enable these keybindings in normal vim mode (not insert mode). And <code>&lt;leader&gt;</code> can be configured, defaults to <code>\</code></p>
<h3 id="heading-testing">Testing</h3>
<p>Now open up a typescript file.</p>
<p>Keep in mind that as per the config above, we need a <code>package.json</code> or <code>.git</code> in your tree for nvim to see your full project. But it will work on any random <code>.ts</code> file in isolation.</p>
<p>My example:</p>
<pre><code class="lang-plaintext">nvim /tmp/test.ts
</code></pre>
<p>To check if LSP is configured successfully, run this command in nvim:</p>
<pre><code class="lang-plaintext">:checkhealth vim.lsp
</code></pre>
<p>And you should see the diagnostic like so. In particular look for Active Clients (what is configured for your current buffer) and Enabled Configuration (global available LSP settings):</p>
<pre><code class="lang-plaintext">vim.lsp:                                                                    ✅

- LSP log level : WARN
- Log path: /Users/kt/.local/state/nvim/lsp.log
- Log size: 11 KB

vim.lsp: Active Clients ~
- tsserver (id: 1)
  - Version: ? (no serverInfo.version response)
  - Root directory: nil
  - Command: { "typescript-language-server", "--stdio" }
  - Settings: {}
  - Attached buffers: 1

vim.lsp: Enabled Configurations ~
- tsserver:
  - cmd: { "typescript-language-server", "--stdio" }
  - filetypes: typescript


vim.lsp: File Watcher ~
- file watching "(workspace/didChangeWatchedFiles)" disabled on all clients

vim.lsp: Position Encodings ~
- No buffers contain mixed position encodings
</code></pre>
<p>Then go back to the typescript file and type</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">testyTest</span>(<span class="hljs-params">a: <span class="hljs-built_in">number</span></span>) </span>{
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">testyTestEmpty</span>(<span class="hljs-params"></span>) </span>{
}

tes
</code></pre>
<p>And from here trigger your code completion. Default keybinding is <code>C-n</code>. And this is what I see, code completion comes up, and you can make a selection with <code>TAB</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760433258544/376dd1db-261c-41ef-a948-b9512db3f90f.png" alt="Image showing neovim with the above code snippet, tes, followed by a drop down offering the options testyTest or testyTestEmpty" class="image--center mx-auto" /></p>
<h3 id="heading-diagnostic-hints-and-errors">Diagnostic hints and errors</h3>
<p>After you’ve accepted that, you can try something like this:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">testyTest</span>(<span class="hljs-params">a: <span class="hljs-built_in">number</span></span>) </span>{
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">testyTestEmpty</span>(<span class="hljs-params"></span>) </span>{
}

testyTest()
</code></pre>
<p>Once you exit insert mode, you will see these marks:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760433547313/9c3bc7ec-6bd3-42b7-b392-31e38923c43d.png" alt="image showing the above code snippet with an H next to the function defintion of testyTest and an E next to the invocation of testyTest" class="image--center mx-auto" /></p>
<p>This is Neovim’s diagnostic marks - the H indicates a Hint, E indicates Error. The default keybindings are <code>]d</code> / <code>[d</code> to jump backwards and forwards between them in the buffer, and <code>&lt;C-w&gt;d</code> to shows diagnostic at cursor like so:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760434129853/1ee1701b-5d85-4077-882a-e7c5b7e2e1c5.png" alt="image showing details of the diagnostic error on the testyTest() function invocation" class="image--center mx-auto" /></p>
<p><a target="_blank" href="https://neovim.io/doc/user/diagnostic.html">See the docs for full diagnostic mark keybindings</a> and of course you can remap them.</p>
<h3 id="heading-going-further">Going further</h3>
<p>I’ve only just scratched the surface here. I found that there wasn’t a particular single guide to string everything together. So I wrote up what I found.</p>
<p>Check out all the other features available in LSP. Look up general guides on Neovim LSP, they don’t need to be TypeScript specific.</p>
<p>Also look at the excellent Neovim docs, either through their website or through <code>:help lsp</code> / <code>:help diagnostics</code> in-app, these are the parts I referenced here:</p>
<ul>
<li><p><a target="_blank" href="https://neovim.io/doc/user/lsp.html">Neovim’s LSP docs</a></p>
</li>
<li><p><a target="_blank" href="https://neovim.io/doc/user/diagnostic.html">Neovim's diagnostic marks</a></p>
</li>
<li><p><a target="_blank" href="https://neovim.io/doc/user/usr_40.html#_key-mapping">Neovim’s Keymapping</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Four for Feb]]></title><description><![CDATA[I remember the days before the Internet was for everyone. It was a nerdy thing that you'd disconnect your parents' phone line for. Social media, endless scroll feeds, and hourly news updates were not a thing. Twitter was not there, and hate groups we...]]></description><link>https://blog.konst.kiwi/four-feb24-links</link><guid isPermaLink="true">https://blog.konst.kiwi/four-feb24-links</guid><category><![CDATA[Nostalgia]]></category><category><![CDATA[links]]></category><category><![CDATA[internet]]></category><category><![CDATA[social media]]></category><category><![CDATA[technology]]></category><category><![CDATA[Blogging]]></category><dc:creator><![CDATA[Konstantin Tchernov]]></dc:creator><pubDate>Mon, 05 Feb 2024 08:49:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/iar-afB0QQw/upload/482b388de890d6c44dca1c487342f79c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I remember the days before the Internet was for everyone. It was a nerdy thing that you'd disconnect your parents' phone line for. Social media, endless scroll feeds, and hourly news updates were not a thing. Twitter was not there, and hate groups were hidden in their own little fringe communities rather than being algorithmically rubbed into your face. You couldn't put the Internet in your pocket to take with you everywhere.</p>
<p>A few weird people, myself included, had personal blogs, and you'd do Trackbacks to your friend's blogs or to other Internet weirdos with similar interests. I didn't even think of putting in a hit counter or analytics.</p>
<p>A little before blogging, Geocities! Who needs a reminder? Who doesn't miss those colours, gradients and WordArt GIF banners? </p>
<p>Facebook started appearing later in this phase for me, for uni students only; I logged in once, sent a poke, and left it for several years. IRC, MSN or ICQ is where you'd truly talk to people. The choice of messaging system alone would tell you a bit about a person.</p>
<p>No, perhaps it wasn't all great and wonderful as I described. There is a bit of omission there. But those are some of the bits I remember fondly.</p>
<p>In that spirit, I recently read a few things that reminded me of those old days of wonder and hope. About relatively low tech, about blogging with interesting ideas – over viral content and likes or posturing.</p>
<ul>
<li>I love Kelly Shortridge's blog, and especially her post on <a target="_blank" href="https://kellyshortridge.com/blog/posts/when-something-disappears-from-the-internet/">Amonia Plant Safety</a>. Say whaat? Just read it if you're in tech, or not. I quoted it in a tech conference talk on incidents last year.</li>
<li><a target="_blank" href="https://deramp.com/swtpc.com/MySystem/Northwest_Computer_News_July_1978_pg1.jpg">My System (1978)</a> (alt text version <a target="_blank" href="https://deramp.com/swtpc.com/MySystem/MySystem.htm">here</a>) - a short write-up by a man called Michael Holley that gives a glimpse into how you'd put a personal computer together in the 70s. I love the enthusiasm, and the pride in that photo.</li>
<li>A post about local exploration <a target="_blank" href="https://www.noemamag.com/a-single-small-map-is-enough-for-a-lifetime">A single small map is enough for a lifetime</a>. Coincidentally, I have been doing a lot more of this (not in the same manner as the author!) since going full remote at the end of last year, and it has brought me a lot of fascination for the things I've been missing within short biking distance from my house.</li>
<li>Saving the best for last. I really enjoyed <a target="_blank" href="https://www.fromjason.xyz/p/notebook/where-have-all-the-websites-gone/">Where Have All the Websites Gone</a> by Jason. It gave me the idea for this post. Check out Jason's other content, instant follow.</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Rate limiting  with iptables]]></title><description><![CDATA[When running any site on the internet, it will be scanned regularly by various scripts. This is true for everything I've hosted, from a cat charity blog to financial software sites. It's not personal.
Rate limiting is a rudimentary line of defence ag...]]></description><link>https://blog.konst.kiwi/rate-limiting-with-iptables</link><guid isPermaLink="true">https://blog.konst.kiwi/rate-limiting-with-iptables</guid><category><![CDATA[iptables]]></category><category><![CDATA[firewall]]></category><category><![CDATA[ratelimit]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[Konstantin Tchernov]]></dc:creator><pubDate>Sat, 20 May 2023 10:07:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/s48kOKFzUpw/upload/b6a83f10bd49d5101cd8e56e9e848e94.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When running any site on the internet, it will be scanned regularly by various scripts. This is true for everything I've hosted, from a cat charity blog to financial software sites. It's not personal.</p>
<p>Rate limiting is a rudimentary line of defence against scanners, brute force attempts, SYN floods, and basic Denial of Service (DoS) attacks. Do not put all your faith into rate limiting, but it is a useful tool to whack on in most circumstances.</p>
<h2 id="heading-when-to-use-iptables">When to use iptables</h2>
<p>There are lots of different places where you can rate limit. I've often run into a scenario where I need to run a single-instance site without putting extra infrastructure around it. There could be cost or other restrictions in play.</p>
<p>It could even be a third-party app you're running that you cannot easily modify. This is where knowledge of iptables is beneficial.</p>
<h2 id="heading-background-on-iptables">Background on iptables</h2>
<p>I have done <a target="_blank" href="https://blog.konst.kiwi/hands-on-linux-firewall-rules-with-iptables">an introduction to iptables and connection tracking in an earlier post</a>, and this post will assume a basic understanding of what <code>iptables</code> are.</p>
<h2 id="heading-target-state">Target state</h2>
<p>I will use Docker for this example, as Docker port forwarding creates a few interesting steps. But you can use any web service with <code>iptables</code>.</p>
<p>If you're <em>not</em> on Docker, you will use the <code>INPUT</code> chain instead of <code>FORWARD</code>. The rest of the principles are the same.</p>
<p>Docker's default bridge networking is an extra layer inside your host. The outside traffic is forwarded to the internal Docker network. When the traffic wants to get out of Docker, it is forwarded out of this network.</p>
<p>What we want to get to in terms of iptables is illustrated in this diagram below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1684570971114/b685b8e6-627d-4fd7-a0d6-f92d86607fc5.png" alt class="image--center mx-auto" /></p>
<p>Docker's default bridge networking will configure the <code>FORWARD</code> and <code>DOCKER-USER</code> chains for us. Then we will plug our rate limit solution into that.</p>
<h3 id="heading-forward-chain">FORWARD chain</h3>
<p>The <code>FORWARD</code> chain is when your host works as a proxy, forwarding the traffic it has received to somewhere else. In the context of Docker, this is when the traffic is forwarded to the internal bridge network.</p>
<h3 id="heading-docker-user-chain">DOCKER-USER chain</h3>
<p>The Docker daemon configures this as the first rule in the <code>FORWARD</code> chain. By default, it is empty. When you add your own rules into this chain, they get evaluated before traffic to the Docker network.</p>
<h2 id="heading-set-up">Set up</h2>
<h3 id="heading-1-create-a-rate-limit-chain">1. Create a RATE-LIMIT chain</h3>
<p>Follow the basic <a target="_blank" href="https://blog.konst.kiwi/hands-on-linux-firewall-rules-with-iptables#heading-set-up-iptables">set up steps for iptables</a> if you don't already have them configured.</p>
<p>Add new chains called <code>RATE-LIMIT</code> and <code>LOGGING</code> (or any other name you think is most appropriate):</p>
<pre><code class="lang-bash">sudo iptables --new-chain RATE-LIMIT
sudo iptables --new-chain LOGGING
sudo iptables -L -n <span class="hljs-comment"># view all the rules</span>
</code></pre>
<p>This should give you a list of rules ending with the new empty chain:</p>
<pre><code class="lang-plaintext">Chain LOGGING (0 references)
target     prot opt source               destination

Chain RATE-LIMIT (0 references)
target     prot opt source               destination
</code></pre>
<h3 id="heading-2-docker">2. Docker</h3>
<p>If you don't already have Docker setup, follow <a target="_blank" href="https://docs.docker.com/engine/install/">the official instructions</a>. Also, look at the post-install steps to give your user Docker access if you don't want to use <code>sudo</code>.</p>
<p>To run a hello-world with Docker, I use this:</p>
<pre><code class="lang-bash">docker run -d -p 8080:80 nginx:latest

<span class="hljs-comment"># Test it</span>
curl localhost:8080
</code></pre>
<p>The last step will print a welcome page HTML.</p>
<h3 id="heading-3-set-the-ratelimit">3. Set the ratelimit</h3>
<p>The rate limit can be implemented using the <code>hashlimit</code> module. There is also a <code>recent</code> module that could be used to accomplish a similar result.</p>
<p>For this example, we set quite an aggressive limit to demonstrate this feature:</p>
<pre><code class="lang-bash">sudo iptables -A RATE-LIMIT --match hashlimit --hashlimit-mode srcip --hashlimit-upto 10/min --hashlimit-name per_ip_conn_rate_limit  --jump RETURN
sudo iptables -A RATE-LIMIT -j LOGGING
sudo iptables -A RATE-LIMIT -j DROP
</code></pre>
<h4 id="heading-explanation">Explanation:</h4>
<ol>
<li><p>Adding a rule to the end of the RATE-LIMIT chain 2.<code>--hashlimit-upto 10/min</code> Match if the IP has made <em>fewer</em> than ten requests per minute 3.<code>-hashlimit-name per_ip_conn_rate_limit</code> use the IP to group connections together (other options include source / dest ports).</p>
</li>
<li><p>If the number of requests is under the specified limit, <code>RETURN</code> to the previous chain 5. If the number of requests exceeds the specified limit, the rule does <em>not</em> match.</p>
</li>
<li><p><code>LOG</code> the offending request.</p>
</li>
<li><p>After the log, we will <code>DROP</code> the offending request</p>
</li>
</ol>
<p>Logging is optional but recommended – you want to see who is probing you and when.</p>
<h3 id="heading-4-configure-the-log">4. Configure the log</h3>
<pre><code class="lang-bash">sudo iptables -A LOGGING -m <span class="hljs-built_in">limit</span> --<span class="hljs-built_in">limit</span> 5/s -j LOG --log-prefix <span class="hljs-string">"iptables-throttled: "</span> --log-level info
sudo iptables -A LOGGING -j RETURN
</code></pre>
<p>This will write to your system log, usually <code>/var/log/messages</code> or <code>/var/log/system</code>, with the prefix <code>iptables-throttled</code>. No more than five times per second to avoid swamping your storage.</p>
<p>More on the logic behind this in <a target="_blank" href="https://blog.konst.kiwi/hands-on-linux-firewall-rules-with-iptables#heading-logging">the earlier post</a></p>
<h3 id="heading-5a-link-the-tables-to-forward-docker-user">5a. Link the tables to FORWARD / DOCKER-USER</h3>
<p>If you've used Docker, you'll link this to the <code>FORWARD</code> chain (see the example below). The <code>FORWARD</code> chain, when configured by Docker, will have the first rule that goes to <code>DOCKER-USER</code> like so:</p>
<pre><code class="lang-plaintext">Chain FORWARD (policy DROP)
target     prot opt source               destination         
DOCKER-USER  all  --  0.0.0.0/0            0.0.0.0/0    

...

Chain DOCKER-USER (1 references)
target     prot opt source               destination         
RETURN     all  --  0.0.0.0/0            0.0.0.0/0
</code></pre>
<p>You can add your new chains to <code>DOCKER-USER</code>, and these will be evaluated before any other Docker-related rules. So add your rate limit here:</p>
<pre><code class="lang-bash">sudo iptables -I DOCKER-USER 1 --jump RATE-LIMIT
</code></pre>
<p>Your <code>sudo iptables -L -n</code> should now end with something like this:</p>
<pre><code class="lang-plaintext">Chain DOCKER-USER (1 references)
target     prot opt source               destination         
RATE-LIMIT  all  --  0.0.0.0/0            0.0.0.0/0           
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           

Chain LOGGING (1 references)
target     prot opt source               destination         
LOG        all  --  0.0.0.0/0            0.0.0.0/0            limit: avg 5/sec burst 5 LOG flags 0 level 6 prefix "iptables-throttled: "
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           

Chain RATE-LIMIT (1 references)
target     prot opt source               destination         
RETURN     all  --  0.0.0.0/0            0.0.0.0/0            limit: up to 5/sec burst 4 mode srcip htable-expire 60000
LOGGING    all  --  0.0.0.0/0            0.0.0.0/0
DROP       all  --  0.0.0.0/0            0.0.0.0/0
</code></pre>
<h3 id="heading-5b-link-the-tables-to-input">5b. Link the tables to INPUT</h3>
<p>If you're <em>not using Docker</em>, then add this <code>RATE-LIMIT</code> chain to the top of your <code>INPUT</code> chain instead. I'll leave that as an exercise to experiment with, but it builds on all the same principles.</p>
<h3 id="heading-6-extra-whitelisting-ips-cidr-ranges">6. Extra: Whitelisting IPs / CIDR ranges</h3>
<p>If you want some IPs or CIDR ranges to bypass the rate limit, add some <code>RETURN</code> rules to the top of your <code>RATE-LIMIT</code> chain.</p>
<p>Docker networks have IP ranges, and you would typically not want Docker containers that talk to each other to be rate limited like you limit external traffic.</p>
<p>You can get their IP ranges with <code>docker network ls</code> and then <code>docker inspect &lt;network_name&gt;</code>, and look under <code>Subnet</code>.</p>
<p>For example, if you're only using the default bridge network, it is called <code>bridge</code>, and you can get its IP range by calling <code>docker inspect bridge | grep Subnet</code>. If this is <code>172.17.0.0/16</code>, then you can add the exception:</p>
<pre><code class="lang-bash">sudo iptables -I RATE-LIMIT 1 --<span class="hljs-built_in">source</span> <span class="hljs-string">"172.17.0.0/16"</span> -j RETURN
</code></pre>
<h3 id="heading-7-test">7. Test</h3>
<p>Make sure that your host is open to remote connections on port 8080. Then run a script from a different machine on a loop hitting your open port and see if it gets limited.</p>
<pre><code class="lang-bash">IP=<span class="hljs-string">"&lt;replace with your host's URL or IP&gt;"</span>
i=1
<span class="hljs-keyword">while</span> <span class="hljs-literal">true</span>; <span class="hljs-keyword">do</span>
  curl -sS --connect-timeout 2 <span class="hljs-string">"<span class="hljs-variable">$IP</span>:8080"</span> &gt;/dev/null
  <span class="hljs-built_in">echo</span> <span class="hljs-string">"Attempt <span class="hljs-variable">$i</span> got exit code $?"</span>
   i=$((i+<span class="hljs-number">1</span>))
<span class="hljs-keyword">done</span>
</code></pre>
<p>You should see connection timeouts after a short time, and the remote host's system log (e.g. <code>/var/log/messages</code>) should contain your logs with the <code>iptables-throttled</code> prefix.</p>
<p>When you <code>tail -f /var/log/messages</code>, you should see lines that look like this:</p>
<pre><code class="lang-plaintext">May 20 08:29:57 helloworld kernel: [597429.154452] iptables-throttled: IN=ens5 OUT=docker0 MAC=12:34:56:78:9A:BC:12:34:56:78:9A:BC:08:00 SRC=123.456.789.1 DST=172.17.0.2 LEN=133 TOS=0x00 PREC=0x00 TTL=44 ID=0 DF PROTO=TCP SPT=54980 DPT=80 WINDOW=2052 RES=0x00 ACK PSH URGP=0
</code></pre>
<p>Note that the DST address is your Docker container's IP address on your internal network, not your host's IP address. Also, see the <code>ACK PSH</code> messages in the example above – these are parts of the TCP protocol.</p>
<p>The rate limit and log messages are based on the components of the TCP protocol, not on each HTTP request.</p>
<h1 id="heading-extension-persistence">Extension: Persistence</h1>
<p>As I described in detail in <a target="_blank" href="https://blog.konst.kiwi/hands-on-linux-firewall-rules-with-iptables#heading-persistence">my first article on iptables</a> – <code>iptables</code> do not persist across reboot by default.</p>
<p>The Docker daemon <code>dockerd</code> will recreate its own networking and rules when the container restarts, but your additions to <code>DOCKER-USER</code> will not stick.</p>
<p>To make them persist, there are several approaches you can take:</p>
<ol>
<li><p>Use the <code>iptables-persistent</code> module to save the state and load it on boot (see <a target="_blank" href="https://blog.konst.kiwi/hands-on-linux-firewall-rules-with-iptables#heading-persistence">my first article on iptables</a>).</p>
<ul>
<li><p>With docker this gets more complicated. You must prevent the Docker daemon from adding further <code>iptables</code> rules. Otherwise, they can clash with your changes. You can do that by setting the key <code>iptables</code> to <code>false</code> in <code>/etc/docker/daemon.json</code></p>
</li>
<li><p>Drawback is that you must persist all the tables generated by the Docker daemon and any other service on your host. Not just your additions.</p>
</li>
</ul>
</li>
<li><p>Add the above rules into a script that runs after the Docker daemon is started.</p>
</li>
</ol>
<p>Option 2 is summarised below.</p>
<h3 id="heading-option-2-persistence-with-systemd">Option 2 – Persistence with systemd</h3>
<p>You can follow this example if your Linux system uses <code>systemd</code> to manage services (default on most distros). We will wait for the Docker daemon service to start, create its own rules, and then attach our rules.</p>
<p>With <code>sudo</code>, create a file <code>/opt/configure-iptables-ratelimit.sh</code> and paste all of the commands you executed above into it.</p>
<pre><code class="lang-sh"><span class="hljs-meta">#!/bin/sh</span>
<span class="hljs-built_in">set</span> -e
</code></pre>
<pre><code class="lang-sh">sudo chmod +x /opt/configure-iptables-ratelimit.sh
</code></pre>
<p>Create a file <code>/etc/systemd/system/iptables-ratelimit.service</code>, and paste the following into it (with <code>sudo</code>):</p>
<pre><code class="lang-plaintext">[Unit]
Description=Configure rate limiting with iptables
After=docker.service

[Service]
Type=oneshot
ExecStart=/opt/configure-iptables-ratelimit.sh

[Install]
WantedBy=multi-user.target
</code></pre>
<p>If you have other services that modify <code>iptables</code>, add them to the <code>After=</code> clause in your <code>systemd</code> config. If you're not using docker, do not add the <code>After</code> line.</p>
<p>Enable the service on boot: <code>sudo systemctl enable iptables-ratelimit</code></p>
<p>Reboot to test the persistence. Remember to start the docker container again (or add a <code>systemd</code> config to start it).</p>
<h1 id="heading-further-reading">Further reading</h1>
<ul>
<li><p><a target="_blank" href="https://www.linux.org/docs/man8/iptables-extensions.html">iptables-extensions docs on hashlimit</a></p>
</li>
<li><p><a target="_blank" href="https://docs.docker.com/network/iptables/">Docker and iptables</a></p>
</li>
<li><p><a target="_blank" href="https://www.howtouselinux.com/post/tcp-flags">TCP protocol flags</a></p>
</li>
<li><p><a target="_blank" href="https://manpages.debian.org/stretch/systemd/systemd.service.5.en.html">Systemd conf manpage</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Time pressure did not cause your outage]]></title><description><![CDATA[You are crafting your beautiful application, paying extra attention to detail, but your boss is breathing down your neck saying: "Ship! Ship! Ship! 🚢 ".
Shortcuts are taken. Mistakes are made. Your bug goes into Prod, and now you have an outage.
May...]]></description><link>https://blog.konst.kiwi/time-pressure-did-not-cause-your-outage</link><guid isPermaLink="true">https://blog.konst.kiwi/time-pressure-did-not-cause-your-outage</guid><category><![CDATA[software development]]></category><category><![CDATA[postmortem]]></category><category><![CDATA[Time management]]></category><category><![CDATA[career advice]]></category><dc:creator><![CDATA[Konstantin Tchernov]]></dc:creator><pubDate>Sun, 30 Apr 2023 19:00:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/buTcpgLbDaI/upload/b8ce3187b10a1e2eee04ecc492148176.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You are crafting your beautiful application, paying extra attention to detail, but your boss is breathing down your neck saying: "Ship! Ship! Ship! 🚢 ".</p>
<p>Shortcuts are taken. Mistakes are made. Your bug goes into Prod, and now you have an outage.</p>
<p>Maybe you've failed to try out your new feature on real-world data, and a bad query is now hammering your database, which does not scale.</p>
<p>You didn't have time to cover all scenarios on realistic datasets.</p>
<p>You didn't have time to write the extra tests.</p>
<p>You didn't think of all the possible scenarios.</p>
<p>This is where you, as a developer, throw your arms up in despair and say: "time pressure!"</p>
<p>I admit it, I absolutely <em>love</em> a good "I told you so!". We warned you, Evilboss, and now look what happened!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682758455177/38e6f817-f5a6-44a8-8cc2-76ccef519367.jpeg" alt="Nelson from Simpsons, pointing his finger with his mouth open. The subtitle says: &quot;I told you so&quot;" class="image--center mx-auto" /></p>
<p>While time pressure may be a significant contributing factor, I also firmly believe developers can do much better, even under the constraint of time. Focusing on this one issue is a distraction.</p>
<h2 id="heading-the-root-cause">The Root Cause</h2>
<p>"Find the Root Cause" is a common phrase. What happened? They ask. The answer is usually – a bug.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1681723694095/11c980ca-cb40-446f-b940-40915228fa2e.png" alt="A hand-drawn word &quot;bug&quot; in all caps, with arrows pointing to it from all sides." class="image--center mx-auto" /></p>
<p>However, we must think beyond that to improve our software craft.</p>
<h2 id="heading-typical-flow">Typical flow</h2>
<p>If your dev outfit is more than one or two developers, you likely have a release process of some sort. Typically it would be a variant of something like the diagram below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682742900356/1368a71b-d1df-40bb-8f3a-664e2d4fe4ec.png" alt="Boxes with arrows between them. In this order: Code, PR, Build, Automated Tests, Manual Testing, Deploy, Release (Feature Flag On), Monitoring and Alerting" class="image--center mx-auto" /></p>
<p>You may have more or fewer steps, but if you're entirely missing something like testing, that is a red flag that something is already not quite right.</p>
<p>You typically go through all these layers for something to be released, even in a rush. You can move through this flow quickly while minimising problems if this flow is set up well.</p>
<h2 id="heading-failures-in-the-flow">Failures in the flow</h2>
<p>When something has failed, look at your version of your development flow above.</p>
<p>Assume that humans are fallible and will write bugs. You cannot prevent that.</p>
<p>Where could your issue have been caught, after the code has been typed but before it hits prod? It may be one or two steps or all the steps, or maybe you need an extra step.</p>
<p>Returning to our example, a bad API is written that is not tested with production data, causing chaos on the database in prod. These are questions you could ask:</p>
<ol>
<li><p><strong>Code</strong> – a bug is written</p>
<ul>
<li><p>Did the developer call a dependency that was not well documented?</p>
</li>
<li><p>Was it a junior working without enough support?</p>
</li>
<li><p>Was there a lack of sound patterns to follow?</p>
</li>
<li><p>Was there an unintended side effect from a complicated legacy method?</p>
</li>
<li><p>Is there fragile legacy code that needs a clean-up?</p>
</li>
</ul>
</li>
<li><p><strong>Peer Review (PR)</strong> – bug is approved</p>
<ul>
<li><p>Are your PRs just rubber stamps?</p>
</li>
<li><p>Was the peer reviewer not qualified?</p>
</li>
<li><p>Was there too much code in one PR?</p>
</li>
</ul>
</li>
<li><p><strong>Build</strong> – build is kicked off and passes</p>
<ul>
<li><p>Do you have unit tests as part of the build?</p>
</li>
<li><p>Do you have good patterns for unit tests to cover similar scenarios?</p>
</li>
<li><p>Do you have linting or other static code checking?</p>
</li>
</ul>
</li>
<li><p><strong>Automated Tests</strong> – integration tests or end-to-end tests</p>
<ul>
<li><p>Is your test dataset realistic?</p>
</li>
<li><p>Do you cover performance?</p>
</li>
<li><p>Are your tests flaky and are now just being ignored?</p>
</li>
</ul>
</li>
<li><p><strong>Deployment</strong> <strong>and Release –</strong> code is deployed, then a feature flag is turned on</p>
<ul>
<li><p>Do you have a rollback plan? Feature flag off?</p>
</li>
<li><p>Can you release a feature to 10% of users for the first few hours to test for errors and performance issues?</p>
</li>
</ul>
</li>
<li><p><strong>Monitoring and Alerting</strong> – this can be an ambulance at the bottom of the cliff, but an important one.</p>
<ul>
<li><p>Did your alerts tell you about the problem, or did your users?</p>
</li>
<li><p>How soon did your alerts go off after the problem was shipped? Can you narrow this time?</p>
</li>
</ul>
</li>
</ol>
<p>Critically look through all this, and then identify the most impactful improvements that can be made.</p>
<h2 id="heading-idealism-vs-reality">Idealism vs Reality</h2>
<p>Not every company can have all the steps above; not everyone is open to change. We don't all live in idealistic blog posts or ride unicorns.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1682760560993/830a16fa-7020-48e6-b3df-1e8cfac15a37.jpeg" alt="A silhouette of a unicorn, standing on its rear legs, in some water. A giant full moon in the background reflects on the water below." class="image--center mx-auto" /></p>
<p>This is where you use the failure to your advantage, show your bosses and coworkers that things need to improve, and then start working towards it bit by bit. You do not need permission to do this!</p>
<p>One test here. One good pattern to follow in the future. A couple of PR comments to your workmates to nudge them towards doing things a little better each time too.</p>
<p>I have seen dreadful codebases at companies with dreadful cultures improve through small iterations made by people who care to improve.</p>
<p>And your results at the end of it will most likely be appreciated. If not, take the experience from doing this to somewhere else where it will be recognised.</p>
<h2 id="heading-back-to-time-pressure">Back to Time Pressure</h2>
<p>Yes, time pressure sucks and makes things worse. You should absolutely raise it as an issue, propose more reasonable alternatives and the like. But you will still have to deal with too much work to squeeze into too little time as part of life.</p>
<p>When time pressure is there, and it is not budging, embrace it as being another constraint, part of an elegant puzzle that needs solving.</p>
<hr />
<p><strong>What do you think? Leave a comment down below.</strong></p>
]]></content:encoded></item><item><title><![CDATA[Dev Retro 2022  – sudo reboot]]></title><description><![CDATA[As a software developer, I have turned my long-time hobby into a job, which is not always a good idea. Your job does become more than just a way to make money, which leads to trouble. This blog post is my retrospective on a big mindset reboot I have ...]]></description><link>https://blog.konst.kiwi/dev-retro-2022-sudo-reboot</link><guid isPermaLink="true">https://blog.konst.kiwi/dev-retro-2022-sudo-reboot</guid><dc:creator><![CDATA[Konstantin Tchernov]]></dc:creator><pubDate>Wed, 28 Dec 2022 00:16:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672185790602/2d43cc20-2cc0-40f6-95c7-94adeb23662c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As a software developer, I have turned my long-time hobby into a job, which is not always a good idea. Your job does become more than just a way to make money, which leads to trouble. This blog post is my retrospective on a big mindset reboot I have been going through in the past year or so.</p>
<p>Late 2021 was when I realised I need a big mindset shift, and 2022 was when I carried through that change and became a lot more intentional about seeking what I want for myself.</p>
<p>This is going to be opinionated. If the advice below goes against the grain for you, that is ok. There isn't a single mould to fit us all, and this is not intended as general-purpose advice for everyone. But I hope I can share my way of thinking with some people.</p>
<h2 id="heading-the-constant-hype">The Constant Hype</h2>
<p>Software Development changes quickly around us, things that were trendy a few years ago will go stale or obsolete. Tools, techniques, and whole paradigms shift.</p>
<p>My father, a Fortran developer, retired this year and likely will not be replaced with another Fortran guru. Fortran was first created in 1957. Even Fortran has not been constant, if you look at it over 60 years, it has had a lot of evolution. But it is hard for me to imagine sticking to one main programming language for my entire career.</p>
<p>Things change fast, and there is always a lot to master. It is not just the programming language that I'm talking about. In the past few years, things that have piqued my interest may have looked like this:</p>
<ul>
<li><p>Mobile apps!</p>
</li>
<li><p>Single Page (web) Apps! React.js, no Vue.js!</p>
</li>
<li><p>Blockchain!</p>
</li>
<li><p>Machine learning!</p>
</li>
<li><p>Computer Vision!</p>
</li>
<li><p>Go, Python, TypeScript, Bash, .NET Core, Rust, keep going.</p>
</li>
<li><p>All the clouds!</p>
</li>
<li><p>Docker, no Serverless, no go back to containers with Kubernetes!</p>
</li>
<li><p>Wait get away from cloud lock-in!</p>
</li>
<li><p>Now the AI tyrants are writing code and taking our jobs!</p>
</li>
</ul>
<p><img src="https://www.memesmonkey.com/images/memesmonkey/5a/5aea0875d011bb6ddc2be1adbce9f4bf.jpeg" alt="Woddy and Buzz Lightyear from Toystory. Buzz is potining into the distance with a smile, and says &quot;Hype, hype everywhere!&quot;. Woody is looking on in horror." class="image--center mx-auto" /></p>
<p>Ok, "hype" is a strong word – a lot of new things are fads, but others are legitimate trends that are good to learn. Trouble is, it can be hard to separate, and you definitely cannot sit still for too long without major change. <em>(Unless you are doing some super specialised Fortran-like archaeology, delivering to an industry that resists change.)</em></p>
<p>If you dive further into each item on that list above, you will find years of further digging you can do into each of those areas to hone your skills.</p>
<h2 id="heading-no-to-always-keep-growing">No to "Always Keep Growing"</h2>
<p>This is where people tell you to "always keep growing"! I have heard this so many times. And honestly, this has been my motto.</p>
<p>While well-meaning, to me this phrase now comes with bad baggage from observing myself as well as others. Elitism. Burnout. Not valuing your workplace or personal relationships. Not valuing your time outside of work. Moving away from your passion into a "higher tier" position or to a "higher tier" company.</p>
<p>I fell into this trap so many times, and after much self-reflection, I am now careful not to chase that.</p>
<h2 id="heading-adapt-with-purpose">Adapt with purpose</h2>
<p>Instead of "always keep growing". Think about what you want to do for yourself and how you can manoeuvre yourself to get there. Be intentional. Be explicit with yourself. Be explicit when you talk to your employer – "I want to move towards this-and-that. I do not want to keep doing so-and-so for long."</p>
<p>That move could be backward, sideways, or in any direction you want. Sometimes it helps to let go of the concept of "growing", and seek something that is fulfilling, even if it feels like more of a steady state.</p>
<p><img src="https://i.imgflip.com/4ana5x.png" alt="Andre the giant from the film &quot;Princess Bride&quot;. He's holding a boulder with a big smirk on his face. Quote on top of it says &quot;I did that on purpose&quot;" /></p>
<p><strong><em>But</em></strong>, keep in mind, that this will include letting go and neglecting some skills that you no longer need on your journey. It is fine to be an expert in X one day and then forget about it later.</p>
<p>This can also mean taking another position or job that is at a less "fancy" company. Even if to others this may look like a demotion. Even if you end up taking less pay in exchange for having a sense of purpose and fulfilment, or a better work-life balance.</p>
<h2 id="heading-dont-chase-the-shiny-titles">Don't chase the shiny titles</h2>
<p>In the past year and something, I have done a very intentional move from Dev Management, being a leader of leads, to a position back to focus on more hands-on tech roles in the Site Reliability Engineering (SRE) and Cloud Architecture space.</p>
<p>It is not that I never want to do management or that I don't like leadership positions. I needed to find something where I can hone in on the technical tools a lot more. And moving into a higher-level leadership position was not helping that goal. Even if it has been something that I was intentionally after for a while.</p>
<p>It does not mean that I never want to pursue that path again. It just means I'm going to put that on hold for now, which is right for me at this point.</p>
<p>The same traps exist if you're pure only the "Individual Contributor" (IC) path, it's easy to get caught up in chasing titles. Senior. Staff. Principal. I was officially a "Master Engineer II" at a top company once (too weird, rephrased on my CV). What I find is that all of it can be somewhat arbitrary and up to the whim of a particular company or people manager. There is also nothing wrong with going from Principal in one company to losing that title in another, especially when you move to a different field.</p>
<p>Don't think about the supposed prestige or the titles of a role too much. They are not universally recognised and chasing those titles can be a distraction away from what you're truly after.</p>
<h1 id="heading-a-new-approach-to-side-missions">A new approach to side missions</h1>
<blockquote>
<p>“What’s the world’s greatest lie?” the boy asked, completely surprised.</p>
<p>“It’s this: that at a certain point in our lives, we lose control of what’s happening to us, and our lives become controlled by fate. That’s the world’s greatest lie”</p>
<p>– The Alchemist, Paulo Coelho</p>
</blockquote>
<p>When taking on a side mission at work, or a home project, where I am somewhat free to look at something else, I used to pick something relevant to what I was doing at work at the time.</p>
<p>My change this year was to let go of that. I have enough experience and enough good reputation in the industry where honing in deeper on something turns into diminishing returns. There are only so many extra opportunities to be gained by going deeper into something I already know.</p>
<p>Instead, I welcome the distraction of doing something tangential, something radically different from what I normally do. Who knows where it will lead career-wise? Maybe nowhere? But I will enjoy the journey nonetheless! And that is more important than the destination to me today.</p>
<h1 id="heading-future">Future</h1>
<p>There are a few things I want to focus on in 2023 that are outside of my hands-on technical skills:</p>
<ul>
<li><p>Technical writing, which is why I'm here.</p>
</li>
<li><p>Public speaking.</p>
</li>
<li><p>Launching my personal side project using the tech I want to play with.</p>
</li>
</ul>
<p>As I've said, this a is a personal retrospective and I hope it can help share my current mindset. The advice is not one side fits all, you always need to think about how things fit your own situation. Please do drop me a line if you have different oppionon in the comments below or find me over at Mastodon.</p>
]]></content:encoded></item><item><title><![CDATA[Hands-on Linux firewall rules with iptables]]></title><description><![CDATA[Let's dive into lower-level firewall configuration at the OS level and go a little beyond the bare basics.
Even when you are hosting with cloud providers that spoil you with their ease of configuration for firewalls, some special requirements may mak...]]></description><link>https://blog.konst.kiwi/hands-on-linux-firewall-rules-with-iptables</link><guid isPermaLink="true">https://blog.konst.kiwi/hands-on-linux-firewall-rules-with-iptables</guid><dc:creator><![CDATA[Konstantin Tchernov]]></dc:creator><pubDate>Tue, 13 Dec 2022 10:51:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1670925377676/zfhjLD_C8.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Let's dive into lower-level firewall configuration at the OS level and go a little beyond the bare basics.</p>
<p>Even when you are hosting with cloud providers that spoil you with their ease of configuration for firewalls, some special requirements may make it necessary for you to understand the lower-level tools.</p>
<p>I have hit this with several different clients, it comes up when their VMs are running a combination of tools such as Docker, third-party security tooling and even CPanel. It gets especially tricky when these third-party tools don't play nice with each other out of the box and set up inconsistent rules. You can end up having to mediate these tools with custom configuration on your part.</p>
<p>In this article, I will run through a few common scenarios with <code>iptables</code>, one of the most popular tools for configuring the Linux kernel firewall. I will cover basic configuration, connection state, persistence, logging, and touch on interaction with Docker in particular.</p>
<h1 id="heading-linux-firewalls">Linux Firewalls</h1>
<p>Linux is such a diverse ecosystem where you choose your tooling, and this extends to firewall configuration utilities. <code>iptables</code> has been around for a long time and is very common but there are others.</p>
<p>Newer versions of Debian ship with <code>nftables</code>, which is a younger cousin of <code>iptables</code>. It is an updated design but bears a lot of similarities.</p>
<p>Under the hood, both <code>iptables</code> and <code>nftables</code> are built around a programmable framework called <code>netfilter</code>. <code>netfilter</code> allows userspace applications to plug into different parts of the Linux kernel networking stack and to define callbacks. Outside of very niche use cases, it is uncommon to use <code>netfilter</code> directly.</p>
<p>These are the common tools, but you could find others or GUI wrappers around the above.</p>
<h1 id="heading-iptables">iptables</h1>
<h2 id="heading-set-up-iptables">Set up iptables</h2>
<p>In your favourite distribution, install <code>iptables</code>. For example Debian and Ubuntu:</p>
<pre><code class="lang-bash">sudo apt-get update
sudo apt-get install iptables
</code></pre>
<p>If you have <code>firewalld</code> installed, for this example please remove or disable it. It is usually not a good idea to mess with <code>iptables</code> directly when you have <code>firewalld</code> running over the top.</p>
<pre><code class="lang-sh">sudo systemctl stop firewalld
sudo systemctl <span class="hljs-built_in">disable</span> firewalld
</code></pre>
<h2 id="heading-defaults">Defaults</h2>
<p>Now list the rules.</p>
<pre><code class="lang-sh">sudo iptables -L
</code></pre>
<p>You should see no restrictions by default, which looks like this:</p>
<pre><code class="lang-txt">Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
</code></pre>
<p>You see in the above there are three default chains:</p>
<ul>
<li><p><code>INPUT</code> – incoming traffic.</p>
</li>
<li><p><code>FORWARD</code> – incoming traffic that is not handled locally, but is passed on to another network (including local Docker networks)</p>
</li>
<li><p><code>OUTPUT</code> – outgoing traffic.</p>
</li>
</ul>
<p>No rules are defined under any of the chains, and the default for all chains is <code>ACCEPT</code> – so any incoming or outgoing packet is accepted.</p>
<h2 id="heading-example">Example</h2>
<p>Let's open port 80 on a host to the world, but open port 8080 only to local network private IP traffic on <code>10.128.0.0/16</code>.</p>
<p><strong><em>CAREFUL</em>:</strong> Use a non-production host to experiment with OS-level firewalls. They are not as easy to undo as cloud-based firewall rules. If you get the rules wrong, you will lock yourself or your users out. If you do get stuck on your test host with the examples below, reboot the host and the rules will reset, more on that later.</p>
<h3 id="heading-open-web-ports">Open web ports</h3>
<pre><code class="lang-sh"><span class="hljs-comment"># 1. Accept incoming TCP traffic to destination port 8080 from any source</span>
sudo iptables  -A INPUT -p tcp --dport 8080 -j ACCEPT

<span class="hljs-comment"># 2. Accept TCP traffic to destination port 8080 from the local network CIDR range</span>
sudo iptables  -A INPUT -p tcp --dport 8081 -s 10.128.0.0/16 -j ACCEPT

<span class="hljs-comment"># 3. Accept TCP traffic on port 22 from your your personal IP address </span>
my_ip=<span class="hljs-comment"># REPLACE THIS WITH YOUR LOCAL IP e.g. 123.45.67.89 </span>
sudo iptables  -A INPUT -p tcp --dport 22 -s <span class="hljs-variable">$my_ip</span>/32 -j ACCEPT
</code></pre>
<h4 id="heading-command-breakdown">Command breakdown</h4>
<p><code>sudo iptables -A INPUT -p tcp --dport 8081 -s 10.128.0.0/16 -j ACCEPT</code> command breakdown:</p>
<ul>
<li><p><code>-A INPUT</code> – add to the <code>INPUT</code> chain (refer to the original table above).</p>
</li>
<li><p><code>-p tcp</code> – the rule applies to the TCP protocol only</p>
</li>
<li><p><code>--dport 8081</code> – the rule applies to the destination port 8081 only</p>
</li>
<li><p><code>-s 10.128.0.0/16</code> – the rule applies to the source IP</p>
</li>
<li><p><code>-j ACCEPT</code> – jump target for the rule. <code>ACCEPT</code> means that the connection will be allowed through.</p>
</li>
</ul>
<p>See <code>man iptables</code> (or <a target="_blank" href="https://linux.die.net/man/8/iptables">here</a>) for complete docs.</p>
<h4 id="heading-resulting-iptables">Resulting iptables</h4>
<p><code>sudo iptables -L -n</code> will that look like this:</p>
<pre><code class="lang-plaintext">Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:8080
ACCEPT     tcp  --  10.128.0.0/16        0.0.0.0/0            tcp dpt:8081
ACCEPT     tcp  --  123.45.67.891         0.0.0.0/0            tcp dpt:22

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
</code></pre>
<h4 id="heading-evaluation">Evaluation</h4>
<p>An incoming connection will be evaluated against the above rules line by line. Once the first condition is met, the action for that condition will be executed. In our case <code>ACCEPT</code>, which will allow the traffic through. No further rules will be evaluated after the first match.</p>
<p>If no rule matches the traffic, then the default policy for the chain will be used. Which in our example is also <code>ACCEPT</code>. So the above config still means "Accept all traffic".</p>
<h4 id="heading-reject">Reject</h4>
<p>Let's reject the traffic that does not match the rules. Again we can append another rule to the end, but this time with a <code>REJECT</code> action.</p>
<pre><code class="lang-sh"><span class="hljs-comment"># 4. REJECT all packets that are not yet matched by other rules</span>
sudo iptables -A INPUT -j REJECT
</code></pre>
<p>Note in all the examples above, <code>iptables -A</code> is used, which appends each rule to the end.</p>
<p>So now the last line of the <code>INPUT</code> table will be:</p>
<pre><code class="lang-txt">REJECT     all  --  0.0.0.0/0            0.0.0.0/0            reject-with icmp-port-unreachable
</code></pre>
<h2 id="heading-test">Test</h2>
<p>To try this out, I like to spin up a one-liner python web server, and run this on your <em>remote host</em>:</p>
<pre><code class="lang-sh">nohup python3 -m http.server 8080 2&gt;/dev/null &amp;
nohup python3 -m http.server 8081 2&gt;/dev/null &amp;
</code></pre>
<p>Then on <em>your local machine</em> see if you can reach the web server. *nix:</p>
<pre><code class="lang-sh">my_host=&lt;your remote host IP&gt;
<span class="hljs-comment"># should succeed:</span>
curl <span class="hljs-variable">$my_host</span>:8080

<span class="hljs-comment"># should fail immediately with "Connection refused":</span>
curl <span class="hljs-variable">$my_host</span>:8081
</code></pre>
<p>Remember that 8080 is open to the world, so you should get a response immediately.</p>
<p><strong>If 8080 is not working</strong>:</p>
<ul>
<li><p>Check that your <code>iptables -L</code> matches the output in the example above – look for the ACCEPT before the blanket REJECT.</p>
</li>
<li><p>Check for any other firewalls between you and your remote host. For example, if you're running on AWS, for this example, open up the security group to allow your local IP on all ports.</p>
</li>
</ul>
<h2 id="heading-problem">Problem</h2>
<p>There is one issue with the above setup. Go back into your <em>remote host</em>, and try any sort of outbound traffic.</p>
<pre><code class="lang-plaintext">ping google.com
curl https://google.com:443/
</code></pre>
<p>Both of the above hang and eventually time out. But what's going on? Your <code>OUTPUT</code> chain is still an <code>ACCEPT</code>-all policy. At a first glance, it might seem that this combination of rules should allow all traffic out of your host.</p>
<p>And it does but it doesn't. Your outbound packets will leave your host and reach the Google web server. But the response back from Google will hit your <code>INPUT</code> chain, not match any of the rules and get <code>REJECT</code>ed.</p>
<p>You can try to fix this by whitelisting any IP you expect to contact, but that is usually not feasible, so you can make use of the connection state.</p>
<h2 id="heading-connection-state">Connection State</h2>
<p>Basic <code>iptables</code> rules are <em>stateless</em>. This means that no state about pre-existing connections is taken into consideration. With stateless rules, for traffic to come out of the host and get a response back you need <code>ACCEPT</code> rules in both the <code>INPUT</code> and <code>OUTPUT</code> tables.</p>
<p>The stateless rules use very few resources as it is just a matter of evaluating the rules one by one without any other context. This would also make them very performant.</p>
<p>You can have <em>stateful</em> rules with the <code>conntrack</code> matcher. When <code>conntrack</code> is used, each connection is tracked in memory and can be looked up during rule evaluation.</p>
<h2 id="heading-fixing-outbound-traffic">Fixing outbound traffic</h2>
<p>To allow a response to come back for any outbound connection that you make, add this rule:</p>
<pre><code class="lang-sh">sudo iptables -I INPUT 1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
</code></pre>
<p>This is saying:</p>
<ol>
<li><p><code>-I INPUT 1</code> Insert a rule at index 1 – we don't want this rule to go to the end, it needs to come before the explicit <code>REJECT</code>.</p>
</li>
<li><p><code>-m conntrack</code> Use the <code>conntrack</code> matcher.</p>
</li>
<li><p><code>--ctstate ESTABLISHED,RELATED</code> Match connections that have a state of <code>ESTABLISHED</code> or <code>RELATED</code> – existing connections only (i.e. created after an accepted <code>OUTPUT</code> message)</p>
</li>
<li><p><code>-j ACCEPT</code> Accept them all.</p>
</li>
</ol>
<p>Now you can check the rules:</p>
<pre><code class="lang-sh">sudo iptables -L -n
</code></pre>
<p>And see the output like this:</p>
<pre><code class="lang-txt">Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:8080
ACCEPT     tcp  --  10.128.0.0/16        0.0.0.0/0            tcp dpt:8081
ACCEPT     tcp  --  123.45.67.89         0.0.0.0/0            tcp dpt:22
REJECT     all  --  0.0.0.0/0            0.0.0.0/0            reject-with icmp-port-unreachable

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
</code></pre>
<p>Try Google again and you will get a response:</p>
<pre><code class="lang-plaintext">ping google.com
curl https://google.com:443/
</code></pre>
<h2 id="heading-reject-vs-drop">REJECT vs DROP</h2>
<p>You can see in the examples above, the <code>REJECT</code> line ends with <code>reject-with icmp-port-unreachable</code>. Any connection that reaches this rule will <em>immediately</em> get an <code>icmp-port-unreachable</code> response from your server, killing the connection.</p>
<p>An alternative action is <code>DROP</code>. In that case, your server will silently drop the connection without a response – the client will wait until it times out.</p>
<p><code>REJECT</code> provides faster feedback. But at other times <code>DROP</code> may be preferable. If someone is not where they should be – do you need to bother to send them a response at all?</p>
<h1 id="heading-persistence">Persistence</h1>
<p><code>iptables</code> out of the box does not have persistence built in. Your rules will stay in place until a reboot, but once you reboot your machine the rules will be wiped.</p>
<p>Persistence is managed outside of <code>iptables</code> themselves using external utilities.</p>
<h2 id="heading-iptables-persistent">iptables-persistent</h2>
<p>For the simple setup above, you could make your rules persistent by installing this package (Debian/Ubuntu):</p>
<pre><code class="lang-sh">sudo apt-get install iptables-persistent
</code></pre>
<p>This will:</p>
<ol>
<li><p>give you two new tools: <code>iptables-save</code> and <code>iptables-restore</code>.</p>
</li>
<li><p>Save your current config to a file such as <code>/etc/iptables/rules.v4</code></p>
</li>
<li><p>Add an <code>init.d</code> script <code>/etc/init.d/iptables-persistent</code> (or <code>/etc/init.d/netfilter-persistent</code>) to restore the saved rules on boot.</p>
</li>
</ol>
<h2 id="heading-iptables-persistent-manual-operations">iptables-persistent – Manual operations</h2>
<p>You can trigger them manually these by calling:</p>
<pre><code class="lang-shell">sudo iptables-save &gt;/etc/iptables/rules.v4
sudo iptables-restore &lt;/etc/iptables/rules.v4
</code></pre>
<p>This creates a human-readable and editable file that you can tweak if needed.</p>
<p>But do note, that <code>iptables-save</code> and <code>iptables-restore</code> are very crude tools for basic use cases. Changes to rules are not automatically reflected in the persisted rules. You will need to manage the lifecycle yourself, by calling <code>sudo iptables-save &gt;/etc/iptables/rules.v4</code> after each subsequent change.</p>
<h1 id="heading-logging">Logging</h1>
<p>Logging for <code>iptables</code> is not enabled out of the box. If you do need to enable logging for debugging or auditing purposes, make sure you consider how much you want to log. Here is one technique that you can try:</p>
<ol>
<li><p>Add a new Chain called <code>LOGGING</code></p>
</li>
<li><p>Jump to this chain from wherever you want this log to come. For example to log all INPUT, add at an index 1 (<code>-I INPUT 1</code>). Or if you want to log only under certain conditions insert it at a more appropriate place in the ordered list of rules.</p>
</li>
<li><p>Log the message. If you're enabling this in prod. It is recommended that you do some throttling here, you don't want a DoS to flood your log and hence your disk space.</p>
</li>
<li><p><code>RETURN</code> out of the <code>LOGGING</code> table</p>
</li>
</ol>
<p>This can be accomplished using the example below. It will issue a log for all incoming traffic other than the traffic that matches the first rule (port <code>8080</code> in our previous example).</p>
<pre><code class="lang-sh"><span class="hljs-comment"># 1.</span>
sudo iptables -N LOGGING
<span class="hljs-comment"># 2.</span>
sudo iptables -I INPUT 2 -j LOGGING
<span class="hljs-comment"># 3.</span>
<span class="hljs-comment"># -m limit --limit 10/s will throttle at 10 messages per second. Good for debugging purposes.</span>
sudo iptables -A LOGGING -m <span class="hljs-built_in">limit</span> --<span class="hljs-built_in">limit</span> 5/s -j LOG --log-prefix <span class="hljs-string">"iptables-input: "</span> --log-level info
<span class="hljs-comment"># 4.</span>
sudo iptables -A LOGGING -j RETURN
</code></pre>
<p>The logs will go to your default system log (e.g. <code>/var/log/messages</code> on Debian). You can configure this behaviour using your logging utility such as <code>rsyslogd</code>.</p>
<p><strong><em>CAUTION</em></strong> because logging connections on a real production server can fill up your disk space quite fast, look into limiting what you log and into log rotation (<code>logrotate</code> for your system logs).</p>
<h1 id="heading-clean-up">Clean up</h1>
<p>If you are keeping the above host around, you might want to shut off the test web servers and clear the <code>iptables</code>.</p>
<pre><code class="lang-sh"><span class="hljs-comment"># One-liner to kill processes with the words 'http.server' </span>
ps x | grep http.server | awk <span class="hljs-string">'{print $1}'</span> | head -n -1 | xargs <span class="hljs-built_in">kill</span>
</code></pre>
<p>If you want to clean up <code>iptables</code>, you can try the <code>-D &lt;chain&gt; &lt;position&gt;</code> syntax to delete rules one by one (e.g. <code>iptables -D INPUT 1</code> to delete first rule in <code>INPUT</code>). Or reboot the machine (if you haven't set up a restore on boot using <code>iptables-restore</code>).</p>
<h1 id="heading-brief-word-on-docker">Brief word on Docker</h1>
<p>The Docker daemon has a built-in limited understanding of <code>iptables</code> (and the higher level wrapper around it that you may be using –<code>firewalld</code>). The daemon sets the networking up somewhat differently for host and bridge types of Docker networking.</p>
<p>By default, when you bind to a host port, the Docker daemon will add rules to your <code>iptables</code> to allow <em>all</em> incoming traffic for any ports that you listen to for host networking. This is where you need to be careful with your <code>iptables</code> setup and its persistence.</p>
<p>You can read more on what Docker does in their documentation on <a target="_blank" href="https://docs.docker.com/network/iptables/">"Docker and iptables"</a>. Play around with it and see what it does. You will see that it creates new chains that get jumped to from the standard <code>FORWARD</code> chain.</p>
<h2 id="heading-persistence-with-docker">Persistence with Docker</h2>
<p>If you're going to modify the rules and you want to make use of persistence, make sure you set the JSON value <code>"tables": false</code> in <code>/etc/docker/daemon.json</code> and restart the Docker daemon. Otherwise, the daemon can override or conflict with the changes that you make in <code>iptables</code> yourself.</p>
<h1 id="heading-ipv6">IPv6</h1>
<p>This article only touched on IPv4. <code>iptables</code> does not apply to IPv6 traffic, but its companion tool <code>ip6tables</code> with the same CLI interface will let you define IPv6. Similarly, you can use <code>ip6tables-restore</code> and <code>ip6tables-save</code>.</p>
<h1 id="heading-further-reading">Further Reading</h1>
<ul>
<li><p><a target="_blank" href="https://linux.die.net/man/8/iptables">manpage</a></p>
</li>
<li><p><a target="_blank" href="https://www.rigacci.org/wiki/lib/exe/fetch.php/doc/appunti/linux/sa/iptables/conntrack.html">conntrack advanced usage</a>.</p>
</li>
<li><p><a target="_blank" href="https://linuxhandbook.com/iptables-vs-nftables/">iptables vs nftables</a></p>
</li>
<li><p><a target="_blank" href="https://netfilter.org/">netfilter.org</a></p>
</li>
</ul>
]]></content:encoded></item></channel></rss>