Infrastruktura jako kod z Cursorem
Agent Cursora dobrze pisze moduły Terraform, Dockerfile i manifesty Kubernetes, gdy reguła projektu ustala konwencje, a serwery MCP tylko do odczytu pokazują mu prawdziwy schemat providera i konto w chmurze. O poprawności zmiany infrastruktury świadczy bramka, a nie czytanie HCL: formatowanie, walidacja, skan checkov i podsumowanie planu, które wskazuje każde usunięcie.
Ta strona jest dla dewelopera, który odpowiada za infrastrukturę serwisu, i dla tech leada, który zatwierdza jej zmiany. Poprosiłeś Agenta o serwis ECS za load balancerem i dostałeś 300 linii Terraform, które przechodzą walidację. Plan mówi 4 to add, 1 to change, 1 to destroy, a niszczonym zasobem jest twoja produkcyjna baza danych, bo Agent zmienił jej nazwę, żeby pasowała do konwencji nazewnictwa.
Najpierw skonfiguruj reguły projektu i MCP w Cursorze; ta strona korzysta z obu.
Co daje ci praca nad infrastrukturą w Cursorze
Dział zatytułowany „Co daje ci praca nad infrastrukturą w Cursorze”- Regułę projektu, która ustala chmurę, nazewnictwo, tagi i bazowe wymagania bezpieczeństwa oraz zabrania Agentowi wdrażania czegokolwiek.
- Serwery MCP Terraform i AWS podłączone tylko do odczytu, dzięki którym Agent sprawdza prawdziwe argumenty providera i istniejące zasoby, zamiast zgadywać.
- Trzy prompty do skopiowania: moduł Terraform, produkcyjny Dockerfile i manifesty Kustomize.
- Jeden skrypt bramki, któremu recenzent ufa bez czytania każdej linii:
fmt,validate, checkov na kodzie, na sekretach i na planie oraz twarde zatrzymanie przy każdym usunięciu lub zastąpieniu zasobu. - Ścieżkę akceptacji, w której
terraform applyuruchamia CI, a nie Agent.
Dlaczego kod infrastruktury potrzebuje innej bramki niż kod aplikacji
Dział zatytułowany „Dlaczego kod infrastruktury potrzebuje innej bramki niż kod aplikacji”Kod aplikacji ma testy, które padają, gdy zachowanie jest złe. Kod infrastruktury w większości repozytoriów nie ma testów jednostkowych, a moduł, który przechodzi walidację, nadal może otworzyć port 22 na cały internet albo zastąpić zasób przechowujący dane. Dowodem zmiany infrastruktury jest więc plan i skan polityk, a nie diff.
| Artefakt | Bramka, która robi się czerwona | Co wyłapuje |
|---|---|---|
| Terraform | terraform fmt -check, terraform validate | Błędy składni, wymyślone argumenty, brakujące zmienne |
| Terraform | checkov --framework terraform,secrets | Otwarte security groups, niezaszyfrowane dane, symbole wieloznaczne * w politykach IAM, sekrety w plikach |
| Plan Terraform | checkov --framework terraform_plan i podsumowanie terraform show -json przez jq | Usunięcia, zastąpienia i naruszenia polityk w wartościach wyliczanych w planie |
| Dockerfile | checkov --framework dockerfile, docker build, użytkownik ustawiony w obrazie | Użytkownik root, brak HEALTHCHECK, build działający tylko na twoim laptopie |
| Kubernetes | kubectl kustomize, checkov --framework kubernetes, kubectl apply --dry-run=server | Brak limitów i sond, uprzywilejowane pody, manifesty odrzucane przez API server |
checkov obsługuje wszystkie trzy formaty jednym narzędziem. Flagi z tej strony sprawdziliśmy na checkov 3.3.21 z PyPI; narzędzie kończy się kodem 1, gdy którakolwiek kontrola nie przejdzie, więc bez zmian działa jako bramka w CI.
Skonfiguruj Cursora do pracy z infrastrukturą
Dział zatytułowany „Skonfiguruj Cursora do pracy z infrastrukturą”-
Napisz regułę infrastruktury. Zapisz ją jako
.cursor/rules/infrastructure.mdc. Globy sprawiają, że ładuje się tylko wtedy, gdy Agent dotyka Terraform, Dockerfile albo manifestów Kubernetes, więc nie obciąża kontekstu przy pracy nad aplikacją. Frontmatter jest taki sam jak w przykładzie reguły dla wybranych plików w przewodniku Reguły projektu; jeśli reguła się nie ładuje, wykonaj opisany tam test kanarkowy.---globs: infrastructure/**/*.tf, **/Dockerfile, k8s/**alwaysApply: false---Cloud: AWS, regions eu-west-1 (primary) and us-east-1 (CloudFront, ACM for CloudFront).Layout: reusable modules in infrastructure/modules/, one root per environment ininfrastructure/environments/{staging,production}/. Pin versions in versions.tf; copy theexisting pins, never invent a newer one.Naming: {env}-{service}-{resource}, for example prod-api-alb.Tags on every resource: Environment, Service, Team, ManagedBy=terraform (use default_tags).Security baseline:- No 0.0.0.0/0 ingress except the public ALB on 443.- Encryption at rest on every RDS instance, S3 bucket and EBS volume.- IMDSv2 required on EC2 and launch templates.- No secret values in any file. Reference Secrets Manager ARNs or variables only.- IAM policies name actions and resources; no "*" in either without a comment saying why.Before you change existing code:- Never rename a resource address or a name argument on a stateful resource (RDS, S3,EFS, DynamoDB). If a rename is required, add a moved block and say so.- Use the Terraform MCP server for provider arguments and the AWS MCP server to listwhat already exists. Never create a resource the account already has; use an import block.Done means AWS_PROFILE=agent-readonly scripts/iac-check.sh <environment dir> exits 0;paste its last 20 lines.Never run terraform apply, terraform destroy, terraform import, terraform state, or anykubectl apply/delete, including --dry-run=server. Stop after the plan and show the summary. -
Podłącz serwery MCP Terraform i AWS tylko do odczytu. Dodaj oba do
.cursor/mcp.json. Serwer Terraform bez tokena udostępnia wyłącznie publiczne zapytania do Terraform Registry. Serwer AWS API zREAD_OPERATIONS_ONLYodmawia każdej operacji AWS, której poziom dostępu w IAM toWrite.{"mcpServers": {"terraform": {"command": "docker","args": ["run", "-i", "--rm", "hashicorp/terraform-mcp-server:1.3.0"]},"aws-api": {"command": "uvx","args": ["awslabs.aws-api-mcp-server@1.5.6"],"env": {"AWS_REGION": "eu-west-1","AWS_API_MCP_PROFILE_NAME": "agent-readonly","READ_OPERATIONS_ONLY": "true"}}}}Profil
agent-readonlymusi wskazywać rolę IAM, która może tylko czytać, na przykład z zarządzaną przez AWS politykąReadOnlyAccess. README serweraaws-api-mcp-serverostrzega, że nawet rola zReadOnlyAccessmoże zwrócić w wynikach poleceń poświadczenia AWS albo inne wrażliwe dane, a te wyniki trafiają do kontekstu Agenta. Lepiej użyćViewOnlyAccessalbo własnej, zawężonej polityki odczytu, która pomija odczyty zawierające sekrety, takie jaksecretsmanager:GetSecretValuei konfiguracja środowiska funkcji Lambda. To samo README nazywa IAM „the primary and most reliable security control”, aREAD_OPERATIONS_ONLYdodatkową warstwą. Wersje sprawdziliśmy 2026-09-26:hashicorp/terraform-mcp-server1.3.0 w Docker Hub iawslabs.aws-api-mcp-server1.5.6 w PyPI. Inne serwery dla infrastruktury opisuje przewodnik po MCP dla infrastruktury. -
Trzymaj apply poza zasięgiem Agenta. Cursor opisuje kontrolę nad terminalem na stronie Run modes. Nie dodawaj
terraform apply,terraform destroy,kubectl applyanikubectl deletedo żadnej allowlisty i nigdy nie włączaj wykonywania bez nadzoru na maszynie z poświadczeniami do chmury. Żeby blokować je w kodzie, a nie z przyzwyczajenia, dodaj strażnika opartego na hookach opisanego w przewodniku Zabezpieczenie Cursora w zespole. Ten strażnik blokuje polecenia sieciowe,git pushi pliki z sekretami, ale nie zapisy w infrastrukturze, więc dopisz tę linię do jego gałęzi dla poleceń powłoki, a te przypadki do jego testu w CI. Wzorzec blokuje teżkubectl apply --dry-run=server, bo reguła zostawia nawet dry run człowiekowi albo CI. Wzorzec łapie bezpośrednie wywołania, ale nie opakowanie w rodzajumake apply. Zabezpieczeniem, które naprawdę działa, jest brak w sesji poświadczeń z prawem zapisu (krok 4 poniżej i krok 4 listy w sekcji o zatwierdzaniu). To samo dotyczy bramki: terminal Agenta dziedziczy poświadczenia do chmury, które ma już twoja powłoka, często profil administratora, więc uruchamiaj bramkę jakoAWS_PROFILE=agent-readonly scripts/iac-check.sh ….// .cursor/hooks/guard.mjs, inside the `mode === 'shell'` branch, before the final allowif (/\bterraform\b[^;&|\n]*\s(apply|destroy|import|state)\b|\bkubectl\b[^;&|\n]*\s(apply|delete)\b/.test(cmd))deny(`Infrastructure write blocked by repo policy: ${cmd}`);scripts/check-cursor-guard.sh expect shell '{"command":"terraform apply"}' denyexpect shell '{"command":"terraform -chdir=envs/prod destroy"}' denyexpect shell '{"command":"kubectl apply --dry-run=server -f x.yaml"}' denyexpect shell '{"command":"terraform plan -out=tfplan"}' allow -
Sprawdź konfigurację, zanim jej zaufasz. Przetestuj rolę bezpośrednio, poza Agentem. Pierwsze polecenie musi pokazać rolę tylko do odczytu. Drugie musi zwrócić
AccessDenied.Okno terminala aws sts get-caller-identity --profile agent-readonlyaws s3api create-bucket --bucket agent-write-test-7f3a \--create-bucket-configuration LocationConstraint=eu-west-1 --profile agent-readonlyJeśli bucket powstał, rola ma prawo zapisu. Popraw rolę IAM, zanim pójdziesz dalej.
Potem zadaj Agentowi dwa pytania w nowym czacie. Pierwsza odpowiedź musi powołać się na prawdziwe wywołanie narzędzia MCP. Druga prośba musi zostać odrzucona.
Using the aws-api MCP server, which AWS account and region are you connected to? List the VPC IDs.Use only the aws-api MCP server; do not run any terminal command. Create an S3 bucket named agent-write-test-7f3a.Odmowa serwera MCP dowodzi tylko tego, że ustawiono
READ_OPERATIONS_ONLY: serwer blokuje wywołanie lokalnie, więc żądanie nie trafia do AWS i IAM nie zostaje sprawdzone. Rolę sprawdza wyłącznie bezpośrednia próba przez CLI.
Wygeneruj moduł Terraform Agentem
Dział zatytułowany „Wygeneruj moduł Terraform Agentem”Zacznij w Plan Mode, który według Cursora tworzy „detailed implementation plans before writing any code”. Plan infrastruktury tanio się poprawia; 300 linii HCL już nie.
W planie, który zaproponuje Agent, sprawdź tylko trzy rzeczy: czy używa istniejących zasobów zamiast tworzyć nowe, czy rola zadania nie daje niczego poza ścieżką sekretów i czy nie dotyka żadnego zasobu przechowującego dane. Resztę sprawdza bramka.
Gdy terraform validate albo plan zwróci błąd, wklej cały komunikat do tego samego czatu. Błąd w rodzaju Reference to undeclared resource zwykle oznacza, że Agent odwołał się do zasobu z innego modułu. Poprawka to output w tamtym module i zmienna wejściowa tutaj, a nie kopia zasobu.
Udowodnij zmianę Terraform bez czytania każdej linii
Dział zatytułowany „Udowodnij zmianę Terraform bez czytania każdej linii”Dodaj ten skrypt do repozytorium jako scripts/iac-check.sh. Agent uruchamia go, zanim zgłosi, że skończył, a CI uruchamia ten sam plik przy każdym pull requeście, więc obaj widzą ten sam werdykt.
#!/usr/bin/env bash# Usage: scripts/iac-check.sh infrastructure/environments/staging# Needs terraform, checkov and jq. Run it only with read-only cloud credentials.set -euo pipefaildir="${1:?usage: iac-check.sh <environment dir>}"
terraform fmt -check -recursive infrastructure/terraform -chdir="$dir" init -input=falseterraform -chdir="$dir" validatecheckov -d infrastructure/ --framework terraform,secrets --compact --quiet
# -lock=false: planning with a read-only role cannot take the state lockterraform -chdir="$dir" plan -input=false -lock=false -out=tfplanterraform -chdir="$dir" show -json tfplan > "$dir/tfplan.json"checkov -f "$dir/tfplan.json" --framework terraform_plan --compact --quiet
echo "Planned actions:"jq '[(.resource_changes // [])[].change.actions | join("+")] | group_by(.) | map({(.[0]): length}) | add' "$dir/tfplan.json"
echo "Deletes and replacements:"jq -r '(.resource_changes // [])[] | select(.change.actions | index("delete")) | "\(.change.actions | join("+"))\t\(.address)"' "$dir/tfplan.json"
if [ "${ALLOW_DESTROY:-0}" != "1" ]; then jq -e '[(.resource_changes // [])[] | select(.change.actions | index("delete"))] | length == 0' "$dir/tfplan.json" > /dev/null \ || { echo "Plan deletes or replaces resources. Re-run with ALLOW_DESTROY=1 only after a human approves the list above."; exit 1; }fiUruchamiaj bramkę z profilem tylko do odczytu, na przykład AWS_PROFILE=agent-readonly scripts/iac-check.sh infrastructure/environments/staging, żeby terminal Agenta nigdy nie tworzył planu z poświadczeniami do zapisu. CI ustawia zamiast tego własną rolę tylko do odczytu, co pokazuje sekcja o zatwierdzaniu.
Dodaj tfplan i tfplan.json do .gitignore: plik planu może zawierać wrażliwe wartości otwartym tekstem.
Skrypt zamienia duży diff w krótką listę, którą człowiek oceni w minutę:
| Człowiek czyta | Bramka sprawdza |
|---|---|
Liczbę akcji każdego typu (create, update, delete+create) i to, czy pasują do zlecenia | Formatowanie, składnię i niezadeklarowane odwołania |
| Każdą linię pod „Deletes and replacements” | Polityki security groups, szyfrowania, IMDSv2 i symboli wieloznacznych * w politykach IAM |
| Zmiany polityk IAM, bo checkov potwierdza, że polityka jest wąska, a nie że jest właściwa | Wartości znane dopiero w planie, sprawdzane skanem terraform_plan |
delete+create na bazie danych, buckecie albo systemie plików oznacza stop. Poproś Agenta o blok moved albo o cofnięcie zmiany nazwy i uruchom bramkę ponownie. Jak to podsumowanie łączy się z wynikami testów i skanów w jednym manifeście pull requesta, opisuje pakiet dowodów.
Buduj kontenery i manifesty Kubernetes z tą samą bramką
Dział zatytułowany „Buduj kontenery i manifesty Kubernetes z tą samą bramką”Wzorzec się powtarza: konkretny prompt, a potem kontrola, która robi się czerwona na błędach typowych dla Agenta. W Dockerfile są to zwykle użytkownik root, brak health checka i zależności deweloperskie skopiowane do finalnego obrazu.
Przy Kubernetes daj Agentowi w kontekście Dockerfile i moduł Terraform, żeby porty, ścieżki health checków i rozmiary zasobów zgadzały się we wszystkich trzech warstwach.
ExternalSecret wymaga zainstalowanego w klastrze External Secrets Operator. Bez niego dry run po stronie serwera odrzuci ten typ zasobu.
Ostatnia kontrola działa na klastrze stagingowym: kubectl apply --dry-run=server -f /tmp/staging.yaml. API server waliduje obiekty, uruchamia webhooki admission i niczego nie zapisuje. Wymaga jednak tych samych uprawnień RBAC co prawdziwy apply, więc uruchom go sam albo z CI, zamiast dawać Agentowi takie poświadczenia. Głębsze wzorce dla Dockera i klastrów znajdziesz w konteneryzacji z agentami AI i w przepisach na Kubernetes.
Kto zatwierdza zmianę infrastruktury?
Dział zatytułowany „Kto zatwierdza zmianę infrastruktury?”Agent pisze i sprawdza; o tym, co trafia do chmury, decydują ludzie i CI.
- Autor pracuje z Agentem, czyta podsumowanie planu i listę usunięć, a potem otwiera pull request z oboma wklejonymi w opis.
- CI uruchamia
scripts/iac-check.shdla każdego zmienionego środowiska z rolą tylko do odczytu i publikuje podsumowanie planu w pull requeście. Czerwona bramka blokuje merge.terraform planuruchamia pluginy providerów i każde źródło danychexternalz gałęzi, więc uruchamiaj ten job tylko dla gałęzi z własnego repozytorium, nigdy dla forków, i daj mu krótkotrwałą rolę tylko do odczytu przez OIDC. Jak napisać i sprawdzić taki workflow, pokazuje strona pipeline’y CI/CD z Cursorem. - Właściciel kodu dla
infrastructure/, wskazany wCODEOWNERS, zatwierdza zmianę. Jego review obejmuje podsumowanie, usunięcia i zmiany IAM, a nie każdą linię. Bugbot może dodać drugie, automatyczne review, a PR Routing & Approval może kierować ścieżki infrastruktury do właściwego właściciela. - CI wykonuje apply po merge’u, z chronionego środowiska, które jako jedyne ma poświadczenia do zapisu. Nie ma ich żaden laptop ani żadna sesja Agenta.
Co się psuje, gdy Cursor pisze infrastrukturę?
Dział zatytułowany „Co się psuje, gdy Cursor pisze infrastrukturę?”Agent wymyśla argument albo wersję providera. terraform validate zwraca Unsupported argument albo init nie znajduje wersji. Jak naprawić: sprawdź w ustawieniach MCP Cursora, czy serwer Terraform jest połączony, a potem poproś Agenta, żeby sprawdził tam zasób i zacytował listę argumentów, zanim cokolwiek zmieni. Linia reguły o kopiowaniu istniejących wersji zapobiega ich rozjeżdżaniu się.
Plan zastępuje zasób przechowujący dane. Bramka zatrzymuje się na delete+create przy bazie danych albo buckecie. Zwykła przyczyna to zmieniony adres zasobu albo argument name, który wymusza zastąpienie. Jak naprawić: cofnij zmianę nazwy albo dodaj blok moved przy zmianie adresu. Dopisz do zasobu lifecycle { prevent_destroy = true }, żeby następną próbę odrzucił sam Terraform.
Agent tworzy coś, co już istnieje. Apply kończy się błędem already exists albo plan tworzy drugi VPC. Jak naprawić: poproś Agenta, żeby wylistował istniejący zasób przez serwer AWS MCP i napisał dla niego blok import. Sprawdzaj plan, aż zasób będzie importowany, bez akcji create.
checkov odrzuca świadomy wyjątek. Bucket serwowany bezpośrednio jako strona S3, bez CloudFront przed nim, potrzebuje ACL z publicznym odczytem i nie przechodzi CKV_AWS_20. Jak naprawić: pomiń tę jedną kontrolę na tym jednym zasobie, podając powód. Umieść komentarz w zasobie, który checkov wskazuje w linii FAILED. Dla CKV_AWS_20 to blok aws_s3_bucket, a nie aws_s3_bucket_acl. Dodaj w tym bloku #checkov:skip=CKV_AWS_20:Public S3 website endpoint, no CloudFront i niech właściciel kodu zatwierdzi ten komentarz. Nigdy nie dodawaj globalnego --skip-check do skryptu, bo wyciszysz kontrolę dla każdego przyszłego zasobu.
Sekret trafia do pliku. Agent wkleił prawdziwą wartość do terraform.tfvars albo manifestu. Jak naprawić: traktuj wartość jako ujawnioną i zrotuj ją, nawet jeśli commit nie opuścił twojej maszyny. Zastąp ją odwołaniem do Secrets Manager. Potem sprawdź, czy skan secrets w bramce odrzuca kopię roboczą pliku, która nadal zawiera starą wartość. Jeśli ją przepuszcza, checkov nie rozpoznaje formatu tego sekretu i sama bramka nie wyłapie następnego.
Profil MCP może zapisywać. Bezpośrednia próba przez CLI z kroku 4 konfiguracji się powiodła i utworzyła bucket. Jak naprawić: usuń bucket, podepnij do roli profilu politykę tylko do odczytu i usuń wszystkie pozostałe. READ_OPERATIONS_ONLY nie zastępuje roli tylko do odczytu.
Staging i produkcja się rozjeżdżają. Moduł dostaje nową zmienną, a ustawia ją tylko jedno środowisko. Jak naprawić: uruchamiaj bramkę dla każdego katalogu środowiska, nie tylko dla zmienionego. Poproś Agenta, żeby dodał zmienną do każdego terraform.tfvars z komentarzem wyjaśniającym różnice między środowiskami.