TECHNICAL NEWS & REPORTS

Intlight Tech Insights & Engineering Reports

HOMENews & Blog

A curated collection of deep-dive technical articles on Nuxt 4 architecture, Multi-Region Kubernetes, Zero-Trust WAF, Microservices, and Autonomous AI Agents.

KubernetesTECHNICAL NEWS & REPORTS

I Automated My Entire GitOps Security Stack. The First Thing It Blocked Was My Own Salary.

"I Automated My Entire GitOps Security Stack. The First Thing It Blocked Was My Own..."
L
Le BeltagyAuthor:
17/8/2026 6 phút
I Automated My Entire GitOps Security Stack. The First Thing It Blocked Was My Own Salary.

I Automated My Entire GitOps Security Stack. The First Thing It Blocked Was My Own Salary.

From "zero-trust hero" to "I can't deploy my own payday fix" — how I chained kube-radar, admission webhooks, and ArgoCD into an autonomous security pipeline, the regex that deemed me a threat, and why your guardrails become prison bars when you forget the escape hatch.


The Setup

It started with a single YAML file that should never have made it to production.

I was reviewing a pull request for VehicleMetrics at 10 PM on a Thursday. A junior contributor — bless their enthusiasm — had added a new ClusterRole for a debugging sidecar. It looked innocent enough:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: vehiclemetrics-debug
rules:
- apiGroups: ["*"]
  resources: ["*"]
  verbs: ["*"]

Cluster-admin in a trench coat. One kubectl apply away from "we don't know who owns this cluster anymore."

I caught it because I was looking. But what about the PRs I don't review? What about the 2 AM "hotfix" branches that bypass CODEOWNERS because someone's pager is screaming? What about the Helm chart I copy-pasted from Stack Overflow that binds a service account to pods/exec — the permission that lets anyone kubectl exec into a running pod and dump environment variables?

I manage Kubernetes at Siemens professionally. I run bare-metal clusters in my closet obsessively. And I had built NEMESIS, my purple-team tool, to attack my own infrastructure.

But I had never built something to stop the attack before it started.

So I did what any engineer with too much caffeine and a weekend would do: I automated the entire security review pipeline. No human in the loop. If the code was malicious, sloppy, or just stupid, it would never touch the cluster.

The system worked perfectly.

Then Friday at 4:47 PM happened.


Why Not Just Use Branch Protection?

You're thinking: this is a git problem, not a Kubernetes problem.

Branch protection, CODEOWNERS, required reviews — I had all of it. Here's why it's not enough:

1. YAML is a liar

That wildcard ClusterRole? It passed yamllint. It passed helm lint. It passed a human reviewer who was looking at 14 files in a 3,000-line PR. The danger wasn't in the syntax. It was in the semantics.

2. Security is boring until it's catastrophic

Nobody wants to be the reviewer who blocks a PR for three hours debating whether pods/exec is necessary. So they approve it. I know because I've done it.

3. "LGTM" is not a security control

A thumbs-up emoji doesn't enforce least privilege. A required reviewer count doesn't understand RBAC. I wanted a system that understood Kubernetes security natively, not a social protocol that assumed everyone was careful.


The Stack I Built

I took three tools I already trusted and wired them into ArgoCD's deployment pipeline:

GitHub PR
    │
    ▼
gitops-validator (GitHub App)
    ├─ kube-radar scan → RBAC wildcard / overprivilege detection
    ├─ NEMESIS static analysis → container image CVE + misconfig
    └─ kyverno-lite webhook → policy enforcement (custom rules)
    │
    ▼
ArgoCD PreSync Job
    └─ admission-controller validates the rendered manifests
    │
    ▼
Cluster (only if all gates pass)

Tool 1: kube-radar (my own Go CLI)

I wrote this when I was learning Go. It parses Kubernetes RBAC resources and scores them by risk. Wildcards = instant block. pods/exec, secrets/*, clusterroles/* without namespace restriction = flag for human review.

I containerized it and turned it into a GitHub Actions job.

# .github/workflows/gitops-security.yml
jobs:
  rbac-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: kube-radar scan
        uses: docker://ghcr.io/le-beltagy/kube-radar:v0.3
        with:
          args: scan --path ./manifests --severity critical --fail-on-critical

Tool 2: NEMESIS static analysis

The same purple-team engine that lives in my cluster as a DaemonSet has a scan mode. It reads a container image reference, pulls it into an isolated namespace, and runs Trivy + kube-bench + custom checks. If the image contains a known CVE with CVSS > 7.0, the pipeline fails.

Tool 3: kyverno-lite (custom admission webhook)

I didn't need all of Kyverno's DSL. I needed four hard rules:

  1. No container runs as root
  2. No image uses latest tag
  3. No RBAC rule has * on apiGroups, resources, AND verbs
  4. Every deployment must have resources.requests set

So I wrote a lightweight admission webhook in Go — just 400 lines — using controller-runtime. It receives AdmissionReview requests from the Kubernetes API server and returns allowed: true/false.

// webhook.go — the RBAC gatekeeper
func validateRBAC(req *admissionv1.AdmissionRequest) bool {
    var role rbacv1.ClusterRole
    json.Unmarshal(req.Object.Raw, &role)

    for _, rule := range role.Rules {
        // Rule 3: The "Deadly Asterisk"
        if slices.Contains(rule.APIGroups, "*") &&
           slices.Contains(rule.Resources, "*") &&
           slices.Contains(rule.Verbs, "*") {
            return false // ❌ DENIED
        }
    }
    return true // ✅ ALLOWED
}

I packaged it as a ValidatingWebhookConfiguration:

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: gitops-security-webhook
webhooks:
  - name: rbac.gitops.lebeltagy.io
    rules:
      - operations: ["CREATE", "UPDATE"]
        apiGroups: ["rbac.authorization.k8s.io"]
        apiVersions: ["v1"]
        resources: ["clusterroles", "roles"]
    clientConfig:
      service:
        name: gitops-webhook
        namespace: security
        path: "/validate-rbac"
    failurePolicy: Fail
    admissionReviewVersions: ["v1"]
    sideEffects: None

failurePolicy: Fail. This is the critical line. If the webhook is down, nothing gets deployed. I wanted security over availability. I would regret this later.


The ArgoCD Integration

ArgoCD has a feature most people ignore: PreSync hooks. You can run a Kubernetes Job before any sync operation. If the Job fails, the sync aborts.

I created a PreSync Job that:

  1. Renders the Helm chart
  2. Runs kube-radar against the rendered manifests
  3. Runs NEMESIS against the container images referenced in the manifests
  4. Sends a Slack notification with the scan results
apiVersion: batch/v1
kind: Job
metadata:
  name: gitops-security-gate
  annotations:
    argocd.argoproj.io/hook: PreSync
    argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
  template:
    spec:
      containers:
        - name: validator
          image: ghcr.io/le-beltagy/gitops-validator:v1.2
          env:
            - name: REPO_URL
              value: "https://github.com/le-beltagy/vehiclemetrics"
            - name: TARGET_REVISION
              value: "HEAD"
      restartPolicy: Never

If this Job exits with code 0, ArgoCD deploys. If it exits with code 1, the sync is blocked and the Application shows SyncFailed.

I tested it on a deliberately bad PR — that wildcard ClusterRole from the beginning. The pipeline caught it. The PR was blocked. The cluster stayed safe.

I felt like a god.


Friday, 4:47 PM

It was the last workday of the month. Payroll for the Dutch startup's pilot program — my first real SaaS revenue — needed to be invoiced through the VehicleMetrics billing service.

The billing service had a bug. A timezone issue: it calculated prorated usage in UTC but invoiced in CET, overcharging the customer by exactly one day. The customer noticed. I needed to ship a fix before 5 PM or the invoice would go out wrong.

I wrote the fix in 12 minutes. One line changed in a Python utility. Tests passed. I pushed, merged, and watched ArgoCD.

The PreSync Job started.

It failed.

[gitops-validator] ERROR: kube-radar detected CRITICAL violation
[gitops-validator] File: manifests/vehiclemetrics-billing-sa.yaml
[gitops-validator] Resource: ServiceAccount/vehiclemetrics-billing
[gitops-validator] Issue: Binds to ClusterRole with pods/exec permission
[gitops-validator] SEVERITY: Critical — automatic block enabled
[gitops-validator] Exit code: 1

Wait. What?

I hadn't changed anything in vehiclemetrics-billing-sa.yaml. That file had been in the repo for weeks. Why was it failing now?

I checked the git diff. The billing fix was a one-line Python change. No RBAC touched. But the PreSync Job scans the entire rendered manifest tree, not just the diff. And kube-radar had a new rule I had merged the night before — version v0.3 — that now flagged pods/exec as critical, not just a warning.

The rule change was good. The ServiceAccount was overprivileged. But I had shipped the new kube-radar rule on Thursday evening, forgotten about it, and now on Friday at 4:52 PM, my own security pipeline was treating my production manifests as a threat.

ArgoCD showed SyncFailed.

The billing fix was not deployed.


4:55 PM: The Panic Override

I had two choices:

Option A: Fix the RBAC properly — create a restricted Role with only the necessary permissions, update the ServiceAccount binding, run the tests, commit, push, wait for the pipeline.

Estimated time: 20 minutes. It was 4:55 PM. The invoice batch job ran at 5:00 PM.

Option B: Bypass the security pipeline and force the sync.

ArgoCD lets you do this. You can click "Sync" with "Prune" and "Replace" checked. You can skip the PreSync hook. I am an admin. I have the power.

I hovered over the button.

And I realized: if I bypassed my own security gate the first time it inconvenienced me, the entire system was theater. I had built an automated bouncer and was about to sneak in through the back door because I was wearing the right jacket.

So I didn't.


5:03 PM: The Real Fix (And The Bug I Actually Shipped)

I spent 8 minutes — invoice job be damned — writing a proper Role:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: vehiclemetrics-billing
  namespace: vehiclemetrics-prod
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list"]
- apiGroups: [""]
  resources: ["pods/log"]
  verbs: ["get"]

Removed pods/exec. Removed the ClusterRole binding. Applied the principle of least privilege.

Committed. Pushed. Pipeline passed. ArgoCD synced at 5:02 PM.

The invoice job? It ran at 5:03 PM. With the correct timezone fix. The customer never knew how close they came to a bad bill — or how close I came to disabling my own security stack.

But here's the part that haunts me:

The original pods/exec permission wasn't even needed. It was a copy-paste from a debugging session three weeks ago. I had left it in the manifest because "it worked" and I was too lazy to scope it down.

My security pipeline didn't create a problem. It revealed a problem I had been ignoring. The thing that blocked my salary was the thing that was already wrong.


The Aftermath: 48 Hours of Paranoia

I spent the weekend reviewing every manifest in the repo. Here's what I found:

File Issue Risk
debug-namespace/role.yaml verbs: ["*"] on ConfigMaps Any pod can read secrets mounted as ConfigMaps
monitoring/sa.yaml ServiceAccount bound to cluster-admin Prometheus can read all secrets
temp/backup-job.yaml Container runs as root Privilege escalation vector
ingress/traefik-rbac.yaml apiGroups: ["*"] on 3 resources Over-scoped for ingress needs

Four critical issues. In my own repo. That I had written or approved.

Without the automated gate, they would have stayed there until someone exploited them — or until a compliance audit found them and I had to explain why a billing service could exec into pods.


What I Changed (The No-Escape-Hatch Problem)

The system was right to block me. But the system was also dangerous because it had no emergency override that didn't require me to become a liar.

Here's the architecture now:

GitHub PR
    │
    ▼
gitops-validator (GitHub App)
    ├─ kube-radar scan → CRITICAL = block PR
    ├─ NEMESIS scan → CRITICAL = block PR
    └─ kyverno-lite webhook → policy check
    │
    ▼
ArgoCD PreSync Job
    └─ Full validation rerun
    │
    ▼
ValidatingWebhookConfiguration (cluster gate)
    │
    ▼
Cluster

The fix: I added a @security-override label. If a PR is labeled with this, the pipeline still runs, still reports every violation, but emits a warning instead of a block. The label can only be applied by a GitHub Team called security-admins, which has exactly one member: me. And every override is logged to a dedicated Slack channel and a write-once S3 bucket.

I also changed failurePolicy: Fail to failurePolicy: Ignore on the admission webhook, with a twist: if the webhook is unreachable, ArgoCD flags the Application as Unknown and pauses automated syncs. Security is enforced when healthy. Availability is preserved when degraded.

webhooks:
  - name: rbac.gitops.lebeltagy.io
    # ...
    failurePolicy: Ignore  # Don't crash deploys if webhook is down

But the real fix wasn't technical. It was procedural:

I stopped treating "works" as the standard. "Least privilege" is the standard.


The Numbers

Metric Before (manual review) After (automated gates)
RBAC violations in prod 4 known, unknown unknowns 0 (all caught in CI)
CVEs deployed to cluster ~3 per month (after-the-fact scans) 0 (blocked in PreSync)
Time to review a PR 45 min avg (human) 3 min (automated) + human for exceptions
False positive rate N/A ~5% (tunable via severity threshold)
Times I almost disabled my own salary 0 1

What I'd Do Differently

1. Don't ship new scanner rules on Thursday night

If you're changing what "critical" means, do it Monday morning when you have the week to deal with the blast radius. Not the day before you might need to deploy a hotfix.

2. Every guardrail needs a documented escape hatch

Not a secret backdoor. A visible, audited, tightly-scoped bypass. If your emergency procedure is "log in as admin and disable the thing," you don't have security. You have security theater with an intermission.

3. Scan the diff, not the world

The PreSync Job originally scanned the entire manifest tree. Now it scans only the Helm release diff: what changed, not what exists. Existing bad configs get flagged in a weekly full scan, not during hotfix deployments.

4. Your production manifests are dirtier than you think

I promise you. Go run kube-radar or Popeye or any RBAC scanner against your cluster right now. You'll find something embarrassing. The question isn't whether you have debt — it's whether you have a system that finds it before your attacker does.


Why I'm Keeping It

It's been three weeks since the Friday incident. The Dutch pilot expanded to a second customer. I haven't had a single 3 AM security scare. And when my latest contributor opened a PR with pods/exec in it, the pipeline blocked it before I even saw the notification.

I didn't have to be the bad cop. The code was.

Is automated GitOps security more work to maintain? Yes. I spend maybe an hour per week tuning rules and reviewing override logs.

Is it worth it? Last week, a penetration tester — hired by the second customer — spent two days trying to escalate privileges in the cluster. He found one over-scoped Role. It was in a staging namespace with no production data.

He wrote in his report: "The target environment exhibits unusually robust RBAC hygiene for an early-stage SaaS platform."

That sentence was worth every minute.


TL;DR — The "Don't Block Your Own Salary" Checklist

  • Run an RBAC scanner against your cluster today (kube-radar, Popeye, or rbac-audit)
  • Add a PreSync security gate to ArgoCD/Flux before your next deploy
  • Ship new scanner rules on Monday, not Thursday
  • Build an override mechanism that is audited, not secret
  • Change failurePolicy: Fail to Ignore if you don't have 24/7 webhook SREs
  • Scan the diff for deploy gates, scan the world for weekly audits
  • Remember: the pipeline that blocks you is the pipeline that saves you

Want the admission webhook code + ArgoCD PreSync manifests? Drop a comment — I'll open-source the gitops-validator repo if there's interest.

Ever been locked out of your own system by your own automation? Tell me your war story below. We can start a support group.

Tags: #kubernetes #security #gitops #devops #go #argocd #rbac #automation #platformengineering


🔗 Nguồn bài viết gốc: Le Beltagy

Discussions & Comments15

Leave a Technical Comment

Share your architectural thoughts or ask technical questions...

K
Kenji SatoPrincipal Infrastructure Architect
2 days ago

Intlight's multi-region architectural blueprints set the benchmark for ultra-resilient, enterprise-grade cloud systems in 2026.

M
Marcus VanceHead of AI Engineering @ NextWave
1 hour ago

The Agent Memory Pipeline section hits the nail on the head. Hierarchical memory indexing with Qdrant vector search is the only sustainable way to scale long-context autonomous agents.

S
Sarah JenkinsLLM Research Scientist
30 mins ago

Yes! Separating episodic memory from working context allows agents to operate indefinitely without token explosion.

P
Priya SharmaDistributed Database Architect
5 hours ago

The latency comparisons between gRPC Protobuf binary encoding and standard JSON payloads demonstrate exactly why internal services should deprecate REST for high-throughput pipelines.

H
Hannah SchmidtDevOps & CI/CD Lead
1 day ago

Can confirm: automated canary deployments with Argo Rollouts and Prometheus metrics analysis prevented several outages for our payment gateways.

E
Elena RostovaLead SRE & Platform Architect
45 mins ago

The KEDA autoscaling setup with custom Prometheus metrics is production-grade. We observed a 60% compute cost reduction after switching to event-driven pod scaling.

SPOTLIGHT & LATEST NEWS

Hot Trending Topics

View All →
I Thought I'd Lost the Plot. I Was Writing It. 🔥 HOT SPOTLIGHT
AI Agents6 min read

I Thought I'd Lost the Plot. I Was Writing It.

I Thought I'd Lost the Plot. I Was Writing It. I set out to build autonomous...

Explore
What Is the Circuit Breaker Pattern? A Practical Guide 🔥 HOT SPOTLIGHT
Microservices6 min read

What Is the Circuit Breaker Pattern? A Practical Guide

What Is the Circuit Breaker Pattern? A Practical Guide for Developers Imagine your...

Explore
I attacked my own npm package before launching it. It let the proposer approve their own writes 🔥 HOT SPOTLIGHT
Security6 min read

I attacked my own npm package before launching it. It let the proposer approve their own writes

My library exists so a human approves an LLM's UPDATE before it runs. It never checked that the approver was somebody other than the proposer — and wrote \"approved\" into the audit trail anyway.

Explore
Build an MCP Server in Go (Part 1): Designing a diagnostic-grade Kubernetes client 🔥 HOT SPOTLIGHT
Kubernetes6 min read

Build an MCP Server in Go (Part 1): Designing a diagnostic-grade Kubernetes client

This post designs the Kubernetes client. The next post wraps it as an MCP server and wires it to an...

Explore
The Write Policy Is the Hard Part: Promotion Pipelines for Agent Memory 🔥 HOT SPOTLIGHT
AI Agents6 min read

The Write Policy Is the Hard Part: Promotion Pipelines for Agent Memory

Storing agent memory is easy. Deciding what earns a permanent write, and keeping the write-path alive through RBAC and network policy, is the real work.

Explore
I Changed How I Think About AI Memory 🔥 HOT SPOTLIGHT
AI Agents6 phút

I Changed How I Think About AI Memory

I Changed How I Think About AI Memory When I first built Lean AI Memory, I focused too...

Explore
Real-Life Refactoring Example: ~3x Less Code to Read 🔥 HOT SPOTLIGHT
Microservices6 phút

Real-Life Refactoring Example: ~3x Less Code to Read

There is a popular idea that refactoring is making code shorter. It is not entirely wrong....

Explore
The Tragedy of the Clean-Handed Auditor 🔥 HOT SPOTLIGHT
Security6 phút

The Tragedy of the Clean-Handed Auditor

\"I could save them if they'd only listen...\" Hey, you. Yeah, you: the compliance or governance...

Explore
Related Articles5 articles
View all Kubernetes →
Build an MCP Server in Go (Part 1): Designing a diagnostic-grade Kubernetes client6 min read
Kubernetes8/17/2026

Build an MCP Server in Go (Part 1): Designing a diagnostic-grade Kubernetes client

This post designs the Kubernetes client. The next post wraps it as an MCP server and wires it to an...

Author: Fer RiosRead Article
Building Sluice: QoS-Aware Capacity Governance for Self-Hosted LLM Inference6 phút
Kubernetes8/17/2026

Building Sluice: QoS-Aware Capacity Governance for Self-Hosted LLM Inference

📦 Project: https://github.com/VampiricCyborg/sluice 1. The Problem: When Capacity Becomes...

Author: Madhav M SRead Article
One GPU, four ways to share it: ten scenarios, and the headline finding I had to retract6 phút
Kubernetes8/17/2026

One GPU, four ways to share it: ten scenarios, and the headline finding I had to retract

I measured every GPU sharing mode across ten scenarios, published a headline finding that duplicate models are nearly free on unified memory, then failed to replicate it and retracted it. Here is what the controlled replication showed and how the original measurement fooled me.

Author: Christopher MaherRead Article
I got tired of SSHing into 10 VMs a day, so I built a live map of my whole infrastructure6 phút
Kubernetes17/8/2026

I got tired of SSHing into 10 VMs a day, so I built a live map of my whole infrastructure

Every day at work looked the same. Something breaks, or I need to push a new image, and I'm SSHing...

Author: ByteStrixRead Article
The Backup Awakens: A Star Wars Story6 phút
Kubernetes17/8/2026

The Backup Awakens: A Star Wars Story

The Quest Begins (The \"Why\") Honestly, I used to think backups were the boring chores you...

Author: TimevoltRead Article