Lab 0: Prerrequisitos

Lab 0: Prerrequisitos

Antes de comenzar, configuraremos el entorno de trabajo, la autenticación en AWS y los permisos necesarios para ejecutar el workshop completo con CloudFormation o Terraform.


¿Dónde correr este workshop?

Opción IaC Entorno recomendado
Opción A: CloudFormation AWS CloudShell ✅ o terminal local ✅
Opción B: Terraform Solo terminal local ⚠️
Detalles

🚫 NO uses AWS CloudShell si elegís la Opción B (Terraform)

AWS CloudShell tiene almacenamiento temporal y efímero. Si la sesión expira, se resetea o cerrás el navegador, los archivos terraform.tfstate se pueden perder permanentemente.

Sin el tfstate, Terraform pierde toda referencia a los recursos ya creados en AWS:

  • terraform plan mostrará que “no hay nada desplegado”
  • terraform destroy no podrá eliminar los recursos — quedarán huérfanos generando costos

Terraform requiere una terminal local con almacenamiento persistente:

  • macOS / Linux: terminal nativa
  • Windows: WSL2

Si ya desplegaste con Terraform desde CloudShell y perdiste el estado, deberás eliminar los recursos manualmente desde la consola de AWS o con AWS CLI antes de poder continuar.


Paso 1: Verificar AWS CLI y autenticación

Ejecutá lo siguiente en tu terminal para verificar tu identidad:

aws sts get-caller-identity

Deberías ver una salida similar a:

{
    "UserId": "AIDA...",
    "Account": "123456789012",
    "Arn": "arn:aws:iam::123456789012:user/tu-usuario"
}

Si el comando falla, asegurate de tener AWS CLI instalado y las credenciales configuradas con aws configure:

Consejo

Agregá export AWS_PAGER="" a tu shell para evitar que los comandos con output largo se detengan esperando input (especialmente útil en CloudShell).


Paso 2: Crear la política IAM para el workshop (Recomendado)

Consejo

Recomendación de seguridad: Se aconseja no usar las credenciales de tu usuario root ni de un usuario con AdministratorAccess. Crear un usuario IAM dedicado con la política de Menor Privilegio (Least Privilege) limita el radio de impacto.

Si ya tenés un usuario con AdministratorAccess y preferís usarlo para el workshop, podés saltar este paso.

Creá la política IAM WorkshopRoute53ARC con AWS CLI:

cat > /tmp/workshop-policy.json << 'POLICY_EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "CloudFormation",
      "Effect": "Allow",
      "Action": [
        "cloudformation:CreateStack", "cloudformation:DeleteStack",
        "cloudformation:DescribeStacks", "cloudformation:DescribeStackEvents",
        "cloudformation:DescribeStackResources", "cloudformation:GetTemplate",
        "cloudformation:UpdateStack", "cloudformation:ValidateTemplate"
      ],
      "Resource": "*"
    },
    {
      "Sid": "EC2",
      "Effect": "Allow",
      "Action": [
        "ec2:Describe*",
        "ec2:CreateVpc", "ec2:DeleteVpc",
        "ec2:CreateSubnet", "ec2:DeleteSubnet",
        "ec2:CreateInternetGateway", "ec2:DeleteInternetGateway",
        "ec2:AttachInternetGateway", "ec2:DetachInternetGateway",
        "ec2:CreateRouteTable", "ec2:DeleteRouteTable",
        "ec2:CreateRoute", "ec2:DeleteRoute",
        "ec2:AssociateRouteTable", "ec2:DisassociateRouteTable",
        "ec2:CreateSecurityGroup", "ec2:DeleteSecurityGroup",
        "ec2:AuthorizeSecurityGroupIngress", "ec2:AuthorizeSecurityGroupEgress",
        "ec2:RevokeSecurityGroupIngress", "ec2:RevokeSecurityGroupEgress",
        "ec2:RunInstances", "ec2:TerminateInstances",
        "ec2:StartInstances", "ec2:StopInstances",
        "ec2:ModifyInstanceAttribute", "ec2:CreateTags", "ec2:DeleteTags"
      ],
      "Resource": "*"
    },
    {
      "Sid": "ELB",
      "Effect": "Allow",
      "Action": [ "elasticloadbalancing:*" ],
      "Resource": "*"
    },
    {
      "Sid": "S3",
      "Effect": "Allow",
      "Action": [
        "s3:Get*", "s3:List*",
        "s3:CreateBucket", "s3:DeleteBucket",
        "s3:PutBucketTagging", "s3:DeleteBucketTagging",
        "s3:PutBucketPolicy", "s3:DeleteBucketPolicy",
        "s3:PutBucketPublicAccessBlock", "s3:PutBucketOwnershipControls",
        "s3:PutObject", "s3:DeleteObject", "s3:DeleteObjects"
      ],
      "Resource": "*"
    },
    {
      "Sid": "CloudFront",
      "Effect": "Allow",
      "Action": [
        "cloudfront:CreateDistribution", "cloudfront:UpdateDistribution",
        "cloudfront:DeleteDistribution", "cloudfront:GetDistribution",
        "cloudfront:GetDistributionConfig", "cloudfront:ListDistributions",
        "cloudfront:CreateOriginAccessControl", "cloudfront:DeleteOriginAccessControl",
        "cloudfront:GetOriginAccessControl", "cloudfront:TagResource"
      ],
      "Resource": "*"
    },
    {
      "Sid": "Amplify",
      "Effect": "Allow",
      "Action": [
        "amplify:CreateApp", "amplify:DeleteApp", "amplify:GetApp",
        "amplify:ListApps", "amplify:UpdateApp",
        "amplify:CreateBranch", "amplify:DeleteBranch", "amplify:GetBranch",
        "amplify:ListBranches", "amplify:UpdateBranch",
        "amplify:CreateDeployment", "amplify:StartDeployment",
        "amplify:GetJob", "amplify:ListJobs",
        "amplify:TagResource", "amplify:UntagResource", "amplify:ListTagsForResource"
      ],
      "Resource": "*"
    },
    {
      "Sid": "Route53",
      "Effect": "Allow",
      "Action": [
        "route53:CreateHostedZone", "route53:DeleteHostedZone",
        "route53:GetHostedZone", "route53:ListHostedZones",
        "route53:ChangeResourceRecordSets", "route53:ListResourceRecordSets",
        "route53:CreateHealthCheck", "route53:DeleteHealthCheck",
        "route53:GetHealthCheck", "route53:ListHealthChecks",
        "route53:UpdateHealthCheck", "route53:GetChange",
        "route53:AssociateVPCWithHostedZone", "route53:DisassociateVPCFromHostedZone"
      ],
      "Resource": "*"
    },
    {
      "Sid": "Route53ARC",
      "Effect": "Allow",
      "Action": [
        "route53-recovery-control-config:*",
        "route53-recovery-cluster:*",
        "route53-recovery-readiness:*"
      ],
      "Resource": "*"
    },
    {
      "Sid": "CloudWatchAndLogs",
      "Effect": "Allow",
      "Action": [
        "cloudwatch:PutMetricAlarm", "cloudwatch:DeleteAlarms",
        "cloudwatch:DescribeAlarms", "cloudwatch:SetAlarmState",
        "cloudwatch:GetMetricData", "cloudwatch:TagResource",
        "logs:CreateLogGroup", "logs:DeleteLogGroup",
        "logs:CreateLogStream", "logs:PutLogEvents",
        "logs:DescribeLogGroups", "logs:FilterLogEvents"
      ],
      "Resource": "*"
    },
    {
      "Sid": "SNS",
      "Effect": "Allow",
      "Action": [
        "sns:CreateTopic", "sns:DeleteTopic",
        "sns:GetTopicAttributes", "sns:SetTopicAttributes",
        "sns:ListTopics", "sns:Subscribe", "sns:Unsubscribe",
        "sns:GetSubscriptionAttributes", "sns:Publish"
      ],
      "Resource": "*"
    },
    {
      "Sid": "Lambda",
      "Effect": "Allow",
      "Action": [
        "lambda:CreateFunction", "lambda:DeleteFunction",
        "lambda:GetFunction", "lambda:GetPolicy",
        "lambda:UpdateFunctionCode", "lambda:UpdateFunctionConfiguration",
        "lambda:InvokeFunction", "lambda:AddPermission", "lambda:RemovePermission",
        "lambda:CreateFunctionUrlConfig", "lambda:GetFunctionUrlConfig",
        "lambda:DeleteFunctionUrlConfig"
      ],
      "Resource": "*"
    },
    {
      "Sid": "IAM",
      "Effect": "Allow",
      "Action": [
        "iam:CreateRole", "iam:DeleteRole", "iam:GetRole", "iam:PassRole",
        "iam:AttachRolePolicy", "iam:DetachRolePolicy",
        "iam:PutRolePolicy", "iam:DeleteRolePolicy", "iam:GetRolePolicy",
        "iam:CreateInstanceProfile", "iam:DeleteInstanceProfile",
        "iam:AddRoleToInstanceProfile", "iam:RemoveRoleFromInstanceProfile",
        "iam:GetInstanceProfile"
      ],
      "Resource": "*"
    },
    {
      "Sid": "SSM",
      "Effect": "Allow",
      "Action": [
        "ssm:SendCommand", "ssm:GetCommandInvocation",
        "ssm:DescribeInstanceInformation", "ssm:CreateDocument",
        "ssm:DeleteDocument", "ssm:DescribeDocument", "ssm:GetDocument",
        "ssm:StartAutomationExecution", "ssm:GetAutomationExecution"
      ],
      "Resource": "*"
    },
    {
      "Sid": "STSReadOnly",
      "Effect": "Allow",
      "Action": [ "sts:GetCallerIdentity" ],
      "Resource": "*"
    }
  ]
}
POLICY_EOF

aws iam create-policy \
  --policy-name WorkshopRoute53ARC \
  --policy-document file:///tmp/workshop-policy.json \
  --description "Politica Least Privilege para el workshop de Route53 ARC"

echo "✅ Política WorkshopRoute53ARC creada"

Adjuntá la política al usuario IAM que vas a usar para el workshop:

CALLER_ARN=$(aws sts get-caller-identity --query 'Arn' --output text)
POLICY_ARN="arn:aws:iam::${AWS_ACCOUNT_ID}:policy/WorkshopRoute53ARC"

# Detectar si es usuario IAM o rol (SSO/federated)
if echo "$CALLER_ARN" | grep -q ":user/"; then
  WORKSHOP_USER=$(echo "$CALLER_ARN" | cut -d'/' -f2)
  aws iam attach-user-policy \
    --user-name $WORKSHOP_USER \
    --policy-arn $POLICY_ARN
  echo "✅ Política adjuntada al usuario IAM: $WORKSHOP_USER"
else
  echo "ℹ️  Detectado rol IAM o usuario SSO (ARN: $CALLER_ARN)"
  echo "   'attach-user-policy' solo aplica a usuarios IAM clásicos."
  echo "   Para roles/SSO, adjuntá la política desde la consola de IAM:"
  echo "   IAM → Users → tu usuario → Add permissions → Attach policies directly → WorkshopRoute53ARC"
  echo "   O adjuntála al Permission Set de IAM Identity Center si usás SSO."
fi

Alternativa vía consola: IAM → Users → tu usuario → Add permissions → Attach policies directly → buscar WorkshopRoute53ARC → Add permissions.


Paso 3: Instalar Terraform CLI (Solo si elegís la Opción B)

Aviso

Terraform solo en terminal local. No instales Terraform en CloudShell — el almacenamiento es efímero y el tfstate se perdería.

macOS

brew tap hashicorp/tap
brew install hashicorp/tap/terraform
terraform version

Linux (Ubuntu / Debian)

wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" \
  | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
terraform version

Windows (WSL2)

Usá los comandos de Linux dentro de tu distribución WSL2.


Paso 4: Crear carpetas de trabajo

# Para Opción A (CloudFormation):
mkdir -p /tmp/route53-arc-cfn

# Para Opción B (Terraform) — en tu terminal local:
mkdir -p ~/route53-arc-tf/{01_primary,02_secondary,03_arc,04_automation}

Paso 5: Detectar VPC y generar global.env

El siguiente script detecta automáticamente la VPC Default y genera el archivo global.env con todas las variables del workshop.

Aviso

Editá NOTIFICATION_EMAIL antes de continuar al Lab 4. Si dejás el valor placeholder, la suscripción SNS nunca se confirmará y las alertas de failover no llegarán.

DEFAULT_VPC_CHECK=$(aws ec2 describe-vpcs \
  --filters "Name=is-default,Values=true" \
  --query "Vpcs[0].VpcId" --output text 2>/dev/null)

if [ "$DEFAULT_VPC_CHECK" != "None" ] && [ -n "$DEFAULT_VPC_CHECK" ]; then
  AUTO_VPC_MODE="default"
  echo "✅ VPC Default detectada: $DEFAULT_VPC_CHECK"
else
  AUTO_VPC_MODE="new"
  echo "ℹ️  No se encontró VPC Default. Se creará VPC nueva automáticamente."
fi

cat > ~/global.env << EOF
export AWS_REGION="us-east-1"
# us-east-1 es OBLIGATORIO — Route 53 ARC solo existe en esta región
export AWS_ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
export VPC_MODE="${AUTO_VPC_MODE}"
export USE_DEFAULT_VPC="$( [ "${AUTO_VPC_MODE}" = "default" ] && echo "true" || echo "false" )"
export AWS_PAGER=""

# Dominio (opcional)
# "" = modo demo sin DNS público (los Routing Controls funcionan igual)
# "tudominio.com" = dominio real con Hosted Zone pública en Route 53
export DOMAIN_NAME=""
export APP_SUBDOMAIN="app"
export HOSTED_ZONE_ID=""

# ⚠️  REEMPLAZÁ este valor con tu email real antes del Lab 4
export NOTIFICATION_EMAIL="tu-email@ejemplo.com"

# Las siguientes variables se populan automáticamente en labs posteriores
# export FALLBACK_DOMAIN=""
# export CLUSTER_ENDPOINT=""
# export ENDPOINT_REGION=""
EOF

source ~/global.env
echo "✅ Variables configuradas → Account: $AWS_ACCOUNT_ID | Region: $AWS_REGION | VPC_MODE: $VPC_MODE"

Reemplazá el email con tu dirección real:

# macOS:
sed -i '' 's/tu-email@ejemplo.com/TU_EMAIL@tudominio.com/' ~/global.env

# Linux / CloudShell:
sed -i 's/tu-email@ejemplo.com/TU_EMAIL@tudominio.com/' ~/global.env

source ~/global.env
echo "✅ Email configurado: $NOTIFICATION_EMAIL"

✅ Verificación del Lab 0

echo "=== Verificación Lab 0: Prerrequisitos ==="

aws sts get-caller-identity --query Account --output text > /dev/null \
  && echo "✅ AWS CLI autenticado" \
  || echo "❌ Error de credenciales AWS CLI"

if command -v terraform &> /dev/null; then
  echo "✅ Terraform instalado: $(terraform version | head -1)"
else
  echo "ℹ️  Terraform no instalado (solo necesario para la Opción B)"
fi

if [ "$NOTIFICATION_EMAIL" = "tu-email@ejemplo.com" ] || [ -z "$NOTIFICATION_EMAIL" ]; then
  echo "⚠️  NOTIFICATION_EMAIL no configurado — editá ~/global.env antes del Lab 4"
else
  echo "✅ NOTIFICATION_EMAIL: $NOTIFICATION_EMAIL"
fi

echo "Account: $AWS_ACCOUNT_ID | Región: $AWS_REGION | VPC_MODE: $VPC_MODE"
echo "=== Lab 0 completado ==="

Siguiente paso → Lab 1: Sitio Primario