<?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: Manuel Engelhardt</title>
    <description>The latest articles on DEV Community by Manuel Engelhardt (@manuel_engelhardt_e793c88).</description>
    <link>https://dev.to/manuel_engelhardt_e793c88</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%2F3627720%2F1ba9ea9f-a273-47eb-a64f-b34b42fec1e8.jpg</url>
      <title>DEV Community: Manuel Engelhardt</title>
      <link>https://dev.to/manuel_engelhardt_e793c88</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/manuel_engelhardt_e793c88"/>
    <language>en</language>
    <item>
      <title>Kubernetes RBAC: Least Privilege statt cluster-admin für CI</title>
      <dc:creator>Manuel Engelhardt</dc:creator>
      <pubDate>Fri, 04 Sep 2026 08:44:09 +0000</pubDate>
      <link>https://dev.to/manuel_engelhardt_e793c88/kubernetes-rbac-least-privilege-statt-cluster-admin-fur-ci-59g4</link>
      <guid>https://dev.to/manuel_engelhardt_e793c88/kubernetes-rbac-least-privilege-statt-cluster-admin-fur-ci-59g4</guid>
      <description>&lt;p&gt;&lt;em&gt;English readers: this is a German-language post on Kubernetes RBAC least-privilege patterns — Role vs. ClusterRole, right-sizing ServiceAccount bindings for CI pipelines, and where RBAC's authority ends. Originally published at &lt;a href="https://muellermh.blog/2026/09/04/kubernetes-rbac-least-privilege-statt-cluster-admin-fur-ci/" rel="noopener noreferrer"&gt;muellermh.blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  API-Zugriff statt Workload-Verhalten
&lt;/h2&gt;

&lt;p&gt;Admission Control entscheidet, was beim Erstellen eines Pods erlaubt ist. Runtime Security erkennt verdächtiges Verhalten, während ein Container läuft. &lt;a href="https://muellermh.blog/2026/09/02/kubernetes-networkpolicies-warum-dein-cluster-offen-ist/" rel="noopener noreferrer"&gt;NetworkPolicies&lt;/a&gt; begrenzen, mit wem ein Pod im Netzwerk sprechen darf. RBAC (Role-Based Access Control) setzt eine Ebene davor an: Es regelt, wer überhaupt mit der Kubernetes-API sprechen darf und was er dort tun darf — Pods erstellen, Secrets lesen, Nodes löschen. Das ist auch die Abgrenzung zum &lt;a href="https://muellermh.blog/2026/06/12/kubernetes-secrets-warum-base64-kein-schutz-ist/" rel="noopener noreferrer"&gt;Artikel über Kubernetes-Secrets und base64&lt;/a&gt;: Dort ging es darum, dass base64 keine Verschlüsselung ist. Hier geht es darum, wer über die API überhaupt an das Secret-Objekt herankommt. Zwei verschiedene Kontrollen, zwei verschiedene Angriffsflächen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Das eigentliche Risiko: cluster-admin für ServiceAccounts und CI-Pipelines
&lt;/h2&gt;

&lt;p&gt;Eine RBAC-Fehlkonfiguration ist keine exotische Rechteausweitung, sondern eine banale Bequemlichkeitsentscheidung: Eine CI-Pipeline soll deployen, verschiedene Ressourcen in verschiedenen Namespaces anfassen, und irgendwann bindet jemand den ServiceAccount an &lt;code&gt;cluster-admin&lt;/code&gt;, um endlich Ruhe vor Rechte-Fehlern zu haben.&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;rbac.authorization.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;ClusterRoleBinding&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;ci-pipeline-admin&lt;/span&gt;
&lt;span class="na"&gt;subjects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&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;ServiceAccount&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;ci-deployer&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;ci&lt;/span&gt;
&lt;span class="na"&gt;roleRef&lt;/span&gt;&lt;span class="pi"&gt;:&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;ClusterRole&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;cluster-admin&lt;/span&gt;
  &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Das Token dieses ServiceAccounts liegt danach in jedem Pipeline-Lauf — als Klartext-Umgebungsvariable oder als gemountetes Secret. Wer dieses Token in die Hände bekommt — über eine kompromittierte Dependency, einen geleakten Log, eine falsch konfigurierte Artifact-Ablage — hat nicht Zugriff auf die Deploy-Funktion der Pipeline, sondern auf den gesamten Cluster: alle Secrets in allen Namespaces lesen, beliebige privilegierte Pods erstellen und darüber auf den Node ausbrechen, RBAC-Objekte selbst verändern. Das ist kein theoretisches Risiko, das nur in Threat-Modeling-Workshops existiert — es ist die direkte Konsequenz einer Zeile YAML, die in dieser Form leicht entsteht.&lt;/p&gt;

&lt;h2&gt;
  
  
  Role vs. ClusterRole: RBAC nach dem Least-Privilege-Prinzip zuschneiden
&lt;/h2&gt;

&lt;p&gt;Der Gegenentwurf ist Scoping auf das, was die Pipeline tatsächlich braucht. Eine &lt;code&gt;Role&lt;/code&gt; gilt nur innerhalb eines Namespace:&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;rbac.authorization.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;Role&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;deployer&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;app&lt;/span&gt;
&lt;span class="na"&gt;rules&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;apps"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deployments"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;verbs&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;get"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;watch"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;update"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;patch"&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;resources&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;pods"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;verbs&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;get"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;watch"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gebunden wird sie über eine &lt;code&gt;RoleBinding&lt;/code&gt;, ebenfalls im selben Namespace:&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;rbac.authorization.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;RoleBinding&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;ci-deployer-binding&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;app&lt;/span&gt;
&lt;span class="na"&gt;subjects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&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;ServiceAccount&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;ci-deployer&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;ci&lt;/span&gt;
&lt;span class="na"&gt;roleRef&lt;/span&gt;&lt;span class="pi"&gt;:&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;Role&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;deployer&lt;/span&gt;
  &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Damit kann der ServiceAccount &lt;code&gt;ci-deployer&lt;/code&gt; Deployments im Namespace &lt;code&gt;app&lt;/code&gt; aktualisieren — und sonst nichts. Kein Zugriff auf &lt;code&gt;billing&lt;/code&gt;, kein Zugriff auf Secrets, kein Zugriff auf Cluster-weite Ressourcen wie Nodes oder Namespaces selbst.&lt;/p&gt;

&lt;p&gt;Für genau solche Cluster-weiten Ressourcen — oder wenn dieselbe Rolle über viele Namespaces hinweg gebraucht wird — gibt es &lt;code&gt;ClusterRole&lt;/code&gt; und &lt;code&gt;ClusterRoleBinding&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.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;ClusterRole&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;pod-reader-clusterwide&lt;/span&gt;
&lt;span class="na"&gt;rules&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;resources&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;pods"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;verbs&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;get"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;watch"&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;rbac.authorization.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;ClusterRoleBinding&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;monitoring-pod-reader&lt;/span&gt;
&lt;span class="na"&gt;subjects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&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;ServiceAccount&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;monitoring&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;monitoring&lt;/span&gt;
&lt;span class="na"&gt;roleRef&lt;/span&gt;&lt;span class="pi"&gt;:&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;ClusterRole&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;pod-reader-clusterwide&lt;/span&gt;
  &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Der Unterschied liegt nicht im Namen, sondern im Scope: Ein &lt;code&gt;RoleBinding&lt;/code&gt; kann auch auf eine &lt;code&gt;ClusterRole&lt;/code&gt; verweisen (dann gilt sie nur im Namespace der Binding), ein &lt;code&gt;ClusterRoleBinding&lt;/code&gt; dagegen wirkt immer clusterweit. &lt;code&gt;cluster-admin&lt;/code&gt; ist selbst nur eine besonders weit gefasste &lt;code&gt;ClusterRole&lt;/code&gt; — gebunden über &lt;code&gt;ClusterRoleBinding&lt;/code&gt;, wirkt sie überall.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rechte messen statt schätzen
&lt;/h2&gt;

&lt;p&gt;Bevor du eine Rolle zuschneidest, lohnt sich der Blick auf das, was ein Subjekt aktuell tatsächlich darf. &lt;code&gt;kubectl auth can-i --list&lt;/code&gt; mit &lt;code&gt;--as&lt;/code&gt; simuliert einen anderen Identitätsträger, ohne dass du dessen Token brauchst:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl auth can-i &lt;span class="nt"&gt;--list&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--as&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;system:serviceaccount:ci:ci-deployer &lt;span class="nt"&gt;-n&lt;/span&gt; app

&lt;span class="c"&gt;# gezielter Einzel-Check auf dieselbe Identität&lt;/span&gt;
kubectl auth can-i delete secrets &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--as&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;system:serviceaccount:ci:ci-deployer &lt;span class="nt"&gt;-n&lt;/span&gt; app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Das beantwortet "was darf dieses Subjekt". Die umgekehrte Frage — "wer darf diese Aktion" — beantworten &lt;code&gt;kubectl auth can-i&lt;/code&gt; allein nicht, dafür aber Zusatzwerkzeuge wie &lt;code&gt;rbac-lookup&lt;/code&gt; (Fairwinds) oder &lt;code&gt;kubectl-who-can&lt;/code&gt; (Aqua Security): Beide durchsuchen alle Rollen und Bindings im Cluster und listen auf, welche Subjekte ein bestimmtes Verb auf eine bestimmte Ressource anwenden dürfen. Gerade bei gewachsenen Clustern mit vielen ServiceAccounts ist das der einzige praktikable Weg, cluster-admin-Bindings überhaupt zu finden, statt sie zu erraten.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aggregated ClusterRoles: erweiterbar ohne Rechteausweitung
&lt;/h2&gt;

&lt;p&gt;Wenn ein Controller oder ein CRD eigene Ressourcen mitbringt, die in bestehende Standardrollen wie &lt;code&gt;view&lt;/code&gt; oder &lt;code&gt;edit&lt;/code&gt; einsortiert werden sollen, muss dafür nicht die eingebaute ClusterRole editiert werden. Kubernetes' Standardrollen selbst nutzen dafür Aggregation über Label-Selektoren:&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;rbac.authorization.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;ClusterRole&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;custom-metrics-reader&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;rbac.authorization.k8s.io/aggregate-to-view&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
&lt;span class="na"&gt;rules&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;metrics.k8s.io"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pods"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;verbs&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;get"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Diese ClusterRole wird über das Label automatisch in die eingebaute &lt;code&gt;view&lt;/code&gt;-Rolle eingemischt, sobald sie existiert. Der Vorteil: Erweiterbarkeit ohne die Basis-Rolle direkt zu verändern und ohne das Risiko, bei einem Merge versehentlich mehr Rechte einzuführen als beabsichtigt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wo RBAC aufhört
&lt;/h2&gt;

&lt;p&gt;RBAC regelt, wer über die API worauf zugreifen darf — es sagt nichts darüber aus, was innerhalb eines Pods mit bereits legitim erteilten Rechten passiert. Ein Pod, dessen ServiceAccount berechtigt ist, Secrets im eigenen Namespace zu lesen, kann diese Rechte über eine Remote-Code-Execution-Schwachstelle in der Anwendung genauso nutzen wie über den vorgesehenen Code-Pfad. RBAC hat in diesem Fall exakt das durchgesetzt, was in der Rolle stand — das Problem liegt dann entweder in einer zu weit gefassten Rolle (ein Least-Privilege-Problem, das sich mit den Mitteln aus diesem Artikel beheben lässt) oder darin, dass der Angriff einen legitim erlaubten Zugriff über einen Code-Pfad ausnutzt, den keine API-Autorisierung einschränken kann. Genau hier ergänzen sich die anderen Bausteine dieser Serie: Admission Control verhindert von vornherein, dass Pods mit unnötig weiten Rechten oder Privilegien überhaupt starten, Runtime Security erkennt, wenn ein Pod sich anders verhält als erwartet. RBAC ist die Grundlage, nicht die vollständige Antwort.&lt;/p&gt;

&lt;h2&gt;
  
  
  Woran es hier weitergeht
&lt;/h2&gt;

&lt;p&gt;Least-Privilege-Reviews an RBAC-Bindings sind kein einmaliges Audit, sondern ein wiederkehrendes Thema in dieser Serie — Rollen wachsen mit jeder neuen Pipeline-Anforderung, und niemand entfernt Rechte von sich aus wieder.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>security</category>
      <category>devops</category>
    </item>
    <item>
      <title>OIDC Workload Identity Federation in CI/CD: das Ende langlebiger Cloud-Secrets</title>
      <dc:creator>Manuel Engelhardt</dc:creator>
      <pubDate>Thu, 03 Sep 2026 10:42:35 +0000</pubDate>
      <link>https://dev.to/manuel_engelhardt_e793c88/oidc-workload-identity-federation-in-cicd-das-ende-langlebiger-cloud-secrets-2c8k</link>
      <guid>https://dev.to/manuel_engelhardt_e793c88/oidc-workload-identity-federation-in-cicd-das-ende-langlebiger-cloud-secrets-2c8k</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is in German. It covers OIDC Workload Identity Federation for CI/CD pipelines (AWS, GCP, Azure): how the trust handshake works, real IAM trust-policy and workflow snippets, and the pitfalls that keep long-lived cloud secrets alive during migration. Canonical version on my blog: &lt;a href="https://muellermh.blog/2026/08/31/oidc-workload-identity-federation-in-ci-cd-das-ende-langlebiger-cloud-secrets/" rel="noopener noreferrer"&gt;muellermh.blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ein AWS Access Key in einem GitHub Actions Secret ist ein Passwort, das nie abläuft, bis jemand es manuell rotiert. Genau das macht ihn zum bevorzugten Ziel: er liegt fest an einem Ort, gilt oft über Monate, und die Rechte dahinter sind meist großzügiger zugeschnitten, als der einzelne Workflow braucht. Wird er kopiert, aus einem Log ausgelesen oder aus einem kompromittierten Dependency-Paket exfiltriert, funktioniert er so lange weiter, bis jemand den Diebstahl bemerkt und den Key widerruft. Genau dieses Muster, gespeicherte, langlebige Credentials in der Pipeline, ist der wiederkehrende Fund in Berichten zu CI/CD-Kompromittierungen.&lt;/p&gt;

&lt;p&gt;OIDC Workload Identity Federation dreht das Modell um. Statt eines gespeicherten Secrets stellt der CI-Anbieter pro Lauf ein signiertes JSON Web Token aus. Die Cloud prüft dieses Token gegen eine Trust Policy und stellt im Gegenzug ein kurzlebiges Zugriffstoken aus, das nur für die Dauer des Jobs gilt. Es gibt kein Secret mehr, das irgendwo liegt und gestohlen werden kann, weil es keins mehr gibt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wie der Trust Handshake abläuft
&lt;/h2&gt;

&lt;p&gt;GitHub Actions tritt dabei als OpenID Connect Identity Provider auf. Für jeden Job, der &lt;code&gt;id-token: write&lt;/code&gt; in seinen Permissions gesetzt hat, kann der Workflow ein JWT anfordern. Dieses Token enthält unter anderem folgende Claims:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iss: https://token.actions.githubusercontent.com
sub: repo:meine-org/mein-repo:ref:refs/heads/main
aud: sts.amazonaws.com
repository: meine-org/mein-repo
workflow: deploy.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Die Cloud Seite, egal ob AWS, GCP oder Azure, kennt den öffentlichen Schlüssel von &lt;code&gt;token.actions.githubusercontent.com&lt;/code&gt; und kann die Signatur des Tokens prüfen, ohne dass GitHub und die Cloud vorher ein gemeinsames Secret ausgetauscht haben. Danach entscheidet eine Trust Policy anhand der Claims im Token, ob und mit welcher Rolle Zugriff gewährt wird. Zwei Claims tragen hier die eigentliche Sicherheitsentscheidung: &lt;code&gt;sub&lt;/code&gt; bindet den Zugriff an ein konkretes Repository, einen Branch oder ein Environment, &lt;code&gt;aud&lt;/code&gt; stellt sicher, dass das Token wirklich für diese eine Cloud ausgestellt wurde und nicht für einen anderen Dienst umgewidmet werden kann. Das ausgestellte Zugriffstoken selbst ist danach nur für die Laufzeit des Jobs gültig, nicht für den nächsten Lauf und nicht wiederverwendbar, wenn der Job längst durch ist.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS in der Praxis: Trust Policy und Workflow
&lt;/h2&gt;

&lt;p&gt;Auf der AWS Seite richtest du einmalig einen OIDC Identity Provider für &lt;code&gt;token.actions.githubusercontent.com&lt;/code&gt; ein und hängst eine IAM Rolle mit passender Trust Policy daran:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Federated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts:AssumeRoleWithWebIdentity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"StringEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"token.actions.githubusercontent.com:aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts.amazonaws.com"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"StringLike"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"token.actions.githubusercontent.com:sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"repo:meine-org/mein-repo:ref:refs/heads/main"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Im Workflow selbst brauchst du dafür kein einziges gespeichertes Secret mehr:&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;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arn:aws:iam::123456789012:role/gh-actions-deploy&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eu-central-1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws s3 ls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Die Action fordert das JWT selbst an, tauscht es gegen ein STS Token, und exportiert die resultierenden temporären Zugangsdaten als Umgebungsvariablen für die folgenden Steps. Nach Ende des Jobs sind sie wertlos.&lt;/p&gt;

&lt;h2&gt;
  
  
  GCP und Azure kurz skizziert
&lt;/h2&gt;

&lt;p&gt;Bei Google Cloud heißt der äquivalente Mechanismus Workload Identity Federation: du legst einen Workload Identity Pool und darin einen Provider an, der GitHub als Identity Provider akzeptiert, mit einer Attribute Condition, die wieder auf &lt;code&gt;assertion.repository&lt;/code&gt; prüft. Bei Azure trägst du an einer App Registrierung eine Federated Credential ein, mit &lt;code&gt;token.actions.githubusercontent.com&lt;/code&gt; als Issuer und demselben &lt;code&gt;sub&lt;/code&gt; Muster wie bei AWS. In beiden Fällen ist die Grundidee identisch zu AWS: kein gespeichertes Secret, sondern eine Trust Beziehung, die pro Lauf neu geprüft wird.&lt;/p&gt;

&lt;h2&gt;
  
  
  Typische Fallstricke bei der Migration
&lt;/h2&gt;

&lt;p&gt;Der häufigste Fehler ist ein zu weiter &lt;code&gt;sub&lt;/code&gt; Wildcard. Eine Bedingung wie &lt;code&gt;repo:meine-org/*:*&lt;/code&gt; erlaubt jedem Repository und jedem Branch der Organisation, dieselbe Rolle zu übernehmen, das ist im Ergebnis kaum enger als ein geteilter Access Key. Die Bedingung gehört auf ein konkretes Repository und, wo möglich, auf ein Environment oder einen Branch eingeschränkt, nicht auf die ganze Organisation.&lt;/p&gt;

&lt;p&gt;Der zweite Fehler ist eine Trust Policy ohne &lt;code&gt;aud&lt;/code&gt; Prüfung. Fehlt die Bedingung auf &lt;code&gt;token.actions.githubusercontent.com:aud&lt;/code&gt;, verlässt sich die Rolle allein auf &lt;code&gt;sub&lt;/code&gt;, und ein Token, das eigentlich für einen anderen Zweck ausgestellt wurde, aber zufällig einen passenden &lt;code&gt;sub&lt;/code&gt; Claim trägt, würde ebenfalls akzeptiert. Beide Bedingungen gehören zusammen in die Policy, nicht nur eine davon.&lt;/p&gt;

&lt;p&gt;Der dritte Punkt betrifft die Migration selbst. Alte Pipelines laufen häufig noch eine Weile mit dem gespeicherten Access Key parallel zur neuen OIDC Rolle, während die Umstellung getestet wird. Genau in dieser Übergangsphase bleibt der alte Key aktiv und wird oft schlicht vergessen, sobald die neue Rolle einmal läuft. Ohne einen festen Termin, an dem der alte IAM User deaktiviert und der Key gelöscht wird, bleibt die eigentliche Angriffsfläche, die die Migration beseitigen sollte, einfach liegen.&lt;/p&gt;

&lt;p&gt;Wie du Rechte in der Pipeline generell auf das Nötige zuschneidest, unabhängig vom Auth Mechanismus, ist eine eigene Frage, die OIDC Workload Identity nicht beantwortet. Es ersetzt darin genau einen Baustein, das gespeicherte Cloud Secret, nicht die Sorgfalt bei der Rechtevergabe selbst.&lt;/p&gt;

</description>
      <category>oidc</category>
      <category>githubactions</category>
      <category>aws</category>
      <category>security</category>
    </item>
    <item>
      <title>GitSecOps: Why Compliance Only Works When Teams Can Prove What They Deliver — Not Just Promise It</title>
      <dc:creator>Manuel Engelhardt</dc:creator>
      <pubDate>Mon, 24 Nov 2025 16:05:50 +0000</pubDate>
      <link>https://dev.to/manuel_engelhardt_e793c88/gitsecops-why-compliance-only-works-when-teams-can-prove-what-they-deliver-not-just-promise-it-42hp</link>
      <guid>https://dev.to/manuel_engelhardt_e793c88/gitsecops-why-compliance-only-works-when-teams-can-prove-what-they-deliver-not-just-promise-it-42hp</guid>
      <description>&lt;p&gt;Many organizations are working hard to meet NIS2, DORA, or supply-chain-security requirements.&lt;br&gt;
And yet they still fail at a point that seems almost trivial:&lt;/p&gt;

&lt;p&gt;👉 They can’t technically prove what actually happened.&lt;/p&gt;

&lt;p&gt;Auditors ask:&lt;br&gt;
“Show me when, by whom, why, how, and with what something was deployed.”&lt;/p&gt;

&lt;p&gt;And the usual reality is:&lt;br&gt;
— 7 tools&lt;br&gt;
— 5 ticket systems&lt;br&gt;
— 0 unified evidence&lt;br&gt;
— 100% headache&lt;/p&gt;

&lt;p&gt;The solution is simple — but hard to enforce:&lt;/p&gt;

&lt;p&gt;Everything that matters must live versioned in Git.&lt;/p&gt;

&lt;p&gt;Code&lt;/p&gt;

&lt;p&gt;IaC&lt;/p&gt;

&lt;p&gt;Policies-as-Code&lt;/p&gt;

&lt;p&gt;Pipelines&lt;/p&gt;

&lt;p&gt;Evidence&lt;/p&gt;

&lt;p&gt;Risk decisions&lt;/p&gt;

&lt;p&gt;Recovery paths&lt;/p&gt;

&lt;p&gt;Not scattered.&lt;br&gt;
Not “documented somewhere.”&lt;br&gt;
But commit-based, signed, traceable.&lt;/p&gt;

&lt;p&gt;That turns Git into the Technical Source of Trust.&lt;/p&gt;

&lt;p&gt;And suddenly NIS2 &amp;amp; DORA become things you can prove, not just answer vaguely.&lt;/p&gt;

&lt;p&gt;🔐 NIS2&lt;/p&gt;

&lt;p&gt;End-to-end automated traceability across the entire software supply chain — without manual heroism.&lt;/p&gt;

&lt;p&gt;🧩 DORA&lt;/p&gt;

&lt;p&gt;Operational resilience by design through reproducible recovery paths and verifiable risk decisions.&lt;/p&gt;

&lt;p&gt;🇪🇺 Digital Sovereignty&lt;/p&gt;

&lt;p&gt;Sovereign code hosting: the technical proof that you operate independently, controllably, and audit-ready.&lt;/p&gt;

&lt;p&gt;What GitSecOps Changes in Practice&lt;/p&gt;

&lt;p&gt;No more “documentation theater”&lt;/p&gt;

&lt;p&gt;Auditors review technical evidence — not slide decks&lt;/p&gt;

&lt;p&gt;Dev, Sec, and Ops speak from the same data&lt;/p&gt;

&lt;p&gt;Every decision is versioned&lt;/p&gt;

&lt;p&gt;Every deviation is visible&lt;/p&gt;

&lt;p&gt;Every delivery is auditable&lt;/p&gt;

&lt;p&gt;Why I'm Writing About This&lt;/p&gt;

&lt;p&gt;I build systems that prove trust — not promise it.&lt;br&gt;
And GitSecOps is the first approach that puts compliance on a technical foundation without slowing down teams.&lt;/p&gt;

&lt;p&gt;If you want to see how GitSecOps can be implemented in practice, I regularly share patterns, examples, and real use cases here.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>gitops</category>
      <category>secops</category>
      <category>gitsecops</category>
    </item>
  </channel>
</rss>
