Automatisert prosjektoppstart i AWS

Harald Vinje
12 min lesning
DevOpsProductivityCloudAWS
Del:

Å sette opp et nytt prosjekt i AWS kan være tidkrevende. Du må opprette nye AWS-kontoer, konfigurere riktige tilganger, sette opp GitHub-repositories, og koble alt sammen. Hva om hele denne prosessen kunne automatiseres med én enkelt kodeendring?

I dette blogginnlegget viser jeg hvordan jeg har bygget et system som automatisk provisionerer komplette prosjektmiljøer – fra AWS-kontoer til ferdigkonfigurerte GitHub-repositories – ved å kun spesifisere et prosjektnavn i en konfigurasjonsfil.

Illustrasjon av AWS og OpenTofu. Bildet er generert av Google Gemini.

Jeg antar litt bakgrunnkunnskap om Terraform og AWS. For en introduksjon kan du lese mitt tidligere blogginnlegg om temaet.

Problemet

Når man skal starte et nytt prosjekt i AWS med moderne best practices er det mye som må eller bør settes opp:

  • Separate AWS-kontoer for hvert miljø (dev, stage, prod) - gir isolasjon og sikkerhet
  • Organisasjonsstruktur i AWS Organizations - for å strukturere kontoer
  • IAM-roller og tilganger for å deploye fra GitHub Actions
  • S3-bøtter for OpenTofu/Terraform state
  • GitHub-repositories for kode og infrastruktur
  • OpenID Connect (OIDC) setup for sikker autentisering uten lagrede secrets
  • GitHub Actions variabler som kobler alt sammen

Man kan gjøre det enklere ved å bruke én AWS-konto, hardkodede credentials, og manuell konfigurasjon. Men det gir dårligere isolasjon, mer kompleks IAM-håndtering, og økt risiko.

Problemet er at å gjøre dette riktig er mye manuelt arbeid som må gjentas for hvert prosjekt. Og det blir fort uoversiktlig når man har mange prosjekter.

Ønsket resultat

Målet var en løsning som gir følgende for hvert nytt prosjekt:

  • Egne AWS-kontoer for hvert miljø - Full isolasjon mellom dev, stage og prod. Dette gir bedre sikkerhet (begrenset blast radius ved feil), enklere kostnadsoppfølging per miljø, og ingen risiko for at ressurser i dev påvirker prod.
  • Dedikert IaC-repo - Ett Terraform-repo per prosjekt. Dette holder infrastrukturkoden organisert og lar hvert team eie sin egen infrastruktur.
  • Code repositories - Separate repos for frontend, backend, eller andre komponenter etter behov. Gir fleksibilitet i struktur og deployment-pipelines.
  • Minimal manuell innsats - Hele oppsettet skal kunne gjøres ved å legge til noen linjer i én konfigurasjonsfil.

Målet er altså å gå fra "null" til "produksjonsklar infrastruktur" på noen få minutter!

Løsningen: IaC for IaC

Løsningen er basert på OpenTofu (en open source fork av Terraform) som automatiserer hele prosessen. OpenTofu har en viktig fordel over Terraform: det støtter dynamisk for_each på providers, noe som gjør det mulig å assume inn i flere AWS-roller på en elegant måte.

Slik fungerer det

Hele prosessen starter med én enkel fil: config/projects.tfvars. Her definerer du prosjektene dine:

config/projects.tfvars
1projects = {
2 "shared-services" = {
3 environments = ["dev", "prod"]
4 code_repo_names = ["shared-services"]
5 }
6
7 "my-new-webapp" = {
8 environments = ["dev", "stage", "prod"]
9 code_repo_names = ["my-new-webapp-frontend", "my-new-webapp-backend"]
10 }
11}


That's it! Fra denne konfigurasjonen vil systemet automatisk:

  • Opprette AWS-kontoer for hvert miljø
  • Sette opp GitHub-repositories (både kode-repos og IaC-repo)
  • Bootstrappe hver konto med nødvendig infrastruktur
  • Koble alt sammen: It just works!

Arkitekturen

Løsningen består av tre hovedsteg som kjører sekvensielt i GitHub Actions:

Steg 1: Main Environment - Opprette AWS-kontoer og repos

modules/project/main.tf
1resource "aws_organizations_organizational_unit" "project_ou" {
2 name = var.project_name
3 parent_id = var.parent_ou_id
4}
5
6resource "aws_organizations_account" "env_account" {
7 for_each = toset(var.environments)
8
9 name = "${var.project_name}-${each.key}"
10 email = "aws+${var.project_name}-${each.key}@${var.organization_domain}"
11 close_on_deletion = true
12 parent_id = aws_organizations_organizational_unit.project_ou.id
13 role_name = "OrganizationAccountAccessRole"
14
15 tags = {
16 Project = var.project_name
17 Environment = each.key
18 }
19}

Dette oppretter:

  • En dedikert Organizational Unit (OU) for prosjektet
  • Separate AWS-kontoer for hvert miljø (dev, stage, prod osv.)
  • GitHub-repositories for kode

Steg 2: Bootstrap - Sette opp infrastruktur i hver konto

Her kommer den smarte delen, og grunnen til at vi bruker OpenTofu. Systemet bruker OpenTofu sin for_each på providers for å dynamisk "assume_role" inn i hver nyopprettede konto (les mer om dynamiske providers her):

environments/bootstrap/main.tf
1provider "aws" {
2 region = "eu-west-1"
3
4 for_each = local.provider_map
5 alias = "by_account"
6
7 assume_role {
8 role_arn = "arn:aws:iam::${each.key}:role/OrganizationAccountAccessRole"
9 }
10}
11
12module "account_bootstrap" {
13 source = "../../modules/account-bootstrap"
14
15 for_each = local.bootstrap_map
16
17 account_id = each.value.account_id
18 organization_name = var.organization_name
19 iac_repo_name = each.value.iac_repo_name
20 environment = each.value.environment
21
22 providers = {
23 aws = aws.by_account[each.value.account_id]
24 }
25}

Bootstrap-modulen setter opp:

GitHub OIDC Provider for sikker autentisering uten secrets:

1resource "aws_iam_openid_connect_provider" "github" {
2 url = "https://token.actions.githubusercontent.com"
3 client_id_list = ["sts.amazonaws.com"]
4 thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
5}

IAM-rolle som GitHub Actions kan bruke:

1resource "aws_iam_role" "github_oidc" {
2 name = "GithubActionsIacDeployRole-${var.iac_repo_name}"
3
4 assume_role_policy = jsonencode({
5 Version = "2012-10-17"
6 Statement = [{
7 Effect = "Allow"
8 Principal = {
9 Federated = aws_iam_openid_connect_provider.github.arn
10 }
11 Action = "sts:AssumeRoleWithWebIdentity"
12 Condition = {
13 StringLike = {
14 "token.actions.githubusercontent.com:sub" = "repo:${var.organization_name}/${var.iac_repo_name}:*"
15 }
16 StringEquals = {
17 "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
18 }
19 }
20 }]
21 })
22}

S3 bucket for state file til den nye IaC-koden:

1resource "aws_s3_bucket" "terraform_state" {
2 bucket = "${var.account_id}-${var.iac_repo_name}-tf-state"
3
4 lifecycle {
5 prevent_destroy = true
6 }
7}
8
9resource "aws_s3_bucket_versioning" "versioning" {
10 bucket = aws_s3_bucket.terraform_state.id
11 versioning_configuration {
12 status = "Enabled"
13 }
14}

Steg 3: Project IaC Repos - Opprette IaC-repositories

Til slutt opprettes et IaC-repository basert på en template, med miljøvariabler som kobler dem til AWS:

modules/github-iac-repo/main.tf
1resource "github_repository" "iac_repo" {
2 name = var.iac_repo_name
3 description = "Infrastructure as Code repository"
4 visibility = "private"
5
6 template {
7 owner = var.organization_name
8 repository = "iac-project-template"
9 }
10}
11
12resource "github_repository_environment" "aws_environments" {
13 for_each = var.environment_role_arns
14
15 repository = github_repository.iac_repo.name
16 environment = each.key
17}
18
19resource "github_actions_environment_variable" "aws_role_arn" {
20 for_each = var.environment_role_arns
21
22 repository = github_repository.iac_repo.name
23 environment = github_repository_environment.aws_environments[each.key].environment
24 variable_name = "AWS_ROLE_ARN"
25 value = each.value
26}

Dette oppretter et GitHub-repo basert på en template, med miljøer (dev, stage, prod) og miljøvariabler som peker til riktige IAM-roller i AWS.

Hvorfor 3 steg? 🤔

Rekkefølgen er ikke tilfeldig. Først må vi opprette selve kontoene og reposene som resten av prosessen skal bruke. Deretter kan vi bootstrappe hver konto med nødvendige roller, OIDC-oppsett og state-bøtter slik at de faktisk kan administreres videre. Til slutt, når alt det grunnleggende er på plass, genereres prosjektets IaC-repo som tar over ansvaret for den videre infrastrukturen. Med andre ord – hvert steg bygger på resultatet fra det forrige, og derfor må de kjøres sekvensielt.

Men Terraform fikser jo avhengigheter, gjør det ikke?

Som regel, ja – Terraform håndterer rekkefølgen mellom ressurser så lenge alt skjer innenfor samme konto og provider. Her er det annerledes: vi oppretter helt nye AWS-kontoer, og de eksisterer ikke før første apply er ferdig. Først da får vi tilgang til dem og kan anta roller for å kjøre neste steg. Det betyr at Terraform rett og slett ikke kan planlegge alt i én omgang, fordi providerne og tilganger til de nye kontoene ikke finnes ennå. Når infrastrukturen krysser konto- og autentiseringsgrenser, må vi dele opp prosessen i flere omganger for at alt skal henge sammen.

Workflowen i praksis

Hele prosessen er automatisert gjennom GitHub Actions med innebygd godkjenning:

  • Utvikler legger til et nytt prosjekt i config/projects.tfvars
  • Pull Request opprettes - workflow kjører tofu plan og kommenterer planen på PR-en
  • Review og merge til main branch
  • Automatisk apply starter, men venter på manuell godkjenning
  • Godkjenner får en GitHub issue og må godkjenne endringene
  • Tre separate jobs kjører sekvensielt:
    • tofu-apply-main: Oppretter AWS-kontoer og code repos
    • tofu-apply-bootstrap: Setter opp OIDC, roller og S3-bøtter
    • tofu-apply-project-iac-repo: Oppretter IaC-repo med riktige variabler

Hver job krever manuell godkjenning via GitHub Issues før den kjører.

Hvorfor OpenTofu fremfor Terraform?

Et kritisk aspekt av denne løsningen er muligheten til å dynamisk "assume" roller inn i flere AWS-kontoer. Dette krever for_each på provider-nivå:

1provider "aws" {
2 for_each = local.provider_map # Dynamisk basert på antall kontoer
3 alias = "by_account"
4
5 assume_role {
6 role_arn = "arn:aws:iam::${each.key}:role/OrganizationAccountAccessRole"
7 }
8}

Terraform støtter ikke dette, men OpenTofu gjør det. Dette gjør det mulig å bootstrappe et vilkårlig antall AWS-kontoer i samme OpenTofu-kjøring.

Sikkerhet

Løsningen bygger på flere sikkerhetsprinsipper:

OIDC fremfor long-lived credentials: GitHub Actions autentiserer seg mot AWS via OpenID Connect, ingen hemmeligheter lagres

Minste privilegium: Hver IAM-rolle er scopet til ett spesifikt GitHub-repository

Manuell godkjenning: Alle endringer må godkjennes manuelt før apply

Om AdministratorAccess-rollen

Et viktig designvalg er at IaC-repoene får AdministratorAccess i sine respektive AWS-kontoer. Dette er en bevisst avveining mellom sikkerhet og brukbarhet.

Hvorfor AdministratorAccess er akseptabelt:

IaC-roller trenger bred tilgang - Et IaC-repo kan potensielt trenge å opprette hvilke som helst AWS-ressurser (VPCs, RDS, ECS, IAM-roller, Lambda, osv.). Å forutsi nøyaktig hvilke permissions som trengs er vanskelig og vedlikeholdskrevende.

Kontoen er isolert - Hvert prosjekt får egne AWS-kontoer. En kompromittert IaC-rolle påvirker kun det prosjektets ressurser, ikke hele AWS-organisasjonen.

OIDC begrenser angrepsflatene - Rollen kan kun assumes fra ett spesifikt GitHub-repository. Det er ikke en generell admin-credential som kan brukes fra hvor som helst.

Den virkelige sikkerhetsgrensen er GitHub - Tilgang til å endre infrastruktur styres gjennom:

  • GitHub repository permissions (hvem kan committe)
  • Branch protection rules (krever PR-godkjenninger)
  • Code review fra teammedlemmer
  • GitHub Actions approval gates for prod-miljøer

Infrastruktur som kode krever tillit - Personer som skriver infrastrukturkode må uansett ha tillit til å gjøre endringer i produksjon. Det er vanskelig å ha "begrenset IaC".

Alternative tilnærminger:

Hvis du ønsker strengere kontroll, kan du:

  • Service Control Policies (SCPs) på organisasjonsnivå som blokkerer farlige operasjoner (f.eks. slette CloudTrail logs, disable GuardDuty)
  • Custom IAM policies per prosjekt hvis du vet at prosjektet kun trenger spesifikke tjenester
  • GitHub Environment reviewers som krever manuell godkjenning før prod-deployments
  • AWS CloudTrail for å logge alt som gjøres med rollen

Filosofien:

Løsningen bygger på en "high trust"-modell hvor folk som har tilgang til IaC-repoer er betrodde med bred AWS-tilgang. Den reelle sikkerheten ligger i:

  • Hvem får commit-tilgang til IaC-repos
  • Code review og godkjenningsprosesser
  • Audit logging og overvåking av hva som faktisk gjøres

Denne tilnærmingen balanserer sikkerhet med brukbarhet, og unngår kompleksitet som i praksis gir falsk trygghet uten reell sikkerhetsgevinst.

Sletting av prosjekter

Når et prosjekt skal nedlegges, er det noen manuelle steg som må gjøres på grunn av AWS sine restriksjoner:

  • Fjern prosjektet fra config/projects.tfvars
  • Flytt AWS-kontoene til root OU i AWS Organizations Console
  • Lukk kontoene manuelt i AWS Console
  • Re-run feila GitHub Actions job som vil fjerne OU-en
  • Fjern IaC-repoet (manuelt eller via OpenTofu)

Dette er nødvendig fordi AWS krever manuell godkjenning for å lukke kontoer.

Praktisk eksempel: URL-shortener service

La oss se på et konkret eksempel på hvordan dette brukes i praksis. Vi skal lage en URL-shortener service med moderne arkitektur.

Først legger vi til prosjektet i config/projects.tfvars:

1projects = {
2 ...#existing projects
3 "url-shortener" = {
4 environments = ["dev", "stage", "prod"]
5 code_repo_names = ["url-shortener-frontend", "url-shortener-backend"]
6 }
7}

Etter merge og godkjenning av de 3 stegene får vi automatisk:


AWS-kontoer:

  • url-shortener-dev (123456789012)
  • url-shortener-stage (123456789013)
  • url-shortener-prod (123456789014)

GitHub-repositories:

  • iac-url-shortener - IaC-repo med miljøer (dev/stage/prod) og AWS-rolle ARNer ferdigkonfigurert
  • url-shortener-frontend - React-appen
  • url-shortener-backend - API-tjenesten

Og det er alt som trengs 🚀

Resultatet

15 minutter har du:

  • 3 separate AWS-kontoer med full isolasjon
  • 3 GitHub-repos med CI/CD klar til bruk
  • Et Terraform-repo klart til å deploye infrastruktur
  • Ingen manuell konfigurasjon av AWS-tilganger eller secrets

Du kan umiddelbart begynne å jobbe med applikasjonen, og deployments til dev/stage/prod fungerer fra dag én.

Fra å legge til noen linjer i en konfigurasjonsfil til å ha et fullstendig sett opp prosjekt tar nå rundt 10-15 minutter (mesteparten er venting på at AWS provisjonerer kontoer). Tidligere tok dette flere timer med manuelt arbeid.

Hvert nytt prosjekt får:

  • ✅ Dedikerte AWS-kontoer for hvert miljø
  • ✅ Korrekt organisasjonsstruktur i AWS
  • ✅ GitHub-repositories for kode og infrastruktur
  • ✅ Sikker OIDC-autentisering fra GitHub til AWS
  • ✅ S3-bøtter for state management
  • ✅ Ferdigkonfigurerte miljøvariabler i GitHub
  • ✅ En template å starte fra for infrastrukturkode

Alt er klart til å deploye applikasjonen din!

Konklusjon

Ved å bruke OpenTofu (eller Terraform) til å administrere ikke bare applikasjonsinfrastruktur, men også organisasjonsstrukturen og utviklingsverktøyene, kan hele prosessen fra prosjektoppstart til produksjon automatiseres.

Nøkkelen er å tenke på alt som infrastruktur som kode - ikke bare servere og databaser, men også AWS-kontoer, GitHub-repos, og koblingene mellom dem.

OpenTofu sin støtte for dynamisk for_each på providers gjør denne type løsninger mulig på en måte som ikke er like elegant i vanilla Terraform.

Lykke til med automatiseringen! 🚀

Sjekk ut koden på GitHub!

Fant du artikkelen nyttig? Del den gjerne!

Del: