<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>devops &amp;mdash; StealthyCoder</title>
    <link>https://stealthycoder.writeas.com/tag:devops</link>
    <description>Making code ninjas out of everyone</description>
    <pubDate>Sat, 13 Jun 2026 14:06:27 +0000</pubDate>
    <item>
      <title>What a fantastic ride</title>
      <link>https://stealthycoder.writeas.com/what-a-fantastic-ride?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[I was put in charge to write some extra tests in our framework covering our Docker registry endpoints. We created a framework around Locust. !--more-- Naturally, I first started learning the framework and it is pretty nice to use. You create simple classes that house the flow of the requests you want to execute and you call them one by one, stating what should be the success and what the failure. &#xA;&#xA;The goal was to prove that our Python code was horrible and needed to be switched to Golang implementation ASAP. &#xA;&#xA;Rough start&#xA;&#xA;I could not even start our Docker container because some Werkzeug, Flask and Locust combo made it all not work anymore. So I first had to untangle that mess. It turned out that some older code of Flask used a specific call to a function that does not exist anymore at the provided location. &#xA;&#xA;  For all who are interested, the actual error is: cannot import name &#39;BaseResponse&#39; from &#39;werkzeug.wrappers&#39;. &#xA;&#xA;After that initial rough start I started out by mapping out how Docker actually works. What happens when you do docker pull or docker login for example. Turns out they are all just HTTP calls to a REST API backend. That returns some data and with that data we continue onward to more calls until all the data has been gotten for docker to actually create the containers and/or images. &#xA;&#xA;Docker API&#xA;&#xA;I wrote the simple Python PoC code for the DockerAPI client. In principle I can use that code now to get any image I want, but I do not use that. So I included that whole code into our Locust framework to make sure the test was always set up correctly, and that subsequent images were deleted. &#xA;&#xA;I ran into the second problem. Images cannot be removed from a Docker registry by default. You have to enable that feature. So when I started talking to our devs, they said just forget about it. Do the setup code once, so that the image exists that is needed in a shared test repository and continue onward.&#xA;&#xA;So I scrapped the entire code out of Locust and began again anew.&#xA;&#xA;Concurrent issues&#xA;&#xA;Next up came the problem that I wanted to only get credentials once, and share those credentials amongst the distributed workers. There were several hosts that each run multiple workers as separate processes. I wanted on each of those hosts, that one call got made by the worker process to get a nice token and share that token in memory with the rest. In comes SharedMemory by Python. I got it to finally work after fixing all my concurrent race condition failures, where there was no synchronised flag to make sure everybody waited on each other. &#xA;&#xA;After all that code, the rest of the devs were that is cool but we do not need it. Just call the login at each start of the flow, it will create credentials and if there are already credentials it will return them. So again rip out the code written so far and start anew.&#xA;&#xA;Finally on my way&#xA;&#xA;Started again with the new flow and now I got a nice test up and running. The data returned was a bit baffling and showed our Python code was not the bottleneck as previously thought, hoped for. It was our Nginx reverse proxy setup. Split out the nginx pods unto their own and updated the config to handle things a bit better and give more threads and workers basically. &#xA;&#xA;Okay after fixing the nginx pods, then ran the tests again and it turned out the Docker registry itself was a bottleneck. It just could not cope in terms of memory usage and freeing up stuff. We use Redis as our cache layer and Google Cloud Storage (GCS) as our bucket to actually store the data retrieved by Docker registry. &#xA;&#xA;Breathing room&#xA;&#xA;We had so much services jammed together in one pod it was crazy. Basically one pod ran the following services:&#xA;Nginx&#xA;Redis&#xA;Docker registry&#xA;Flask app&#xA;&#xA;Then there was no control of what pod ran what services, so it could be that one pod ran 2 nginx + redis + docker registry + flask, whilst another ran only docker registry + flask. So back to basics, get one service per pod and split off the docker registry unto it&#39;s own node. Now we have the following setup:&#xA;&#xA;Nodepool A:&#xA;   Three nodes&#xA;        running one pod each of Nginx&#xA;        running one pod each of Flask&#xA;        running one pod total of Redis&#xA;Nodepool B:&#xA;   Three nodes&#xA;        running one pod each of Docker Registry&#xA;&#xA;Now that that was cleared up, the next bottleneck seemed to be Redis? So I turned to Redis and it&#39;s config and found out we actually were not using the staging Redis but the production Redis ?!?!?!&#xA;&#xA;I quickly changed that config and made it so there was one node running a dedicated Redis. So the full situation becomes:&#xA;&#xA;Nodepool A:&#xA;   Three nodes&#xA;        running one pod each of Nginx&#xA;        running one pod each of Flask&#xA;Nodepool B:&#xA;   Three nodes&#xA;        running one pod each of Docker Registry&#xA;Nodepool C:&#xA;   One node&#xA;        running one pod total of Redis&#xA;&#xA;Okay, now can we finally move onward to find out that the Python code itself is so slow?&#xA;&#xA;gunicorn&#xA;&#xA;Well not so fast. Turns out that gunicorn was behaving badly and might do with some optimisation. gunicorn uses different worker classes and if we do not feed it the right ones with the right parameters it might actually be blocking. The reason I started looking down this rabbit hole was because of the gunicorn logs stating they ran out of workers. &#xA;&#xA;After much experimenting on what parameters work best, turns out the best one that worked for us was the following:&#xA;&#xA;CONCURRENCYSETTING=$(python3 -c &#39;import multiprocessing as mp; print(mp.cpucount() * 2)&#39;)&#xA;exec /usr/local/bin/gunicorn -n internalauthsecret -w${CONCURRENCYSETTING} -k gevent --worker-connections=1000 -b 0.0.0.0:8000 internalauthsecret:app -t 180&#xA;Meaning use the gevent type worker class, with 1000 worker connections. Also use a total amount of workers to twice the amount of cores available to us in whatever host we are running as. This also meant it is dynamic to the point where if we would ever upgrade the hardware of the node underlying the pod it will grow with it automatically without us having to make sure we also update the amount of workers. &#xA;&#xA;Conclusion&#xA;&#xA;After fixing all the infrastructure setup of correctly allocating memory and CPU to each of the services, coupled with separating them out to make sure each of them gets the appropriate amount needed. Making sure our nginx was configured correctly. Followed by actually configuring the services in staging correctly to point at services in staging rather than production, followed by configuring the gunicorn service and fine-tuning it, there was still a slight bottleneck. &#xA;&#xA;Yeey, finally Python code is slow and dumb and move on to Golang. Hold on, let us first see what is being the bottleneck. I made some call graphs using the following module https://github.com/daneads/pycallgraph2. It showed that the bottleneck was partly in our shared library code that handled authentication and also the way we were determining when to call that particular function. Finally the culprit has been located.&#xA;&#xA;To fix the shared library code was easy, just improved the for loops and small optimizations in terms of what to store so we do not do a constant looking up of the same values. Cache more in Redis, then also use a Redis connection pool rather than starting up a new connection every time for each query. &#xA;&#xA;To fix the problem of knowing when to call the function in the shared code was a literal one if else statement added to the previously declaring of the variable logic. It was a code fix of 44 characters that resulted in an improvement of the total time spent. The longest before this fix was 465ms on the shared library code path. After fixing both it was only around 60ms. So instead of the code being able to handle roughly 2 per second we could now handle roughly 15 per second per worker per worker_connection. &#xA;&#xA;After that roller-coaster of a ride, I made sure we could handle millions of requests coming in rather than just couple of hundred. The next optimisations lie in Network I/O and other factors. Even if we would move towards Golang implementation it might gain us 1ms max in terms of code maybe, that is even highly optimistic and probably not even realistic. The rest lies in the fact that we have a nginx going to a docker registry talking to another service running somewhere else again on the network that talks to Redis. Those round trip times are starting to add up.&#xA;&#xA;However that is for another time. Right now we got enough to make sure we can get through the next years of running our service. If we need more, just scale the entire setup to include more nodes, until the bottleneck is network throughput/bandwith. Then we will revisit this. &#xA;&#xA;#100DaysToOffload #DevOps #python ]]&gt;</description>
      <content:encoded><![CDATA[<p>I was put in charge to write some extra tests in our framework covering our Docker registry endpoints. We created a framework around <a href="https://locust.io/" rel="nofollow">Locust</a>.  Naturally, I first started learning the framework and it is pretty nice to use. You create simple classes that house the flow of the requests you want to execute and you call them one by one, stating what should be the success and what the failure.</p>

<p>The goal was to prove that our Python code was horrible and needed to be switched to Golang implementation ASAP.</p>

<h2 id="rough-start" id="rough-start">Rough start</h2>

<p>I could not even start our Docker container because some Werkzeug, Flask and Locust combo made it all not work anymore. So I first had to untangle that mess. It turned out that some older code of Flask used a specific call to a function that does not exist anymore at the provided location.</p>

<blockquote><p>For all who are interested, the actual error is: <code>cannot import name &#39;BaseResponse&#39; from &#39;werkzeug.wrappers&#39;</code>.</p></blockquote>

<p>After that initial rough start I started out by mapping out how Docker actually works. What happens when you do <code>docker pull</code> or <code>docker login</code> for example. Turns out they are all just HTTP calls to a REST API backend. That returns some data and with that data we continue onward to more calls until all the data has been gotten for <code>docker</code> to actually create the containers and/or images.</p>

<h2 id="docker-api" id="docker-api">Docker API</h2>

<p>I wrote the simple Python PoC code for the DockerAPI client. In principle I can use that code now to get any image I want, but I do not use that. So I included that whole code into our Locust framework to make sure the test was always set up correctly, and that subsequent images were deleted.</p>

<p>I ran into the second problem. Images cannot be removed from a Docker registry by default. You have to enable that feature. So when I started talking to our devs, they said just forget about it. Do the setup code once, so that the image exists that is needed in a shared test repository and continue onward.</p>

<p>So I scrapped the entire code out of Locust and began again anew.</p>

<h2 id="concurrent-issues" id="concurrent-issues">Concurrent issues</h2>

<p>Next up came the problem that I wanted to only get credentials once, and share those credentials amongst the distributed workers. There were several hosts that each run multiple workers as separate processes. I wanted on each of those hosts, that one call got made by the worker process to get a nice token and share that token in memory with the rest. In comes <a href="https://docs.python.org/3/library/multiprocessing.shared_memory.html" rel="nofollow">SharedMemory</a> by Python. I got it to finally work after fixing all my concurrent race condition failures, where there was no synchronised flag to make sure everybody waited on each other.</p>

<p>After all that code, the rest of the devs were that is cool but we do not need it. Just call the login at each start of the flow, it will create credentials and if there are already credentials it will return them. So again rip out the code written so far and start anew.</p>

<h2 id="finally-on-my-way" id="finally-on-my-way">Finally on my way</h2>

<p>Started again with the new flow and now I got a nice test up and running. The data returned was a bit baffling and showed our Python code was not the bottleneck as previously thought, hoped for. It was our Nginx reverse proxy setup. Split out the nginx pods unto their own and updated the config to handle things a bit better and give more threads and workers basically.</p>

<p>Okay after fixing the nginx pods, then ran the tests again and it turned out the Docker registry itself was a bottleneck. It just could not cope in terms of memory usage and freeing up stuff. We use Redis as our cache layer and Google Cloud Storage (GCS) as our bucket to actually store the data retrieved by Docker registry.</p>

<h2 id="breathing-room" id="breathing-room">Breathing room</h2>

<p>We had so much services jammed together in one pod it was crazy. Basically one pod ran the following services:
– Nginx
– Redis
– Docker registry
– Flask app</p>

<p>Then there was no control of what pod ran what services, so it could be that one pod ran 2 nginx + redis + docker registry + flask, whilst another ran only docker registry + flask. So back to basics, get one service per pod and split off the docker registry unto it&#39;s own node. Now we have the following setup:</p>
<ul><li>Nodepool A:
<ul><li>Three nodes
<ul><li>running one pod each of Nginx</li>
<li>running one pod each of Flask</li>
<li>running one pod total of Redis</li></ul></li></ul></li>
<li>Nodepool B:
<ul><li>Three nodes
<ul><li>running one pod each of Docker Registry</li></ul></li></ul></li></ul>

<p>Now that that was cleared up, the next bottleneck seemed to be Redis? So I turned to Redis and it&#39;s config and found out we actually were not using the staging Redis but the <strong>production</strong> Redis ?!?!?!</p>

<p>I quickly changed that config and made it so there was one node running a dedicated Redis. So the full situation becomes:</p>
<ul><li>Nodepool A:
<ul><li>Three nodes
<ul><li>running one pod each of Nginx</li>
<li>running one pod each of Flask</li></ul></li></ul></li>
<li>Nodepool B:
<ul><li>Three nodes
<ul><li>running one pod each of Docker Registry</li></ul></li></ul></li>
<li>Nodepool C:
<ul><li>One node
<ul><li>running one pod total of Redis</li></ul></li></ul></li></ul>

<p>Okay, now can we finally move onward to find out that the Python code itself is so slow?</p>

<h2 id="gunicorn" id="gunicorn">gunicorn</h2>

<p>Well not so fast. Turns out that <code>gunicorn</code> was behaving badly and might do with some optimisation. <code>gunicorn</code> uses different worker classes and if we do not feed it the right ones with the right parameters it might actually be blocking. The reason I started looking down this rabbit hole was because of the <code>gunicorn</code> logs stating they ran out of workers.</p>

<p>After much experimenting on what parameters work best, turns out the best one that worked for us was the following:</p>

<pre><code class="language-bash">CONCURRENCY_SETTING=$(python3 -c &#39;import multiprocessing as mp; print(mp.cpu_count() * 2)&#39;)
exec /usr/local/bin/gunicorn -n internal_auth_secret -w${CONCURRENCY_SETTING} -k gevent --worker-connections=1000 -b 0.0.0.0:8000 internal_auth_secret:app -t 180
</code></pre>

<p>Meaning use the <code>gevent</code> type worker class, with 1000 worker connections. Also use a total amount of workers to twice the amount of cores available to us in whatever host we are running as. This also meant it is dynamic to the point where if we would ever upgrade the hardware of the node underlying the pod it will grow with it automatically without us having to make sure we also update the amount of workers.</p>

<h2 id="conclusion" id="conclusion">Conclusion</h2>

<p>After fixing all the infrastructure setup of correctly allocating memory and CPU to each of the services, coupled with separating them out to make sure each of them gets the appropriate amount needed. Making sure our nginx was configured correctly. Followed by actually configuring the services in staging correctly to point at services in staging rather than production, followed by configuring the <code>gunicorn</code> service and fine-tuning it, there was still a slight bottleneck.</p>

<p>Yeey, finally Python code is slow and dumb and move on to Golang. Hold on, let us first see what is being the bottleneck. I made some call graphs using the following module <a href="https://github.com/daneads/pycallgraph2" rel="nofollow">https://github.com/daneads/pycallgraph2</a>. It showed that the bottleneck was partly in our shared library code that handled authentication and also the way we were determining when to call that particular function. Finally the culprit has been located.</p>

<p>To fix the shared library code was easy, just improved the for loops and small optimizations in terms of what to store so we do not do a constant looking up of the same values. Cache more in Redis, then also use a Redis connection pool rather than starting up a new connection every time for each query.</p>

<p>To fix the problem of knowing when to call the function in the shared code was a literal one if else statement added to the previously declaring of the variable logic. It was a code fix of 44 characters that resulted in an improvement of the total time spent. The longest before this fix was 465ms on the shared library code path. After fixing both it was only around 60ms. So instead of the code being able to handle roughly 2 per second we could now handle roughly 15 per second per worker per worker_connection.</p>

<p>After that roller-coaster of a ride, I made sure we could handle millions of requests coming in rather than just couple of hundred. The next optimisations lie in Network I/O and other factors. Even if we would move towards Golang implementation it might gain us 1ms max in terms of code maybe, that is even highly optimistic and probably not even realistic. The rest lies in the fact that we have a nginx going to a docker registry talking to another service running somewhere else again on the network that talks to Redis. Those round trip times are starting to add up.</p>

<p>However that is for another time. Right now we got enough to make sure we can get through the next years of running our service. If we need more, just scale the entire setup to include more nodes, until the bottleneck is network throughput/bandwith. Then we will revisit this.</p>

<p><a href="https://stealthycoder.writeas.com/tag:100DaysToOffload" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">100DaysToOffload</span></a> <a href="https://stealthycoder.writeas.com/tag:DevOps" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">DevOps</span></a> <a href="https://stealthycoder.writeas.com/tag:python" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">python</span></a></p>
]]></content:encoded>
      <guid>https://stealthycoder.writeas.com/what-a-fantastic-ride</guid>
      <pubDate>Mon, 02 Jan 2023 21:07:24 +0000</pubDate>
    </item>
    <item>
      <title>Chameleon containers</title>
      <link>https://stealthycoder.writeas.com/chameleon-containers?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[Sometimes you need to run Docker containers in different circumstances, like on Raspberry Pi&#39;s or you have systems that are just wired differently (Alpine) and sometimes even a combination of those (Alpine on a BananaPi) and you do not always have everything laying around to reproduce it. So what do you do? !--more--&#xA;&#xA;I ran into this problem because someone was getting a new Apple MacBook with the M1 chip which is ARMv8 or aarch64 and therefore all my previously made Docker images did not work anymore for this person as they did not work for his architecture. &#xA;&#xA;Luckily Alpine and Debian also provide images for the ARMv8 architecture. Yet that meant I had to build two different images and have a multi arch manifest file inside AWS ECR. Which they support and is easy to do, but I did not want to have that plus it made it so difficult to try and reproduce things as I could build cross architecture but not run it yet. &#xA;&#xA;Get yourself QEMU&#xA;&#xA;So the following one line is all you need to make it possible to run as different architectures.&#xA;&#xA;sudo docker run --rm --privileged multiarch/qemu-user-static:register&#xA;&#xA;That will register all the necessary binaries in order to simulate the other architectures. Then for example run the following:&#xA;&#xA;docker run -it --rm multiarch/alpine:aarch64-edge /bin/ash&#xA;&#xA;Then you get an aarch64 image!.&#xA;&#xA;  Verify with uname -a and arch&#xA;&#xA;Now you can see what it is like to run on those other architectures. The binaries are all of the form qemu-ARCH-static and so there is one for MIPS, SPARC and even PowerPC. &#xA;&#xA;#devlife #devops&#xA;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>Sometimes you need to run Docker containers in different circumstances, like on Raspberry Pi&#39;s or you have systems that are just wired differently (Alpine) and sometimes even a combination of those (Alpine on a BananaPi) and you do not always have everything laying around to reproduce it. So what do you do? </p>

<p>I ran into this problem because someone was getting a new Apple MacBook with the M1 chip which is ARMv8 or <code>aarch64</code> and therefore all my previously made Docker images did not work anymore for this person as they did not work for his architecture.</p>

<p>Luckily Alpine and Debian also provide images for the ARMv8 architecture. Yet that meant I had to build two different images and have a multi arch manifest file inside AWS ECR. Which they support and is easy to do, but I did not want to have that plus it made it so difficult to try and reproduce things as I could build cross architecture but not run it yet.</p>

<h1 id="get-yourself-qemu" id="get-yourself-qemu">Get yourself QEMU</h1>

<p>So the following one line is all you need to make it possible to run as different architectures.</p>

<pre><code>sudo docker run --rm --privileged multiarch/qemu-user-static:register
</code></pre>

<p>That will register all the necessary binaries in order to simulate the other architectures. Then for example run the following:</p>

<pre><code>docker run -it --rm multiarch/alpine:aarch64-edge /bin/ash
</code></pre>

<p>Then you get an <code>aarch64</code> image!.</p>

<blockquote><p>Verify with <code>uname -a</code> and <code>arch</code></p></blockquote>

<p>Now you can see what it is like to run on those other architectures. The binaries are all of the form <code>qemu-&lt;ARCH&gt;-static</code> and so there is one for MIPS, SPARC and even PowerPC.</p>

<p><a href="https://stealthycoder.writeas.com/tag:devlife" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">devlife</span></a> <a href="https://stealthycoder.writeas.com/tag:devops" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">devops</span></a></p>
]]></content:encoded>
      <guid>https://stealthycoder.writeas.com/chameleon-containers</guid>
      <pubDate>Fri, 27 Aug 2021 14:59:35 +0000</pubDate>
    </item>
    <item>
      <title>Docker is fun</title>
      <link>https://stealthycoder.writeas.com/docker-is-fun?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[There was a problem on a server we did not control. It was managed by a third party and we only got a service account. Since things were down and I did not have full root access I got a bit annoyed waiting for them to respond back. &#xA;&#xA;I decided to take matters into my own hands. !--more-- So first things first, we have sudo privileges but you need to know the password. We do not know the password, everything is done with SSH keys. Secondly there is docker on the system but we can only interact with it via their homegrown tool. Hmmz, how can we exploit this? &#xA;&#xA;A special kind of binary&#xA;&#xA;There are binaries in Linux that are marked s for special, or setuid, either or. These kinds of binaries run with what is known as a different effective uid or euid. For example sudo is one of these programs.&#xA;&#xA;-rwsr-xr-x 1 root root 187K Feb 18  2021 /usr/bin/sudo&#xA;That s in the -rws means the setuid bit is set. Of course that is how sudo works, it sets the real uid to the euid and then executes whatever program needed. We can write our own software that does this without asking for passwords.&#xA;&#xA;  Take note however of the fact that the euid comes from who actually owns the binary. So you need root privileges in order to make a binary owned by root and so if you already have that then you don&#39;t need this exploit.&#xA;&#xA;Go make a binary&#xA;&#xA;So let me try my hand at writing it in Go. The filename is stars.go. &#xA;&#xA;package main&#xA;&#xA;import &#34;syscall&#34;&#xA;&#xA;func main() {&#xA;  syscall.Setuid(syscall.Geteuid())&#xA;  syscall.Exec(&#34;/bin/sh&#34;, []string{}, []string{})&#xA;}&#xA;&#xA;That is all it takes. Really, that is it. So we build the binary with go build stars.go and then the following on a machine you control to test it out:&#xA;&#xA;sudo chown root:root stars&#xA;sudo chmod u+s stars&#xA;&#xA;This creates a binary that has the setuid bit and owned by root but anyone can execute and so run it and BAM we got a root shell.&#xA;&#xA;Well not quite. It did not work for me. So I ditched this way.&#xA;&#xA;  Later on I will share what failed to make it work. &#xA;&#xA;Good ol&#39; C&#xA;&#xA;C never fails. So let us write some C code that does the exact same thing.&#xA;&#xA;include stdio.h&#xA;include sys/types.h&#xA;include unistd.h&#xA;include stdlib.h&#xA;&#xA;static uidt euid;&#xA;&#xA;int main (void) {&#xA;  euid = geteuid();&#xA;  setuid(euid);&#xA;  char* args[] = {&#34;/bin/sh&#34;, NULL};&#xA;  execvp(args[0], args);&#xA;}&#xA;&#xA;As you can see it is not a lot more that is needed to be written. Compile with gcc -o stars stars.c and repeat the same steps above for making the binary the correct state. Run it and BAM we do get a root shell. This was so exciting for me. The proof of concept (PoC) worked. &#xA;&#xA;Actual operation&#xA;&#xA;Back to the server. Since there is access to docker via a docker-compose we create the following docker-compose.yml file:&#xA;&#xA;version: &#39;3.3&#39;&#xA;&#xA;services:&#xA;  generic:&#xA;      image: rockylinux/rockylinux&#xA;      command:&#xA;         &#34;/usr/bin/tail&#34;&#xA;         &#34;-f&#34;&#xA;         &#34;/dev/null&#34;&#xA;      volumes:&#xA;         .:/data&#xA;&#xA;Then run the command via their tool to get a container infinitely running and then exec into it. The reason for the RockyLinux container is because I was on CentOS host machine and I wanted to see how RockyLinux was whilst staying relatively close to the host machine. The command is needed otherwise the container will exit immediately. This command is basically trying to endlessly read /dev/null but that will never produce output. &#xA;&#xA;Now navigate to /data and install vim and gcc. I did this with yum but I realized now that actually that should have been dnf, but maybe underwater they are symlinked. &#xA;&#xA;Then after installation create the stars.c file and compile it and fix the binary. Then drop out of the docker container and run the binary on the host system and BAM, root shell, pwned, h4xx0rd and what have you not.&#xA;&#xA;Quickly edited the /etc/sudoers file to give me passwordless sudo and if you really wanted to be fancy you could edit out your wtmp and utmp entries to cover your tracks but I did not feel this was necessary. &#xA;&#xA;Go make a binary, again&#xA;&#xA;So I wanted to get it to work with Go though as I did not understand why it failed. I looked at the official docs and saw it gave back an error object. I thought let me log that out and see what it contains. It contained something along the lines of &#34;operation not permitted&#34;. So a quick search on that with Setuid call and I got the result that Go version 1.15 had a bug that made it not work but Go 1.16 and up had it fixed. This commit has the exact reason.&#xA;&#xA;So I installed Go 1.17 and recompiled and reran and BAM, I got a shell again. &#xA;&#xA;The advantage of making a Golang exploit is you automatically get a statically linked binary that could just be dropped in as long as they ran the general same version of the C library, for example GNU libc, and have the same architecture (both being x8664 or aarch64  for example) . &#xA;&#xA;So if you wanted to target Alpine (musl instead of glibc) running on Raspberry  Pi 4 (aarch64 instead of x86) then you have to cross compile or use some clever tricks I will write in the next post of running docker as a different architecture using QEMU. &#xA;&#xA;Conclusion&#xA;&#xA;This was a fun and simple way of getting around the no root thing. Just remember that Docker shares the kernel with the host and therefore you can easily bypass many restrictions you try to put on your system. For instance making /etc/sudoers read-only (I have root, I can do what I want...) or never sharing passwords only SSH keys. &#xA;&#xA;How to combat this? Well you could use rootless docker that does not do user mapping with the containers. If you do not map users 1-1 with the host you can never actually be root on the host machine and therefore your root user in the container can not create these special binaries on the machine itself. &#xA;&#xA;#devlife #devops #devsecops #secops]]&gt;</description>
      <content:encoded><![CDATA[<p>There was a problem on a server we did not control. It was managed by a third party and we only got a service account. Since things were down and I did not have full root access I got a bit annoyed waiting for them to respond back.</p>

<p>I decided to take matters into my own hands.  So first things first, we have <code>sudo</code> privileges but you need to know the password. We do not know the password, everything is done with SSH keys. Secondly there is <code>docker</code> on the system but we can only interact with it via their homegrown tool. Hmmz, how can we exploit this?</p>

<h1 id="a-special-kind-of-binary" id="a-special-kind-of-binary">A special kind of binary</h1>

<p>There are binaries in Linux that are marked s for special, or <code>setuid</code>, either or. These kinds of binaries run with what is known as a different <em>effective</em> uid or <code>euid</code>. For example <code>sudo</code> is one of these programs.</p>

<pre><code>-rwsr-xr-x 1 root root 187K Feb 18  2021 /usr/bin/sudo
</code></pre>

<p>That s in the <code>-rws</code> means the setuid bit is set. Of course that is how sudo works, it sets the <em>real</em> uid to the <code>euid</code> and then executes whatever program needed. We can write our own software that does this without asking for passwords.</p>

<blockquote><p>Take note however of the fact that the <code>euid</code> comes from who actually owns the binary. So you need root privileges in order to make a binary owned by root and so if you already have that then you don&#39;t need this exploit.</p></blockquote>

<h1 id="go-make-a-binary" id="go-make-a-binary">Go make a binary</h1>

<p>So let me try my hand at writing it in Go. The filename is <code>stars.go</code>.</p>

<pre><code class="language-go">package main

import &#34;syscall&#34;


func main() {
  syscall.Setuid(syscall.Geteuid())
  syscall.Exec(&#34;/bin/sh&#34;, []string{}, []string{})
}

</code></pre>

<p>That is all it takes. Really, that is it. So we build the binary with <code>go build stars.go</code> and then the following on a machine you control to test it out:</p>

<pre><code class="language-bash">sudo chown root:root stars
sudo chmod u+s stars
</code></pre>

<p>This creates a binary that has the <code>setuid</code> bit and owned by root but anyone can execute and so run it and BAM we got a root shell.</p>

<p>Well not quite. It did not work for me. So I ditched this way.</p>

<blockquote><p>Later on I will share what failed to make it work.</p></blockquote>

<h1 id="good-ol-c" id="good-ol-c">Good ol&#39; C</h1>

<p>C never fails. So let us write some C code that does the exact same thing.</p>

<pre><code class="language-C">#include &lt;stdio.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;unistd.h&gt;
#include &lt;stdlib.h&gt;

static uid_t euid;

int main (void) {
  euid = geteuid();
  setuid(euid);
  char* args[] = {&#34;/bin/sh&#34;, NULL};
  execvp(args[0], args);
}

</code></pre>

<p>As you can see it is not a lot more that is needed to be written. Compile with <code>gcc -o stars stars.c</code> and repeat the same steps above for making the binary the correct state. Run it and BAM we do get a root shell. This was so exciting for me. The proof of concept (PoC) worked.</p>

<h1 id="actual-operation" id="actual-operation">Actual operation</h1>

<p>Back to the server. Since there is access to docker via a docker-compose we create the following <code>docker-compose.yml</code> file:</p>

<pre><code class="language-YAML">version: &#39;3.3&#39;

services:
  generic:
      image: rockylinux/rockylinux
      command:
         - &#34;/usr/bin/tail&#34;
         - &#34;-f&#34;
         - &#34;/dev/null&#34;
      volumes:
         - .:/data
</code></pre>

<p>Then run the command via their tool to get a container infinitely running and then exec into it. The reason for the RockyLinux container is because I was on CentOS host machine and I wanted to see how RockyLinux was whilst staying relatively close to the host machine. The command is needed otherwise the container will exit immediately. This command is basically trying to endlessly read <code>/dev/null</code> but that will never produce output.</p>

<p>Now navigate to <code>/data</code> and install <code>vim</code> and <code>gcc</code>. I did this with <code>yum</code> but I realized now that actually that should have been <code>dnf</code>, but maybe underwater they are symlinked.</p>

<p>Then after installation create the <code>stars.c</code> file and compile it and fix the binary. Then drop out of the docker container and run the binary on the host system and BAM, root shell, pwned, h4xx0rd and what have you not.</p>

<p>Quickly edited the <code>/etc/sudoers</code> file to give me passwordless <code>sudo</code> and if you really wanted to be fancy you could edit out your <code>wtmp</code> and <code>utmp</code> entries to cover your tracks but I did not feel this was necessary.</p>

<h1 id="go-make-a-binary-again" id="go-make-a-binary-again">Go make a binary, again</h1>

<p>So I wanted to get it to work with Go though as I did not understand why it failed. I looked at the official docs and saw it gave back an error object. I thought let me log that out and see what it contains. It contained something along the lines of “operation not permitted”. So a quick search on that with <code>Setuid</code> call and I got the result that Go version 1.15 had a bug that made it not work but Go 1.16 and up had it fixed. This <a href="https://github.com/golang/go/commit/d1b1145cace8b968307f9311ff611e4bb810710c" rel="nofollow">commit</a> has the exact reason.</p>

<p>So I installed Go 1.17 and recompiled and reran and BAM, I got a shell again.</p>

<p>The advantage of making a Golang exploit is you automatically get a statically linked binary that could just be dropped in as long as they ran the general same version of the C library, for example GNU libc, and have the same architecture (both being x86_64 or aarch64  for example) .</p>

<p>So if you wanted to target Alpine (musl instead of glibc) running on Raspberry  Pi 4 (aarch64 instead of x86) then you have to cross compile or use some clever tricks I will write in the next post of running docker as a different architecture using QEMU.</p>

<h1 id="conclusion" id="conclusion">Conclusion</h1>

<p>This was a fun and simple way of getting around the no root thing. Just remember that Docker shares the kernel with the host and therefore you can easily bypass many restrictions you try to put on your system. For instance making <code>/etc/sudoers</code> read-only (I have root, I can do what I want...) or never sharing passwords only SSH keys.</p>

<p>How to combat this? Well you could use rootless docker that does not do user mapping with the containers. If you do not map users 1-1 with the host you can never actually be root on the host machine and therefore your <code>root</code> user in the container can not create these special binaries on the machine itself.</p>

<p><a href="https://stealthycoder.writeas.com/tag:devlife" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">devlife</span></a> <a href="https://stealthycoder.writeas.com/tag:devops" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">devops</span></a> <a href="https://stealthycoder.writeas.com/tag:devsecops" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">devsecops</span></a> <a href="https://stealthycoder.writeas.com/tag:secops" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">secops</span></a></p>
]]></content:encoded>
      <guid>https://stealthycoder.writeas.com/docker-is-fun</guid>
      <pubDate>Fri, 27 Aug 2021 14:45:19 +0000</pubDate>
    </item>
    <item>
      <title>PHP is bad.</title>
      <link>https://stealthycoder.writeas.com/php-is-bad?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[I just skimmed/read this article. It states that PHP is just as good as Rust/C/Java. That the opinion that it is bad is just 10 years out of date. !--more--&#xA;&#xA;5 years&#xA;&#xA;I would say even 5 years ago it was really bad. It was not secure and frameworks were lacking professionalism. To be honest I would say PHP is still bad today.&#xA;&#xA;Criticisms&#xA;&#xA;The criticisms to bad structure, SQL injections and general security related things are all batted away with one statement: use frameworks. Just use a framework and magically all things are fixed. That is a lie. The fundamental nature of PHP will make it be bad, no matter how many cool awesome things you build on top of it. &#xA;&#xA;Also frameworks themselves are also not foolproof and still will suffer from security flaws. Aside from this there are some other problems. Just search for all the CVE&#39;s of your favourite framework. They still occur across the board. &#xA;&#xA;Concurrency / parallelism&#xA;&#xA;The main problem with PHP is that it is not async in any way. It does not support threads, multiprocess or other constructs to make it concurrent or scalable in that sense. It just does not utilize the CPU efficiently. This also is combated in the original article by stating just throw more servers at it. Also the database is the bottleneck in all architectures, just scale that first then you can scale the application too. &#xA;&#xA;This is just nonsense. The database is hardly ever the real bottleneck. I made many applications, enterprise and non-enterprise, and I did not once run into the issue the database could not cope with the amount that was requested. Inefficient queries, sure, lots of request to the webserver, sure, but not that the database was struggling and the application was doing just fine. &#xA;&#xA;The fact you should not have to throw more servers at it is what is important. I worked on a simple Java application using Spring Boot and HATEOAS. This application is used by 100s of concurrent users. I only need to run one instance of this application to handle all of that. It is not even one giant instance with lots of resources, it has 2vCPU and 4Gb memory. That is all that is needed to support complex workflows for 100s of concurrent users. The database is a modest t3.small instance. &#xA;&#xA;Code itself&#xA;&#xA;Of course PHP has typehinting these days and namespacing and all good things, but it still suffers from the same problem outlined in this article subtracted with the addendum of this article. The core is trying to get better but it still is weak. I think the author of the article is referring to this ten year opinion. &#xA;&#xA;The tools you get are still slightly different from normal tools. So the things that get built with them are all slightly wonky. &#xA;&#xA;Deployments&#xA;&#xA;To deploy PHP is horrible. Same goes for Python, because it is not a compiled language. So to make artifacts and deploy consistent code is actually quite difficult. Putting them in a container does not help as you always need a webserver to serve the PHP code. Same for Python  where you need something like Gunicorn or similar to wrap around the code. Unless you wrote a server yourself. &#xA;&#xA;It also just is not fun to write PHP.&#xA;&#xA;#code #devops]]&gt;</description>
      <content:encoded><![CDATA[<p>I just skimmed/read <a href="https://getparthenon.com/blog/php-isnt-that-like-really-bad/" rel="nofollow">this</a> article. It states that PHP is just as good as Rust/C/Java. That the opinion that it is bad is just 10 years out of date. </p>

<h1 id="5-years" id="5-years">5 years</h1>

<p>I would say even 5 years ago it was really bad. It was not secure and frameworks were lacking professionalism. To be honest I would say PHP is still bad today.</p>

<h1 id="criticisms" id="criticisms">Criticisms</h1>

<p>The criticisms to bad structure, SQL injections and general security related things are all batted away with one statement: use frameworks. Just use a framework and magically all things are fixed. That is a lie. The fundamental nature of PHP will make it be bad, no matter how many cool awesome things you build on top of it.</p>

<p>Also frameworks themselves are also not foolproof and still will suffer from security flaws. Aside from this there are some other problems. Just search for all the CVE&#39;s of your favourite framework. They still occur across the board.</p>

<h2 id="concurrency-parallelism" id="concurrency-parallelism">Concurrency / parallelism</h2>

<p>The main problem with PHP is that it is not async in any way. It does not support threads, multiprocess or other constructs to make it concurrent or scalable in that sense. It just does not utilize the CPU efficiently. This also is combated in the original article by stating just throw more servers at it. Also the database is the bottleneck in all architectures, just scale that first then you can scale the application too.</p>

<p>This is just nonsense. The database is hardly ever the real bottleneck. I made many applications, enterprise and non-enterprise, and I did not once run into the issue the database could not cope with the amount that was requested. Inefficient queries, sure, lots of request to the webserver, sure, but not that the database was struggling and the application was doing just fine.</p>

<p>The fact you should not have to throw more servers at it is what is important. I worked on a simple Java application using Spring Boot and HATEOAS. This application is used by 100s of concurrent users. I only need to run one instance of this application to handle all of that. It is not even one giant instance with lots of resources, it has 2vCPU and 4Gb memory. That is all that is needed to support complex workflows for 100s of concurrent users. The database is a modest t3.small instance.</p>

<h2 id="code-itself" id="code-itself">Code itself</h2>

<p>Of course PHP has typehinting these days and namespacing and all good things, but it still suffers from the same problem outlined in <a href="https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/" rel="nofollow">this</a> article subtracted with the addendum of <a href="http://maettig.com/2020-09-16-revisiting-a-fractal-of-bad-design" rel="nofollow">this</a> article. The core is trying to get better but it still is weak. I think the author of the article is referring to this ten year opinion.</p>

<p>The tools you get are still slightly different from normal tools. So the things that get built with them are all slightly wonky.</p>

<h1 id="deployments" id="deployments">Deployments</h1>

<p>To deploy PHP is horrible. Same goes for Python, because it is not a compiled language. So to make artifacts and deploy consistent code is actually quite difficult. Putting them in a container does not help as you always need a webserver to serve the PHP code. Same for Python  where you need something like Gunicorn or similar to wrap around the code. Unless you wrote a server yourself.</p>

<p>It also just is not fun to write PHP.</p>

<p><a href="https://stealthycoder.writeas.com/tag:code" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">code</span></a> <a href="https://stealthycoder.writeas.com/tag:devops" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">devops</span></a></p>
]]></content:encoded>
      <guid>https://stealthycoder.writeas.com/php-is-bad</guid>
      <pubDate>Mon, 05 Jul 2021 09:33:22 +0000</pubDate>
    </item>
    <item>
      <title>Ultimate Dockerfile</title>
      <link>https://stealthycoder.writeas.com/ultimate-dockerfile?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[Speaking of learning the basic tools, I think I learned enough of running docker on multiple platforms that I now have a nice setup that is the perfect Docker image that fits as a very perfect boilerplate template to create all your future images with. !--more--&#xA;&#xA;One is based on Alpine, the other on Debian. Both will install basic tools like the needed SSL certificates to connect to HTTPS sites, wget, vim and sudo. The image always runs with a user instead of root, although to be honest the fact the user has access to adm and therefore can run as root makes this futile. However this image can easily be amended when used for production to remove the sudo part of things and just have a user run as a non-root user. &#xA;&#xA;The reason for the different users is so people can become them when running the docker images and so hot reloading and source code ownership does not change when mounting the folders inside the docker container. &#xA;&#xA;Also we install a standard shell, in this case the BaSH shell is chosen. This can be any arbitrary shell. The reason for this is so when people want to exec into the docker image they don&#39;t need to guess is it based on Alpine so I have to choose ash or Debian so I choose bash. Now there is always bash.&#xA;&#xA;  Tip: if you ever want a surefire way to get into a docker image without checking what the underlying system is use /bin/sh . That will always execute and you can then check what other shells are installed.&#xA;&#xA;All these templates look to do is to instill best practices and routines. &#xA;&#xA;Alpine&#xA;&#xA;FROM alpine:3.13.5&#xA;LABEL maintainer=&#34;stealthycoder@stealthycoder.com&#34;&#xA;&#xA;RUN apk update &amp;&amp; \&#xA;    apk upgrade &amp;&amp; \&#xA;    apk add ca-certificates \&#xA;    sudo \&#xA;    vim \&#xA;    bash \&#xA;    wget &amp;&amp; \&#xA;    adduser -u 1000 -D stealthy-adm &amp;&amp; \&#xA;    adduser -u 1001 -D stealthy &amp;&amp; \&#xA;    adduser -u 1002 -D stealthy-alt &amp;&amp; \&#xA;    adduser -u 501 -D stealthy-mac &amp;&amp; \&#xA;    echo &#34;%adm ALL=(ALL) NOPASSWD: ALL&#34;     /etc/sudoers &amp;&amp; \&#xA;    echo &#34;Set disablecoredump false&#34;     /etc/sudo.conf &amp;&amp; \&#xA;    addgroup stealthy-adm adm &amp;&amp; \&#xA;    addgroup stealthy adm &amp;&amp; \&#xA;    addgroup stealthy-alt adm &amp;&amp; \&#xA;    addgroup stealthy-mac adm &amp;&amp; \&#xA;    mkdir -p /srv/http &amp;&amp; \&#xA;    echo -e &#39;#!/usr/bin/env bash\n\&#xA;    sudo chown -R $(id -u):$(id -g) /srv \n\&#xA;    pushd &amp;  /dev/null .boot\n\&#xA;    shopt -s globstar \n\&#xA;    compgen -G */.sh   /dev/null &amp;&amp; \&#xA;    for f in $(ls */.sh)\n\&#xA;    do\n\&#xA;        tr -d &#39;&#39;&#34;\\015&#34;&#39;&#39;  &#34;$f&#34;  &#34;/var/lib/augmented/$f&#34;\n\&#xA;        chmod +x &#34;/var/lib/augmented/$f&#34;\n\&#xA;        bash &#34;/var/lib/augmented/$f&#34;\n\&#xA;    done\n\&#xA;    popd &amp;  /dev/null\n\&#xA;    /usr/bin/env bash -l -c &#34;$&#34; \n\&#xA;&#39;     /srv/entrypoint.sh  &amp;&amp; \&#xA;    chmod +x /srv/entrypoint.sh &amp;&amp; \&#xA;    chown stealthy:stealthy -R /srv &amp;&amp; \&#xA;    rm -r /var/cache/apk/&#xA;&#xA;ENTRYPOINT [ &#34;/srv/entrypoint.sh&#34; ]&#xA;WORKDIR /srv/http&#xA;USER stealthy:stealthy&#xA;&#xA;CMD [&#34;/bin/bash&#34;]&#xA;This template can be extended by installing more packages, and the entryfile can be extended with more logic to run on startup. There is a way to get more scripts in there with the .boot being mounted. This line:&#xA;tr -d &#39;&#39;&#34;\\015&#34;&#39;&#39;  &#34;$f&#34;  &#34;/var/lib/augmented/$f&#34;\n\&#xA;just removes the CR from CRLF endings when it is mounted in Windows, and therefore it can still run when executed in Linux. &#xA;&#xA;Debian&#xA;&#xA;FROM debian:testing-20210329-slim&#xA;LABEL maintainer=&#34;stealthycoder@stealthycoder.com&#34;&#xA;&#xA;RUN apt update &amp;&amp; \&#xA;    apt upgrade -y &amp;&amp; \&#xA;    apt install -y --no-install-recommends ca-certificates \&#xA;    sudo \&#xA;    vim \&#xA;    bash \&#xA;    wget &amp;&amp; \&#xA;    useradd -u 1000 -m -s /bin/bash stealthy-adm &amp;&amp; \&#xA;    useradd -u 1001 -m -s /bin/bash stealthy &amp;&amp; \&#xA;    useradd -u 1002 -m -s /bin/bash stealthy-alt &amp;&amp; \&#xA;    useradd -u 501 -m -s /bin/bash stealthy-mac &amp;&amp; \&#xA;    echo &#34;%adm ALL=(ALL) NOPASSWD: ALL&#34;     /etc/sudoers &amp;&amp; \&#xA;    echo &#34;Set disablecoredump false&#34;     /etc/sudo.conf &amp;&amp; \&#xA;    addgroup stealthy-adm adm &amp;&amp; \&#xA;    addgroup stealthy adm &amp;&amp; \&#xA;    addgroup stealthy-alt adm &amp;&amp; \&#xA;    addgroup stealthy-mac adm &amp;&amp; \&#xA;    mkdir -p /srv/http &amp;&amp; \&#xA;    echo &#39;#!/bin/bash\n\&#xA;    sudo chown -R $(id -u):$(id -g) /srv\n\&#xA;    pushd &amp;  /dev/null .boot\n\&#xA;    shopt -s globstar \n\&#xA;    compgen -G */.sh   /dev/null &amp;&amp; \&#xA;    for f in $(ls */.sh)\n\&#xA;    do\n\&#xA;        tr -d &#39;&#39;&#34;\\015&#34;&#39;&#39;  &#34;$f&#34;  &#34;/var/lib/augmented/$f&#34;\n\&#xA;        chmod +x &#34;/var/lib/augmented/$f&#34;\n\&#xA;        bash &#34;/var/lib/augmented/$f&#34;\n\&#xA;    done\n\&#xA;    popd &amp;  /dev/null\n\&#xA;    /bin/bash -l -c &#34;$*&#34; \n\&#xA;&#39;     /srv/entrypoint.sh  &amp;&amp; \&#xA;    chmod +x /srv/entrypoint.sh&#xA;   &#xA;ENTRYPOINT [ &#34;/srv/entrypoint.sh&#34; ]&#xA;WORKDIR /srv/http&#xA;USER stealthy:stealthy&#xA;&#xA;CMD [&#34;/bin/bash&#34;]&#xA;&#xA;This template can be extended by installing more packages, and the entryfile can be extended with more logic to run on startup. There is a way to get more scripts in there with the .boot being mounted. This line:&#xA;tr -d &#39;&#39;&#34;\\015&#34;&#39;&#39;  &#34;$f&#34;  &#34;/var/lib/augmented/$f&#34;\n\&#xA;just removes the CR from CRLF endings when it is mounted in Windows, and therefore it can still run when executed in Linux. &#xA;&#xA;devops]]&gt;</description>
      <content:encoded><![CDATA[<p>Speaking of learning the basic tools, I think I learned enough of running docker on multiple platforms that I now have a nice setup that is the perfect Docker image that fits as a very perfect boilerplate template to create all your future images with. </p>

<p>One is based on Alpine, the other on Debian. Both will install basic tools like the needed SSL certificates to connect to HTTPS sites, wget, vim and sudo. The image always runs with a user instead of root, although to be honest the fact the user has access to <code>adm</code> and therefore can run as root makes this futile. However this image can easily be amended when used for production to remove the sudo part of things and just have a user run as a non-root user.</p>

<p>The reason for the different users is so people can become them when running the docker images and so hot reloading and source code ownership does not change when mounting the folders inside the docker container.</p>

<p>Also we install a standard shell, in this case the BaSH shell is chosen. This can be any arbitrary shell. The reason for this is so when people want to <code>exec</code> into the docker image they don&#39;t need to guess is it based on Alpine so I have to choose <code>ash</code> or Debian so I choose <code>bash</code>. Now there is always <code>bash</code>.</p>

<blockquote><p>Tip: if you ever want a surefire way to get into a docker image without checking what the underlying system is use <code>/bin/sh</code> . That will always execute and you can then check what other shells are installed.</p></blockquote>

<p>All these templates look to do is to instill best practices and routines.</p>

<h1 id="alpine" id="alpine">Alpine</h1>

<pre><code class="language-dockerfile">FROM alpine:3.13.5
LABEL maintainer=&#34;stealthycoder@stealthycoder.com&#34;

RUN apk update &amp;&amp; \
    apk upgrade &amp;&amp; \
    apk add ca-certificates \
    sudo \
    vim \
    bash \
    wget &amp;&amp; \
    adduser -u 1000 -D stealthy-adm &amp;&amp; \
    adduser -u 1001 -D stealthy &amp;&amp; \
    adduser -u 1002 -D stealthy-alt &amp;&amp; \
    adduser -u 501 -D stealthy-mac &amp;&amp; \
    echo &#34;%adm ALL=(ALL) NOPASSWD: ALL&#34; &gt;&gt; /etc/sudoers &amp;&amp; \
    echo &#34;Set disable_coredump false&#34; &gt;&gt; /etc/sudo.conf &amp;&amp; \
    addgroup stealthy-adm adm &amp;&amp; \
    addgroup stealthy adm &amp;&amp; \
    addgroup stealthy-alt adm &amp;&amp; \
    addgroup stealthy-mac adm &amp;&amp; \
    mkdir -p /srv/http &amp;&amp; \
    echo -e &#39;#!/usr/bin/env bash\n\
    sudo chown -R $(id -u):$(id -g) /srv \n\
    pushd &amp;&gt;/dev/null .boot\n\
    shopt -s globstar \n\
    compgen -G **/*.sh &gt; /dev/null &amp;&amp; \
    for f in $(ls **/*.sh)\n\
    do\n\
        tr -d &#39;&#39;&#34;\\015&#34;&#39;&#39; &lt; &#34;$f&#34; &gt; &#34;/var/lib/augmented/$f&#34;\n\
        chmod +x &#34;/var/lib/augmented/$f&#34;\n\
        bash &#34;/var/lib/augmented/$f&#34;\n\
    done\n\
    popd &amp;&gt;/dev/null\n\
    /usr/bin/env bash -l -c &#34;$*&#34; \n\
&#39; &gt;&gt; /srv/entrypoint.sh  &amp;&amp; \
    chmod +x /srv/entrypoint.sh &amp;&amp; \
    chown stealthy:stealthy -R /srv &amp;&amp; \
    rm -r /var/cache/apk/*

ENTRYPOINT [ &#34;/srv/entrypoint.sh&#34; ]
WORKDIR /srv/http
USER stealthy:stealthy

CMD [&#34;/bin/bash&#34;]
</code></pre>

<p>This template can be extended by installing more packages, and the entryfile can be extended with more logic to run on startup. There is a way to get more scripts in there with the <code>.boot</code> being mounted. This line:</p>

<pre><code>tr -d &#39;&#39;&#34;\\015&#34;&#39;&#39; &lt; &#34;$f&#34; &gt; &#34;/var/lib/augmented/$f&#34;\n\
</code></pre>

<p>just removes the CR from CRLF endings when it is mounted in Windows, and therefore it can still run when executed in Linux.</p>

<h1 id="debian" id="debian">Debian</h1>

<pre><code class="language-dockerfile">FROM debian:testing-20210329-slim
LABEL maintainer=&#34;stealthycoder@stealthycoder.com&#34;

RUN apt update &amp;&amp; \
    apt upgrade -y &amp;&amp; \
    apt install -y --no-install-recommends ca-certificates \
    sudo \
    vim \
    bash \
    wget &amp;&amp; \
    useradd -u 1000 -m -s /bin/bash stealthy-adm &amp;&amp; \
    useradd -u 1001 -m -s /bin/bash stealthy &amp;&amp; \
    useradd -u 1002 -m -s /bin/bash stealthy-alt &amp;&amp; \
    useradd -u 501 -m -s /bin/bash stealthy-mac &amp;&amp; \
    echo &#34;%adm ALL=(ALL) NOPASSWD: ALL&#34; &gt;&gt; /etc/sudoers &amp;&amp; \
    echo &#34;Set disable_coredump false&#34; &gt;&gt; /etc/sudo.conf &amp;&amp; \
    addgroup stealthy-adm adm &amp;&amp; \
    addgroup stealthy adm &amp;&amp; \
    addgroup stealthy-alt adm &amp;&amp; \
    addgroup stealthy-mac adm &amp;&amp; \
    mkdir -p /srv/http &amp;&amp; \
    echo &#39;#!/bin/bash\n\
    sudo chown -R $(id -u):$(id -g) /srv\n\
    pushd &amp;&gt;/dev/null .boot\n\
    shopt -s globstar \n\
    compgen -G **/*.sh &gt; /dev/null &amp;&amp; \
    for f in $(ls **/*.sh)\n\
    do\n\
        tr -d &#39;&#39;&#34;\\015&#34;&#39;&#39; &lt; &#34;$f&#34; &gt; &#34;/var/lib/augmented/$f&#34;\n\
        chmod +x &#34;/var/lib/augmented/$f&#34;\n\
        bash &#34;/var/lib/augmented/$f&#34;\n\
    done\n\
    popd &amp;&gt;/dev/null\n\
    /bin/bash -l -c &#34;$*&#34; \n\
&#39; &gt;&gt; /srv/entrypoint.sh  &amp;&amp; \
    chmod +x /srv/entrypoint.sh
   
ENTRYPOINT [ &#34;/srv/entrypoint.sh&#34; ]
WORKDIR /srv/http
USER stealthy:stealthy

CMD [&#34;/bin/bash&#34;]
</code></pre>

<p>This template can be extended by installing more packages, and the entryfile can be extended with more logic to run on startup. There is a way to get more scripts in there with the <code>.boot</code> being mounted. This line:</p>

<pre><code>tr -d &#39;&#39;&#34;\\015&#34;&#39;&#39; &lt; &#34;$f&#34; &gt; &#34;/var/lib/augmented/$f&#34;\n\
</code></pre>

<p>just removes the CR from CRLF endings when it is mounted in Windows, and therefore it can still run when executed in Linux.</p>

<p><a href="https://stealthycoder.writeas.com/tag:devops" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">devops</span></a></p>
]]></content:encoded>
      <guid>https://stealthycoder.writeas.com/ultimate-dockerfile</guid>
      <pubDate>Wed, 02 Jun 2021 18:11:38 +0000</pubDate>
    </item>
    <item>
      <title>Specific tools for specific jobs</title>
      <link>https://stealthycoder.writeas.com/specific-tools-for-specific-jobs?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[I wrote a piece on microservices and nanoservices and how that is not the way to move forward. !--more--&#xA;&#xA;I thought it would be appropriate to also state when and how to use microservices though. &#xA;&#xA;Toolbelt&#xA;&#xA;Think of an actual toolbelt with which you do construction in the physical world. It should contain some basic tools that are essential in all jobs:&#xA; Hammer&#xA; Philips head screwdriver&#xA; Flat head screwdriver&#xA; Saw&#xA; Drill machine&#xA; Sander or sandpaper&#xA;&#xA;That is basically all you need to do most general jobs. Let us take the hammer for example. You have your basic run of the mill claw hammer. It can hammer in nails and get them out again. Yet there exist a hammer for banging out dents (planishing), a brass hammer that does not create sparks so you can safely use near flammable materials and even a rubber mallet to lay in tiles in the street. If you would present the rubber mallet you would not hammer in the nail with it, but the planishing hammer you might use for it. However it is a specific tool created for a specific job and when you are using it to hammer in nails you might feel uncomfortable using it or feel it is out of place.&#xA;&#xA;Microservices&#xA;&#xA;Microservices are just such an example of a specialist tool that should be used in specific cases. Of course having the tool in your toolbelt is nice, but take care to not overuse it. &#xA;It is unlike having either a pull saw or a push saw or one that is sawing on both pull and push and you can have a preference on either one of them, as long as they all saw the same material and have a similar size you can compare between them. A microservice is like having a hacksaw vs a circular saw. Yes they both saw but do it very differently and used in very different situations.&#xA;&#xA;I get the sense everyone is treating microservices more as a general tool in the belt rather than a special tool. Work on knowing you general tools first and then move into learning specialist tools. This way you are better equipped with knowing why the tool exist and how it can be applied more effectively.&#xA;&#xA;Imagine giving a jewelers&#39; hammer to someone who never used a hammer and say it is to be used in all cases and it would be just as effective as using a claw hammer. &#xA;&#xA;When to use them &#xA;&#xA;The trick to effectively use microservices I think lies in the fact you should be able to use them in isolation, like small monoliths basically. Take a notification service for example. If it is part of the monolith it can work fine, but you cannot use it when the monolith itself is down. That means that when the big application is down all of the functionalities are down, even ones that generally do not need to be affected by this. &#xA;&#xA;Let us move the notifications out of there and into it&#39;s own environment. It should stand to reason that notification code does not really get updated all that often as they generally tend to be the same over a long time, maybe some more platforms to use / support in the future. This also means that the notification service receives fewer updates and also is less prone to be broken due to changes in the core application. &#xA;&#xA;Now the core application goes down, but our notification service stays up and it might even now send notification to us stating the core application is down. &#xA;&#xA;Of course it can still not notify that it itself is down, but that you could monitor somewhere else which itself has the same problem and so on. &#xA;&#xA;devops]]&gt;</description>
      <content:encoded><![CDATA[<p>I wrote <a href="https://stealthycoder.writeas.com/moving-problems-around" rel="nofollow">a piece on microservices and nanoservices</a> and how that is <strong><em>not</em></strong> the way to move forward. </p>

<p>I thought it would be appropriate to also state <em>when</em> and <em>how</em> to use microservices though.</p>

<h1 id="toolbelt" id="toolbelt">Toolbelt</h1>

<p>Think of an actual toolbelt with which you do construction in the physical world. It should contain some basic tools that are essential in all jobs:
 – Hammer
 – Philips head screwdriver
 – Flat head screwdriver
 – Saw
 – Drill machine
 – Sander or sandpaper</p>

<p>That is basically all you need to do most general jobs. Let us take the hammer for example. You have your basic run of the mill claw hammer. It can hammer in nails and get them out again. Yet there exist a hammer for banging out dents (planishing), a brass hammer that does not create sparks so you can safely use near flammable materials and even a rubber mallet to lay in tiles in the street. If you would present the rubber mallet you would not hammer in the nail with it, but the planishing hammer you might use for it. However it is a specific tool created for a specific job and when you are using it to hammer in nails you might feel uncomfortable using it or feel it is out of place.</p>

<h1 id="microservices" id="microservices">Microservices</h1>

<p>Microservices are just such an example of a specialist tool that should be used in specific cases. Of course having the tool in your toolbelt is nice, but take care to not overuse it.
It is unlike having either a pull saw or a push saw or one that is sawing on both pull and push and you can have a preference on either one of them, as long as they all saw the same material and have a similar size you can compare between them. A microservice is like having a hacksaw vs a circular saw. Yes they both saw but do it very differently and used in very different situations.</p>

<p>I get the sense everyone is treating microservices more as a general tool in the belt rather than a special tool. Work on knowing you general tools first and then move into learning specialist tools. This way you are better equipped with knowing why the tool exist and how it can be applied more effectively.</p>

<p>Imagine giving a jewelers&#39; hammer to someone who never used a hammer and say it is to be used in all cases and it would be just as effective as using a claw hammer.</p>

<h1 id="when-to-use-them" id="when-to-use-them">When to use them</h1>

<p>The trick to effectively use microservices I think lies in the fact you should be able to use them in isolation, like small monoliths basically. Take a notification service for example. If it is part of the monolith it can work fine, but you cannot use it when the monolith itself is down. That means that when the big application is down all of the functionalities are down, even ones that generally do not need to be affected by this.</p>

<p>Let us move the notifications out of there and into it&#39;s own environment. It should stand to reason that notification code does not really get updated all that often as they generally tend to be the same over a long time, maybe some more platforms to use / support in the future. This also means that the notification service receives fewer updates and also is less prone to be broken due to changes in the core application.</p>

<p>Now the core application goes down, but our notification service stays up and it might even now send notification to us stating the core application is down.</p>

<p>Of course it can still not notify that it itself is down, but that you could monitor somewhere else which itself has the same problem and so on.</p>

<p><a href="https://stealthycoder.writeas.com/tag:devops" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">devops</span></a></p>
]]></content:encoded>
      <guid>https://stealthycoder.writeas.com/specific-tools-for-specific-jobs</guid>
      <pubDate>Wed, 02 Jun 2021 14:56:11 +0000</pubDate>
    </item>
    <item>
      <title>Moving problems around</title>
      <link>https://stealthycoder.writeas.com/moving-problems-around?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[I recently skimmed / read this article. It is written by someone who has 25 years of experience in the field and 18 of those at Microsoft and currently operates as a Software Architect. It is not always relevant, but in this case I found it to be so. !--more--&#xA;&#xA;I also made a satirical piece on this a while back already. I wish I was not this prognostic. &#xA;&#xA;I will start with the premise of what the articles sketches out and then the proposed solution and lastly why that is not a good move forward and how it simultaneously looks like a better solution yet is not. &#xA;&#xA;The deal with microservices&#xA;&#xA;The article states that microservices can be tough to get right. There is no clear definition on how big a microservice should be and before you know it, you are actually running multiple monoliths together. There is a lot of complexity in dealing with microservices in general, and indeed one of those is software architecture related boundaries.&#xA;&#xA;Scalability&#xA;Another one of those issues is mentioned in the form of scalability. It could be that microservices A and B depend on C, but it seems C is taking a long time all of a sudden. That might result in C scaling up to handle more load but also both A and B as a result of that. So in effect you could have one cascading into the other for scaling. &#xA;&#xA;How would this be in monoliths, well the same. One function might cause the lag, so you scale up the entire application multiple times. In a way you only moved the problem.&#xA;&#xA;Latency&#xA;&#xA;Next is also increased latency. One service calls another, that calls another that calls another. This means lots of added HTTP contexts, database calls etc. This is something to take into consideration.&#xA;&#xA;Deployments&#xA;&#xA;To release a new version can become troublesome if you happen to release a piece of code that had incompatible changes between the latest version and the previous version and spanned two microservices that are dependent on each other. What might happen is that service A will be up but the new version of B not yet and that will result in an error and so the service is stopped and killed and then new version of B is up but the older version of A is still there as healthchecks failed. &#xA;&#xA;Size boundary&#xA;&#xA;Where do you stop a microservice? Personally I think a microservice should be defined by at least one of the following:&#xA;&#xA; One job to do that is atomic in scale&#xA; All domain related functionality&#xA; One literal function&#xA;&#xA;One atomic job&#xA;&#xA;This means the job it does is maybe comprised of several smaller steps but they all are atomic related. For example reserving an item on a order of a customer and also decreasing the current stock to reflect that one is already taken. This needs to either happen or not at all. If you spread these two steps over two different microservices, it is a lot easier for data inconsistency to happen as maybe the order reservation succeeded but for some reason the inventory stock operation did not. Or the other way around, so the item is not being reserved somewhere but somehow one is gone missing.&#xA;&#xA;Domain related&#xA;&#xA;This lies somewhat in the previous example as well, but all things domain related should be tied together. For example all things related to authorization can be grouped into one, or everything to do with images. &#xA;&#xA;One literal function&#xA;&#xA;This just means it literally has one function entrypoint that might call other functions, but it is really really small. For example only ever giving back the time with the timezone you supplied as a parameter. &#xA;&#xA;In essence, microservices are difficult and complex. &#xA;&#xA;Enter nanoservices&#xA;&#xA;So the solution proposed is to use these nanoservices. They are actually the last variant of the previous size examples. So one function that does one thing. Only getting all users, or only getting a specific user. Take your run of the mill CRUD and each of the letters will be a nanoservice essentially. &#xA;&#xA;Dependencies&#xA;&#xA;In order to make this work all of the individual nanoservices have to depend on the same model that is used to represent the database. That is at least one shared dependency, so will you either get a shared lib or will you make one project that has all the functions but somehow integrate a facsimile of RPC ?&#xA;&#xA;Final solution&#xA;&#xA;The final proposed solution how to use the nanoservices, after choosing a way to deal with the dependency problem outlined above, is to have one microservice handle all those nanoservices. In essence taking the second example (domain related functionality) and then calling those nanoservices.&#xA;&#xA;This means you have to deploy a microservice, and all relevant nanoservices if there is a change to the model. Also deploy a microservice and a relevant nanoservice if you change something in the nanoservice. &#xA;&#xA;This is adding even more moving parts, making it more and more complex. Not solving one of the so called shortcomings of microservices. That it is complex and suffers from scalability issues. &#xA;&#xA;Hybrid solution&#xA;&#xA;There exists the option to have a monolith with some satellite services hovering that are microservices so that you get best of both worlds. It is meant for having the database models in one place, all the functionality pertaining to that in one central place but for example reporting is a separate microservice, or maybe the notifications is one such service. &#xA;&#xA;The nanoservices seems to mimic that. You have the monolith in the form of the microservice, and the nanoservices as the satellite services. The problem is however that you made it one abstraction layer further, meaning there is way more to take into account considering the wiring of this grandiose architecture solution.&#xA;&#xA;In this scenario you could have a monolith calling a microservice calling a nanoservice. That is introducing a lot of latency, communications, network traffic and so on. &#xA;&#xA;So all in all I think the proposed solution by an industry veteran is making things worse in general and not better. &#xA;&#xA;devops]]&gt;</description>
      <content:encoded><![CDATA[<p>I recently skimmed / read this <a href="https://auth0.com/blog/implementing-nanoservices-in-aspnet-core/" rel="nofollow">article</a>. It is written by someone who has 25 years of experience in the field and 18 of those at Microsoft and currently operates as a Software Architect. It is not always relevant, but in this case I found it to be so. </p>

<p>I also made a <a href="https://stealthycoder.writeas.com/size-is-important" rel="nofollow">satirical piece</a> on this a while back already. I wish I was not this prognostic.</p>

<p>I will start with the premise of what the articles sketches out and then the proposed solution and lastly why that is not a good move forward and how it simultaneously looks like a better solution yet is <em>not</em>.</p>

<h1 id="the-deal-with-microservices" id="the-deal-with-microservices">The deal with microservices</h1>

<p>The article states that microservices can be tough to get right. There is no clear definition on how big a microservice should be and before you know it, you are actually running multiple monoliths together. There is a lot of complexity in dealing with microservices in general, and indeed one of those is software architecture related boundaries.</p>

<h2 id="scalability" id="scalability">Scalability</h2>

<p>Another one of those issues is mentioned in the form of scalability. It could be that microservices A and B depend on C, but it seems C is taking a long time all of a sudden. That might result in C scaling up to handle more load but also both A and B as a result of that. So in effect you could have one cascading into the other for scaling.</p>

<p>How would this be in monoliths, well the same. One function might cause the lag, so you scale up the entire application multiple times. In a way you only moved the problem.</p>

<h2 id="latency" id="latency">Latency</h2>

<p>Next is also increased latency. One service calls another, that calls another that calls another. This means lots of added HTTP contexts, database calls etc. This is something to take into consideration.</p>

<h2 id="deployments" id="deployments">Deployments</h2>

<p>To release a new version can become troublesome if you happen to release a piece of code that had incompatible changes between the latest version and the previous version and spanned two microservices that are dependent on each other. What might happen is that service A will be up but the new version of B not yet and that will result in an error and so the service is stopped and killed and then new version of B is up but the older version of A is still there as healthchecks failed.</p>

<h2 id="size-boundary" id="size-boundary">Size boundary</h2>

<p>Where do you stop a microservice? Personally I think a microservice should be defined by at least one of the following:</p>
<ul><li>One job to do that is atomic in scale</li>
<li>All domain related functionality</li>
<li>One literal function</li></ul>

<h3 id="one-atomic-job" id="one-atomic-job">One atomic job</h3>

<p>This means the job it does is maybe comprised of several smaller steps but they all are atomic related. For example reserving an item on a order of a customer and also decreasing the current stock to reflect that one is already <em>taken</em>. This needs to either happen or not at all. If you spread these two steps over two different microservices, it is a lot easier for data inconsistency to happen as maybe the order reservation succeeded but for some reason the inventory stock operation did not. Or the other way around, so the item is not being reserved somewhere but somehow one is gone missing.</p>

<h3 id="domain-related" id="domain-related">Domain related</h3>

<p>This lies somewhat in the previous example as well, but all things domain related should be tied together. For example all things related to authorization can be grouped into one, or everything to do with images.</p>

<h3 id="one-literal-function" id="one-literal-function">One literal function</h3>

<p>This just means it literally has one function entrypoint that might call other functions, but it is really really small. For example only ever giving back the time with the timezone you supplied as a parameter.</p>

<p>In essence, microservices are difficult and complex.</p>

<h1 id="enter-nanoservices" id="enter-nanoservices">Enter nanoservices</h1>

<p>So the solution proposed is to use these nanoservices. They are actually the last variant of the previous size examples. So one function that does one thing. Only getting all users, or only getting a specific user. Take your run of the mill CRUD and each of the letters will be a nanoservice essentially.</p>

<h2 id="dependencies" id="dependencies">Dependencies</h2>

<p>In order to make this work all of the individual nanoservices have to depend on the same model that is used to represent the database. That is at least one shared dependency, so will you either get a shared lib or will you make one project that has all the functions but somehow integrate a facsimile of RPC ?</p>

<h1 id="final-solution" id="final-solution">Final solution</h1>

<p>The final proposed solution how to use the nanoservices, after choosing a way to deal with the dependency problem outlined above, is to have one microservice handle all those nanoservices. In essence taking the second example (domain related functionality) and then calling those nanoservices.</p>

<p>This means you have to deploy a microservice, and all relevant nanoservices if there is a change to the model. Also deploy a microservice and a relevant nanoservice if you change something in the nanoservice.</p>

<p>This is adding even more moving parts, making it more and more complex. Not solving one of the so called shortcomings of microservices. That it is complex and suffers from scalability issues.</p>

<h1 id="hybrid-solution" id="hybrid-solution">Hybrid solution</h1>

<p>There exists the option to have a monolith with some satellite services hovering that are microservices so that you get best of both worlds. It is meant for having the database models in one place, all the functionality pertaining to that in one central place but for example reporting is a separate microservice, or maybe the notifications is one such service.</p>

<p>The nanoservices seems to mimic that. You have the monolith in the form of the microservice, and the nanoservices as the satellite services. The problem is however that you made it one abstraction layer further, meaning there is way more to take into account considering the wiring of this grandiose architecture solution.</p>

<p>In this scenario you could have a monolith calling a microservice calling a nanoservice. That is introducing a lot of latency, communications, network traffic and so on.</p>

<p>So all in all I think the proposed solution by an industry veteran is making things worse in general and not better.</p>

<p><a href="https://stealthycoder.writeas.com/tag:devops" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">devops</span></a></p>
]]></content:encoded>
      <guid>https://stealthycoder.writeas.com/moving-problems-around</guid>
      <pubDate>Wed, 02 Jun 2021 14:33:43 +0000</pubDate>
    </item>
    <item>
      <title>No-one is immune</title>
      <link>https://stealthycoder.writeas.com/no-one-is-immune?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[I recently ran into the unsolvable issue that if you ran an npm audit on a React or Angular framework project, it would give back an error because of this CVE. Now the solution was to go to a lower dependency for one of the scripts, but that lower dependency had other high vulnerabilities and so you were in an endless cycle and could not fix it. !--more--&#xA;&#xA;Workaround&#xA;&#xA;Basically just turned off npm audit for now, or increase the audit level to critical instead of just high. &#xA;&#xA;---&#xA;Update&#xA;&#xA;The issue is resolved now because the frameworks released a minor update addressing this. &#xA;&#xA;---&#xA;&#xA;Problem&#xA;&#xA;Sometimes you just run into the fact that major frameworks cannot run fast enough because of so many nested dependencies. The tree graph of the dependencies could rival Yggdrasil. Do not be lulled into a false sense of security thinking these frameworks automatically provide the best of the best security.&#xA;&#xA;Because of all these small moving parts working together to create a complex machination it means the attack surface is quite large actually.  It also opens up the multitude of possible so-called supply chain attacks. &#xA;&#xA;That is where you do not attack the main framework but a package that is used somewhere along the chain in order to create the bigger framework. &#xA;&#xA;#devops #secops #devsecops]]&gt;</description>
      <content:encoded><![CDATA[<p>I recently ran into the unsolvable issue that if you ran an <code>npm audit</code> on a React or Angular framework project, it would give back an error because of this <a href="https://github.com/advisories/GHSA-3wcq-x3mq-6r9p" rel="nofollow">CVE</a>. Now the solution was to go to a lower dependency for one of the scripts, but that lower dependency had other high vulnerabilities and so you were in an endless cycle and could not fix it. </p>

<h2 id="workaround" id="workaround">Workaround</h2>

<p>Basically just turned off <code>npm audit</code> for now, or increase the audit level to <code>critical</code> instead of just <code>high</code>.</p>

<hr/>

<p><strong>Update</strong></p>

<p>The issue is resolved now because the frameworks released a minor update addressing this.</p>

<hr/>

<h1 id="problem" id="problem">Problem</h1>

<p>Sometimes you just run into the fact that major frameworks cannot run fast enough because of so many nested dependencies. The tree graph of the dependencies could rival Yggdrasil. Do not be lulled into a false sense of security thinking these frameworks automatically provide the best of the best security.</p>

<p>Because of all these small moving parts working together to create a complex machination it means the attack surface is quite large actually.  It also opens up the multitude of possible so-called supply chain attacks.</p>

<p>That is where you do not attack the main framework but a package that is used somewhere along the chain in order to create the bigger framework.</p>

<p><a href="https://stealthycoder.writeas.com/tag:devops" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">devops</span></a> <a href="https://stealthycoder.writeas.com/tag:secops" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">secops</span></a> <a href="https://stealthycoder.writeas.com/tag:devsecops" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">devsecops</span></a></p>
]]></content:encoded>
      <guid>https://stealthycoder.writeas.com/no-one-is-immune</guid>
      <pubDate>Wed, 02 Jun 2021 13:45:45 +0000</pubDate>
    </item>
    <item>
      <title>Docker on Windows; Thar be whales aye</title>
      <link>https://stealthycoder.writeas.com/docker-on-windows-thar-be-whales-aye?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[Recently I got the experience of using Docker on Windows Server to actually deploy a production running service. It was a fun experience for sure. I ran into three things that made it worthwhile to write down for posterity. !--more--&#xA;&#xA;Integrated Windows Auth MSSQL Server&#xA;&#xA;There is always a database involved somewhere and this time the customer is already running MSSQL Server. That is not a big deal, we can all connect to database servers using login credentials, right? Wrong. Well in this case wrong, because the IT department is refusing a separate login. Everything has to be done with named accounts and integrated Windows authorization and authentication. That means I cannot connect to it from Docker as you need a special .dll file that only executes under Windows. &#xA;&#xA;This was a slight setback. Luckily, I say luckily in this case, I was coding it in Java as Python3 does not even support integrated windows auth with their SQL drivers. I might be able to salvage something from a project used to get a sql shell when doing IT security but that is stretching it a bit far.&#xA;&#xA;Java just requires a .dll file to be present. Microsoft has a good page on it. So when that was all setup, I could connect and use it. It did mean running it outside of Docker but it is what it is. I had initially wanted it to be part of the main backend API but thankfully I chose to write a quick CLI app instead. &#xA;&#xA;Docker-compose and volumes&#xA;&#xA;In the Docker-Compose file you have the option to specify volumes. These can use the so called short syntax. If you specify a file, a path or even a named volume it all works the same under Linux. One can map a file to another file, a named volume to a file or path and a path to a path. &#xA;&#xA;In Windows the short syntax only supports path -  path mapping. Whether that is a named volume or path as the source, the target always has to be a path. The long syntax has an option to specify the type and it should be set to bind and then a file can be mapped into a file under Windows.&#xA;&#xA;General issues&#xA;&#xA;Stability&#xA;&#xA;The general issue I have is that the Docker for Windows application does not feel very stable. Once it notified for updates, and I updated it. Then it could not start anymore until a full reboot. I was scared quite considerably. &#xA;&#xA;Disk space usage&#xA;&#xA;The other issue I ran into is the fact the file system does not shrink. Now someone said:&#xA;&#xA;  Oh just clear the cache and it will all work&#xA;&#xA;Don&#39;t do this unless you are okay with deleting all your datasup\/sup. If you want to keep the data from for example your named volumes. Tough luck, it is nigh impossible to get it out as Docker runs on a VM and has weird bindings. So it is not transparent at all how to get the data out. Do I have a solution. Of course. &#xA;&#xA;You stop running all current active containers. Start a new one with mapping the named volume to a path in a new throwaway docker container. Add a mapping to a C:\ path as well. &#xA;&#xA;docker run --rm -it &#34;C:\Users\Public\Data\&#34;:/data/c namedvolume:/data/named alpine:3.13.2 ash&#xA;&#xA;Then copy all the data from named volume into the C:\ mapped one. &#xA;&#xA;Et voila. You have it.&#xA;&#xA;Linux containers or Windows containers&#xA;&#xA;Just be aware of the fact one can run either only_ Linux or Windows containers. One cannot run a mix of both. That is something to keep in mind when deciding to run Docker on Windows. &#xA;&#xA;sub\ So I did this and it was a not a happy time. I only lost test data and that did not have an impact. I was distraught for a slight moment though. /sub&#xA;&#xA;devops]]&gt;</description>
      <content:encoded><![CDATA[<p>Recently I got the experience of using Docker on Windows Server to actually deploy a production running service. It was a fun experience for sure. I ran into three things that made it worthwhile to write down for posterity. </p>

<h1 id="integrated-windows-auth-mssql-server" id="integrated-windows-auth-mssql-server">Integrated Windows Auth MSSQL Server</h1>

<p>There is always a database involved somewhere and this time the customer is already running MSSQL Server. That is not a big deal, we can all connect to database servers using login credentials, right? Wrong. Well in this case wrong, because the IT department is refusing a separate login. Everything has to be done with named accounts and integrated Windows authorization and authentication. That means I cannot connect to it from Docker as you need a special <code>.dll</code> file that only executes under Windows.</p>

<p>This was a slight setback. Luckily, I say luckily in this case, I was coding it in Java as Python3 does not even support integrated windows auth with their SQL drivers. I might be able to salvage something from a <a href="https://github.com/SecureAuthCorp/impacket/blob/master/examples/mssqlclient.py" rel="nofollow">project</a> used to get a sql shell when doing IT security but that is stretching it a bit far.</p>

<p>Java just requires a <code>.dll</code> file to be present. Microsoft has a <a href="https://docs.microsoft.com/en-us/sql/connect/jdbc/download-microsoft-jdbc-driver-for-sql-server?view=sql-server-ver15" rel="nofollow">good page</a> on it. So when that was all setup, I could connect and use it. It did mean running it outside of Docker but it is what it is. I had initially wanted it to be part of the main backend API but thankfully I chose to write a quick CLI app instead.</p>

<h1 id="docker-compose-and-volumes" id="docker-compose-and-volumes">Docker-compose and volumes</h1>

<p>In the Docker-Compose file you have the option to specify <code>volumes</code>. These can use the so called <a href="https://docs.docker.com/compose/compose-file/compose-file-v3/#short-syntax-3" rel="nofollow">short syntax</a>. If you specify a file, a path or even a named volume it all works the same under Linux. One can map a file to another file, a named volume to a file or path and a path to a path.</p>

<p>In Windows the short syntax only supports <code>path -&gt; path</code> mapping. Whether that is a named volume or path as the source, the target <strong>always</strong> has to be a path. The <a href="https://docs.docker.com/compose/compose-file/compose-file-v3/#long-syntax-3" rel="nofollow">long syntax</a> has an option to specify the <code>type</code> and it should be set to <code>bind</code> and then a file can be mapped into a file under Windows.</p>

<h1 id="general-issues" id="general-issues">General issues</h1>

<h2 id="stability" id="stability">Stability</h2>

<p>The general issue I have is that the Docker for Windows application does not feel very stable. Once it notified for updates, and I updated it. Then it could not start anymore until a full reboot. I was scared quite considerably.</p>

<h2 id="disk-space-usage" id="disk-space-usage">Disk space usage</h2>

<p>The other issue I ran into is the fact the file system does not shrink. Now someone said:</p>

<blockquote><p>Oh just clear the cache and it will all work</p></blockquote>

<p>Don&#39;t do this unless you are okay with deleting all your data<sup>*</sup>. If you want to keep the data from for example your named volumes. Tough luck, it is nigh impossible to get it out as Docker runs on a VM and has weird bindings. So it is not transparent at all how to get the data out. Do I have a solution. Of course.</p>

<p>You stop running all current active containers. Start a new one with mapping the named volume to a path in a new throwaway docker container. Add a mapping to a <code>C:\</code> path as well.</p>

<p><code>docker run --rm -it &#34;C:\Users\Public\Data\&#34;:/data/c named_volume:/data/named alpine:3.13.2 ash</code></p>

<p>Then copy all the data from named volume into the <code>C:\</code> mapped one.</p>

<p>Et voila. You have it.</p>

<h2 id="linux-containers-or-windows-containers" id="linux-containers-or-windows-containers">Linux containers <strong>or</strong> Windows containers</h2>

<p>Just be aware of the fact one can run <strong>either</strong> <strong><em>only</em></strong> Linux <strong>or</strong> Windows containers. One cannot run a mix of both. That is something to keep in mind when deciding to run Docker on Windows.</p>

<p><sub>* So I did this and it was a not a happy time. I only lost test data and that did not have an impact. I was distraught for a slight moment though. </sub></p>

<p><a href="https://stealthycoder.writeas.com/tag:devops" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">devops</span></a></p>
]]></content:encoded>
      <guid>https://stealthycoder.writeas.com/docker-on-windows-thar-be-whales-aye</guid>
      <pubDate>Sun, 14 Mar 2021 11:34:00 +0000</pubDate>
    </item>
    <item>
      <title>Spring WebFlux Hidden thoughts</title>
      <link>https://stealthycoder.writeas.com/spring-webflux-hidden-thoughts?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[So I recently started using Spring WebFlux and Project Reactor as stated in this post. One more thing I came across as I was debugging why a certain job was not running was that if you do not catch all exceptions and do not have a doOnError statement it will just keep that thought hidden from you and all to itself.  !--more--&#xA;&#xA;Then I sprinkled some log() statements around and it surfaced that the data import/export I was doing had some unfortunate data that Long.parseLong() did not agree with and threw a NumberFormatException. That however was nowhere in my code to catch and so it failed silently. &#xA;&#xA;This meant I could see the job starting and for all intents and purposes see it do nothing afterwards. &#xA;&#xA;Lesson learned, always have a doOnError on each of your flows. &#xA;&#xA;#code #devops #java]]&gt;</description>
      <content:encoded><![CDATA[<p>So I recently started using Spring WebFlux and Project Reactor as stated in <a href="https://stealthycoder.writeas.com/webflux-is-not-for-the-faint-of-heart" rel="nofollow">this</a> post. One more thing I came across as I was debugging why a certain job was not running was that if you do not catch all exceptions and do not have a <code>doOnError</code> statement it will just keep that thought hidden from you and all to itself.  </p>

<p>Then I sprinkled some <code>log()</code> statements around and it surfaced that the data import/export I was doing had some unfortunate data that <code>Long.parseLong()</code> did not agree with and threw a <code>NumberFormatException</code>. That however was nowhere in my code to catch and so it failed silently.</p>

<p>This meant I could see the job starting and for all intents and purposes see it do nothing afterwards.</p>

<p>Lesson learned, always have a <code>doOnError</code> on each of your flows.</p>

<p><a href="https://stealthycoder.writeas.com/tag:code" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">code</span></a> <a href="https://stealthycoder.writeas.com/tag:devops" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">devops</span></a> <a href="https://stealthycoder.writeas.com/tag:java" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">java</span></a></p>
]]></content:encoded>
      <guid>https://stealthycoder.writeas.com/spring-webflux-hidden-thoughts</guid>
      <pubDate>Tue, 19 Jan 2021 22:35:10 +0000</pubDate>
    </item>
  </channel>
</rss>