Egyszer töröltem S3 bucketet, és azóta is bánom. 2022-ben szedtem szét egy staging környezetet, és pár órán belül valaki más már be is húzta ugyanazt a bucket nevet. Egy másik accountban futó CloudFormation stack pedig teljes nyugalommal kezdett logokat írni egy bucketbe, ami már nem az enyém volt. Nem az a péntek volt, amire szívesen emlékszem vissza.

Az AWS most végre kiadta a rendes megoldást: account regional namespace az S3 general purpose bucketekhez. Kb. hét év kellett hozzá, ami egyszerre vicces és kicsit fárasztó.

A Probléma Röviden

Az S3 bucket nevek globálisan egyediek az összes AWS account között. Ha törölsz egy bucketet, utána bárki lefoglalhatja ugyanazt a nevet. Ha kiszámítható elnevezést használsz, például myapp-us-east-1, valaki más hamarabb megszerezheti, mint te egy új régióban.

Ez a bucketsquatting. Addig hangzik elméletinek, amíg egyszer csak a saját környezetedben nem találkozol vele.

Láttam már Terraform state fájlokat rossz bucketbe landolni, mert valaki újrahasználta a workspace-t. Láttam CI pipeline-okat, amik egy eltérített bucket névbe tolták az artifactokat. Olyat is láttam, hogy egy CloudFormation template-be beleégett régiónév nyitott meg egy teljesen felesleges támadási felületet.

Az Új Namespace Minta

A megoldás egy új névminta, ami az accountodhoz köti a bucketet:

<prefix>-<account-id>-<region>-an

Például:

myapp-123456789012-us-west-2-an

Az -an suffix az “account namespace” rövidítése. Ha egy másik account megpróbál olyan bucketet létrehozni, ami beleesik a te account namespace mintádba, az AWS InvalidBucketNamespace hibával visszadobja. Egyszerű, hatékony, és őszintén szólva már régen itt lett volna a helye.

Namespaced Bucket Létrehozása CLI-ből

aws s3api create-bucket \
  --bucket myapp-123456789012-us-east-1-an \
  --bucket-namespace account-regional \
  --region us-east-1

Ha nem us-east-1 a régió, kell a location constraint is:

aws s3api create-bucket \
  --bucket myapp-123456789012-eu-west-1-an \
  --bucket-namespace account-regional \
  --region eu-west-1 \
  --create-bucket-configuration LocationConstraint=eu-west-1

Terraform Példa

Ha Terraformmal kezeled az S3 bucketeket, ezt érdemes alapértelmezett mintává tenni:

data "aws_caller_identity" "current" {}
data "aws_region" "current" {}

resource "aws_s3_bucket" "artifacts" {
  bucket           = "artifacts-${data.aws_caller_identity.current.account_id}-${data.aws_region.current.name}-an"
  bucket_namespace = "account-regional"
}

Én modult csinálnék belőle, hogy az org minden csapata ugyanazt a mintát használja:

module "s3_bucket" {
  source = "./modules/s3-namespaced"
  prefix = "artifacts"
}

A modul elintézi a névképzést. Így kisebb az esélye, hogy valaki lehagy valamit vagy kitalál egy saját verziót.

Kikényszerítés az Org-ban SCP-vel

Az AWS hozzáadott egy új condition key-t is, s3:x-amz-bucket-namespace néven, amit Service Control Policy-kben lehet használni. Én ezt tenném rá minél előbb:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "RequireAccountNamespace",
      "Effect": "Deny",
      "Action": "s3:CreateBucket",
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "s3:x-amz-bucket-namespace": "account-regional"
        }
      }
    }
  ]
}

Ez megakadályozza, hogy bárki az orgban namespace nélkül hozzon létre új bucketet. A meglévő bucketeket nem bántja, de innentől kezdve nem lehet véletlenül kikerülni a védelmet.

CloudFormation Migráció

CloudFormation-nél kicsi a változás:

Resources:
  ArtifactsBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub "artifacts-${AWS::AccountId}-${AWS::Region}-an"
      BucketNamespace: "account-regional"

Az AWS::AccountId és AWS::Region pseudo paraméterek szépen tisztán tartják a template-et. Nincs hardkódolt érték, nincs felesleges barkácsolás.

A Migráció Valósága

Ez nem javítja meg varázsütésre a meglévő bucketeket. Ha van egy myapp-us-east-1 nevű bucketed, ami három éve fut, az továbbra is a globális namespace-ben marad, és ugyanúgy kockázatot jelent, ha egyszer törlöd.

Az átállás útvonala egyszerű, csak nem túl izgalmas:

  1. Hozd létre az új namespaced bucketet
  2. Szinkronizáld az adatokat aws s3 sync-kel
  3. Frissíts minden referenciát (Terraform state, alkalmazás configok, CI pipeline-ok)
  4. Tartsd meg a régi bucketet egy darabig, hogy elkapd, amit kihagytál
  5. Végül töröld a régit

Ezzel nem rohannék rá vakon minden bucketre. A stabil, régóta élő produkciós bucketeknél alacsony a kockázat, amíg nem törlöd őket. Először az efemer környezetekhez tartozó bucketeket venném elő, mert ott reális, hogy újra létrejönnek, eltűnnek, és valaki lecsap rájuk.

Mi a Helyzet GCP-vel és Azure-ral?

A Google Cloud Storage domain-alapú namespace-t használ, ami részben megoldja a problémát. Az Azure Blob Storage a konténereket storage accountok alá szervezi, így a globális egyediség probléma sokkal kisebb.

Az AWS volt az utolsó nagy szereplő, aki rendesen hozzányúlt ehhez. Későn, de legalább végre megtörtént.

Összegzés

Ha most hozol létre új S3 bucketet, szerintem nincs is miről gondolkodni: használd ezt a namespace mintát. Frissítsd a Terraform modulokat, a CloudFormation template-eket és a CDK konstrukciókat is. Az SCP-t pedig érdemes bevezetni az orgban, hogy ez ne csak ajánlás legyen.

A meglévő bucketeknél először listázd ki az efemer környezetekhez tartozókat, és azokat migráld leghamarabb. A többi ráér, de attól még kerüljön fel a backlogra, különben könnyen ott marad örökre.

Hét évet kellett várni erre a javításra, de legalább maga a megoldás tiszta, érthető és használható.