post

Policy as Code für CDK

Policy as Code verschiebt Architekturregeln aus Meetings in prüfbare, versionierte Regeln.

≈ 9 Min. Lesezeit

Jedes Team, das länger mit AWS arbeitet, hat eine ungeschriebene Liste: kein Bucket öffentlich lesbar, jede Tabelle verschlüsselt, keine Instanz größer als nötig, alles getaggt. Diese Liste steht selten irgendwo vollständig. Sie lebt in ein paar Köpfen, in einem Wiki, das niemand mehr öffnet, und in Review-Kommentaren, die davon abhängen, wer gerade schaut und wie viel Zeit er hat. Genau das ist das Problem, um das es hier geht – und cloudpatrol ist mein Versuch, es zu lösen.

In meinem Beitrag über die CDK-Anfänge endet der Text mit einem Nebensatz: Für Regeln, die nicht verhandelbar sind, lohnt sich eine maschinelle Prüfung der synthetisierten Vorlage, eine Art Policy as Code für den eigenen CDK-Output. Dieser Beitrag löst diesen Nebensatz ein. Ich beschreibe nicht ein weiteres Tool, sondern eine Verschiebung: von Architekturregeln, die man erklärt, zu Architekturregeln, die man ausführt.

Warum Regeln aus dem Review herausfallen

Ein Review ist ein Mensch mit begrenzter Aufmerksamkeit. Er sieht heute, dass ein Bucket keine Verschlüsselung hat, und übersieht es nächste Woche, weil der Pull Request groß ist, es Freitagnachmittag ist oder die interessante Diskussion woanders lief. Das ist kein Vorwurf, sondern eine Eigenschaft. Wissen, das nur im Review existiert, ist so verlässlich wie die Tagesform des Reviewers.

Daraus folgt eine unbequeme Beobachtung: Eine Regel, die man nicht ausführen kann, ist keine Regel. Sie ist eine Hoffnung. Solange „kein öffentlicher Bucket" ein Satz im Onboarding-Dokument ist, wird er irgendwann gebrochen, und zwar nicht aus bösem Willen, sondern weil ein Default unauffällig durchrutscht. Der Punkt von Policy as Code ist deshalb weniger technisch als kulturell. Er verschiebt die Regel von einem Ort, an dem sie erinnert werden muss, an einen Ort, an dem sie geprüft wird.

Das CDK macht diese Verschiebung erst möglich. Weil Infrastruktur hier Code ist, der zu einer CloudFormation-Vorlage synthetisiert wird, gibt es einen Zwischenschritt, den man inspizieren kann, bevor irgendetwas in der Cloud entsteht. Genau dieser Zwischenschritt ist der richtige Ort für eine Prüfung.

Der Prüfpunkt liegt zwischen Synthese und Deployment

Man kann Cloud-Ressourcen an vielen Stellen prüfen. Zur Laufzeit über einen Dienst, der Konten scannt, findet Verstöße erst, wenn sie schon existieren. Ein Wochenreport ist besser als nichts, kommt aber immer zu spät für den, der den Fehler gemacht hat. Interessant wird es, wenn die Prüfung dorthin wandert, wo die Entscheidung fällt: in den Moment, in dem der Code die Vorlage erzeugt.

flowchart LR
  Source["CDK-Code (TypeScript)"] --> Synth["cdk synth"]
  Synth --> Policy["Policy-Check"]
  Policy -->|verstoss| Fail["Feedback im PR"]
  Policy -->|ok| Deploy["Deployment"]

cloudpatrol setzt genau an dieser Stelle an. Es hängt sich in die Synthese ein und bewertet die Constructs, während sie entstehen. Das ist ein architektonisch anderer Ansatz als ein nachgelagerter Scanner. Die Prüfung sieht nicht die rohe JSON-Vorlage, sondern die typisierten Constructs mit ihren Eigenschaften. Damit lässt sich eine Regel gegen s3.CfnBucket schreiben, statt gegen einen String-Pfad in einem YAML-Baum zu matchen. Die Grenze, an der geprüft wird, ist dieselbe Grenze, an der auch der Entwickler denkt.

Der Effekt auf den Zyklus ist konkret. Ein Verstoß, der früher als Incident sichtbar wurde – jemand meldet einen offenen Bucket, es gibt eine Untersuchung, ein Fix, ein Postmortem – wird jetzt als roter Testlauf sichtbar, bevor der Pull Request gemergt ist. Die Zeit zwischen Fehler und Erkennung schrumpft von Tagen auf Sekunden, und die Kosten der Korrektur sinken entsprechend.

Eine Policy ist Code, kein Config-Eintrag

Der entscheidende Entwurf in cloudpatrol ist, dass eine Regel keine Zeile in einer YAML-Datei ist, sondern eine Klasse. Sie deklariert, für welchen Construct-Typ sie gilt, und sie enthält eine Validator-Methode, die den konkreten Knoten prüft und Befunde an einen Reporter meldet.

import { Policy, PolicyInterface, Reportable } from "cloudpatrol";
import * as s3 from "@aws-cdk/aws-s3";

export class BucketEncryptionPolicy extends Policy implements PolicyInterface {
  public policyName = "Bucket Encryption";
  public description = "S3 buckets must be encrypted at rest.";
  public scope = s3.CfnBucket;

  public validator(node: s3.CfnBucket, reporter: Reportable): void {
    if (!node.bucketEncryption) {
      reporter.addError(node, this, "Bucket has no server-side encryption configured.");
    }
  }
}

Der scope ist der Selektor: Der Validator läuft nur für Buckets, nicht für jeden Knoten im Baum. Innerhalb des Validators habe ich die volle Ausdruckskraft von TypeScript. Ich kann Bedingungen verschachteln, Ausnahmen für bestimmte Umgebungen zulassen, mehrere Eigenschaften zusammen bewerten. Eine deklarative Regelsprache stößt genau hier an ihre Grenze – die interessanten Regeln sind selten „Feld X muss Y sein", sondern „Feld X muss Y sein, außer wenn Z, und dann bitte mit dieser Begründung".

Angewandt wird das Ganze über eine Patrol, die einen Stack durchläuft:

const patrol = new AwsCdkPatrol(awsDefaults);
patrol.check(stack);

cloudpatrol bringt eine Handvoll eingebauter Policies mit – Verschlüsselung, Versionierung, Instanztypen –, aber die eingebauten sind nur der Startpunkt. Der eigentliche Wert entsteht, wenn ein Team seine eigene ungeschriebene Liste in solche Klassen übersetzt. Ab dann ist die Liste nicht mehr ungeschrieben. Sie liegt im Repository, sie ist versioniert, sie hat einen Autor und eine Historie, und man kann über sie in einem Pull Request diskutieren wie über jeden anderen Code.

warn oder block ist eine Entwurfsentscheidung

Nicht jede Regel hat dasselbe Gewicht. Ein fehlendes Kostenstellen-Tag ist ärgerlich, ein öffentlich lesbarer Bucket mit Kundendaten ist ein Vorfall. Deshalb ist die Schwere einer Regel kein Detail, sondern die zentrale Entwurfsentscheidung. Eine Warnung meldet und lässt durch. Ein Fehler blockiert. Wer alles blockiert, erzieht sein Team dazu, die Prüfung zu umgehen. Wer nichts blockiert, hat am Ende nur eine schön formatierte Log-Ausgabe.

Ich unterscheide dabei bewusst zwischen Guardrails und Architekturpolizei. Guardrails definieren die wenigen Leitplanken, die wirklich nicht verhandelbar sind: Verschlüsselung, minimale Rechte, keine offenen Netze, harte Kostenlimits. Innerhalb dieser Leitplanken bleibt das Team handlungsfähig und schnell. Architekturpolizei dagegen versucht, jede Entscheidung zu zentralisieren, und verlagert damit Verantwortung von den Teams weg. Das Erste beschleunigt sicheres Arbeiten, das Zweite bremst und frustriert. Der Unterschied liegt nicht im Werkzeug, sondern in der Frage, wie viele Regeln man auf block setzt und wie gut man diese wenigen begründet.

type Severity = "warn" | "block";

type RuleMeta = {
  policyName: string;
  severity: Severity;
  reason: string;
  suggestion: string;
};

Jede Regel braucht einen Grund und einen Ausweg

Eine Prüfung, die nur „verboten" sagt, ist eine Wand. Der Entwickler steht davor, versteht nicht, warum, und sucht den kürzesten Weg drumherum. Eine Prüfung, die den Grund und eine Alternative liefert, ist Lernfeedback. Sie erklärt die Entscheidung an genau der Stelle, an der sie relevant wird, und macht den nächsten Entwickler ein Stück selbständiger.

{
  "policy": "lambda-timeout-max",
  "severity": "block",
  "reason": "A timeout above 30s usually hides a synchronous call that should be async.",
  "suggestion": "Move the work behind a queue or a Step Function and return early."
}

Diese drei Felder – Grund, Schwere, Ausweg – sind der Unterschied zwischen einer Regel, die Reibung erzeugt, und einer, die Reibung abbaut. Der Grund verhindert, dass die Regel als Willkür gelesen wird. Der Ausweg verwandelt das Blockieren in eine Empfehlung. Genau deshalb gehört zu jeder Policy in cloudpatrol ein description- und ein link-Feld: Die Regel trägt ihre eigene Begründung mit sich, statt sie in einem Wiki zu parken, das vom Code entkoppelt ist.

Der Pull Request ist der richtige Ort

Guardrails wirken am besten vor dem Deployment, und der Pull Request ist der natürliche Ort dafür, weil dort Code, Kontext und Diskussion zusammenkommen. Praktisch heißt das: Der Policy-Check läuft in derselben Pipeline, die den Code baut und die Infrastruktur ausliefert. Ein roter Check ist dann kein Bürokratie-Schritt, sondern dieselbe Art von Signal wie ein fehlgeschlagener Unit-Test.

Der Nutzen ist an dieser Stelle messbar, und zwar auf drei Ebenen. Erstens die Zykluszeit: Ein Verstoß wird beim Öffnen des Pull Requests sichtbar, nicht Tage später im Betrieb. Zweitens die Konsistenz: Die Prüfung ist immer gleich streng, unabhängig davon, wer reviewt und wie voll der Kalender ist. Drittens die Review-Last: Menschen müssen nicht mehr mechanisch nach denselben zehn Dingen suchen, sondern können sich auf die Fragen konzentrieren, die eine Maschine nicht beantwortet – ist der Zuschnitt des Stacks sinnvoll, passt die Abstraktion, stimmt das Kostenprofil.

Das ist der eigentliche Produktivitätsgewinn. Nicht, dass ein Tool Fehler findet, sondern dass es die immer gleiche, langweilige Prüfarbeit übernimmt und den Menschen für die interessante Arbeit freispielt.

Kosten sind ein Architektursignal

Ein Nebeneffekt lohnt die Erwähnung, weil er oft übersehen wird. Cloud-Kosten sind nicht nur ein Thema für die Buchhaltung, sie sind ein Verhaltenssignal der Architektur. Eine Funktion, die zu oft startet, eine Ressource, die zu großzügig dimensioniert ist, ein Deployment, das unnötig Artefakte erzeugt – all das hinterlässt eine Kostenspur, bevor es als Architekturproblem benannt wird.

flowchart LR
  Usage["Nutzung"] --> Cost["Kostenspur"]
  Cost --> Signal["Signal"]
  Signal --> Rule["Policy als Leitplanke"]

Eine Policy, die einen zu großzügigen Instanztyp oder eine fehlende Lifecycle-Regel markiert, ist deshalb nicht nur eine technische Prüfung. Sie fängt eine Kostenentscheidung ab, bevor sie im nächsten Monat als Rechnung auftaucht. Kosten ersetzen kein Design, aber sie sind ein ehrlicher Reality-Check dafür, ob die Architektur sich so verhält, wie man es beim Entwerfen angenommen hat.

Wo Policy as Code an ihre Grenze kommt

Es wäre ein Fehler, alles in eine Regel gießen zu wollen. Manche Entscheidungen sind keine Prüfung, sondern eine Abwägung, und die gehört zu einem Menschen. Ein Timeout über dreißig Sekunden kann falsch sein oder genau richtig, je nach Kontext. Solche Fälle blockiere ich nicht stumpf, sondern leite sie in ein Architekturgespräch oder einen kurzen ADR um. Die Policy sagt dann nicht „verboten", sondern „das braucht eine Begründung".

Damit hat Policy as Code eine klare Zuständigkeit. Sie eignet sich für die kleine Menge nicht verhandelbarer Leitplanken, deren Verletzung objektiv erkennbar ist. Sie eignet sich nicht für Geschmacksfragen und nicht für Abwägungen, bei denen die richtige Antwort vom Projekt abhängt. Wer diese Grenze respektiert, bekommt ein Werkzeug, das Vertrauen schafft. Wer sie ignoriert und jede Meinung zur blockierenden Regel macht, bekommt ein Werkzeug, das umgangen wird.

Ein zweiter Vorbehalt betrifft die Abstraktion. cloudpatrol prüft, was die Synthese erzeugt. Ein selbst geschriebenes Construct, das ein Dutzend Ressourcen kapselt, kann eine Regel unbeabsichtigt aushebeln, wenn es die geprüfte Eigenschaft anders setzt, als man denkt. Die Prüfung ist also nur so ehrlich wie die Sichtbarkeit der Constructs. Das ist kein Argument gegen Abstraktion, aber eine Erinnerung daran, dass eine Regel und die Bausteine, gegen die sie läuft, zusammen entworfen werden müssen.

Was ich nach ein paar Wochen mitnehme

cloudpatrol ist Alpha, die API bewegt sich, und ich würde heute keine große Initiative darauf aufbauen. Aber die Richtung überzeugt mich, und sie ist unabhängig vom konkreten Tool. Sobald Infrastruktur Code ist, gehören ihre Regeln in denselben Kreislauf wie der Code: versioniert, geprüft, im Pull Request diskutierbar. Der Gewinn ist nicht, dass eine Maschine Fehler findet. Der Gewinn ist, dass eine Architekturentscheidung nicht mehr im Kopf einer einzelnen Person liegt, sondern als ausführbare, begründete Regel im Repository steht, an der ein anderer Entwickler morgen sinnvoll widersprechen kann.

Mein Kriterium dafür, ob sich eine Regel lohnt, ist am Ende einfach. Sie muss einen echten Fehlerfall verhindern, den ich benennen kann, sie muss ihren Grund mitliefern, und sie muss einen Ausweg zeigen. Erfüllt sie das, verschiebt sie Wissen von der Tagesform des Reviewers in eine Prüfung, die immer gleich streng ist. Genau das ist der Kern: Policy as Code verschiebt Architekturregeln aus Meetings und Wikis dorthin, wo sie tatsächlich wirken.

Weiterführende Quellen

Kommentare