Hablemos de S3 – parte 2

Hello word 🙂

En el artículo anterior sobre Amazon S3, exploramos de manera teórica muchas de las funcionalidades que ofrece S3. Vimos ejemplos de casos de uso y analizamos por qué S3 es una pieza fundamental en tantas arquitecturas basadas en AWS.

En esta nueva entrada, vamos a dar un paso más y pasar a la práctica. Crearemos 3 bucket desde cero y configuraremos algunas de sus funcionalidades más destacadas, como:

  • Reglas de ciclo de vida (Lifecycle Rules)

  • Reglas de replicación (Replication Rules)

  • Versionado de objetos

  • Server Access Logging

  • Event notifications usando un tema de SNS

Todo esto lo haremos utilizando Terraform, lo que nos permitirá replicar fácilmente el ejemplo en diferentes entornos, en esta demo vamos a crear un total de tres buckets:

  1. source – Será el bucket principal, donde habilitaremos las funciones destacadas anteriormente.

  2. destination – Aquí se replicará automáticamente todo el contenido del bucket source.
  3. log_bucket – Este bucket almacenará los logs de acceso del bucket source.

Requisitos

  • Tener una cuenta de AWS.
  • Tener instalado Terraform.

El proyecto estará organizado en una única carpeta, dentro de la cual definiremos todos los archivos de Terraform necesarios para crear los recursos.

1- Primero necesitamos exportar nuestras credenciales de AWS en nuestra consola local;

    • export AWS_ACCESS_KEY_ID="XXXXXXXXXX"
      export AWS_SECRET_ACCESS_KEY="XXXXXXXXXX"
    • Comprobamos que estemos conectados de forma correcta
      •  aws sts get-caller-identity

2- En el archivo 0-provider.tf, definiremos los proveedores que utilizaremos en este proyecto.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
    }
  }
}
provider "aws" {
  region = local.region
  default_tags {
    tags = local.tags
  }
}

3- En el archivo 01-locals.tf, definiremos las variables locales del proyecto. Estas variables ayudan a centralizar valores que se utilizarán en múltiples recursos, simplificando la configuración y facilitando el mantenimiento del código.

locals {
  region               = var.region
  bucket_source        = "${var.environment}-${var.region}-${var.project_name}-source"
  bucket_log           = "${var.environment}-${var.region}-${var.project_name}-log"
  bucket_destination   = "${var.environment}-${var.region}-${var.project_name}-destination"
  tags            = var.tags
  common_tags = {
    Environment = var.environment
    Terraform   = "true"
  }
}
4- En el archivo 02-s3.tf, definiremos la creación y configuración completa de los buckets S3, habilitando el versionado, las reglas de ciclo de vida, la replicación de objetos, el logging y las notificaciones mediante un tema de SNS. Con esto garantizamos un bucket seguro.
resource "aws_s3_bucket" "source_bucket" {
  bucket = local.bucket_source
  tags = local.tags
}
resource "aws_s3_bucket" "destination_bucket" {
  bucket = local.bucket_destination
  tags = local.tags
}
resource "aws_s3_bucket" "log_bucket" {
  bucket = local.bucket_log
  tags = local.tags
}
resource "aws_s3_bucket_versioning" "source_bucket_versioning" {
  bucket = aws_s3_bucket.source_bucket.id
  versioning_configuration {
    status = "Enabled"
  }
}
resource "aws_s3_bucket_versioning" "destination_bucket_versioning" {
  bucket = aws_s3_bucket.destination_bucket.id
  versioning_configuration {
    status = "Enabled"
  }
}
resource "aws_s3_bucket_logging" "bucket_logging" {
  bucket = aws_s3_bucket.source_bucket.id
  target_bucket = aws_s3_bucket.log_bucket.id
  target_prefix = "log/"
}
resource "aws_s3_bucket_policy" "log_bucket_policy" {
  bucket = aws_s3_bucket.log_bucket.id
  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Sid       = "AllowS3Logging",
        Effect    = "Allow",
        Principal = {
          Service = "logging.s3.amazonaws.com"
        },
        Action = [
          "s3:PutObject"
        ],
        Resource = "${aws_s3_bucket.log_bucket.arn}/log/*" # El Resource debe coincidir con el target_prefix que definiste en aws_s3_bucket_logging (ej. "log/*").
      }
    ]
  })
}
resource "aws_s3_bucket_lifecycle_configuration" "source_lifecycle" {
  bucket = aws_s3_bucket.source_bucket.id
  rule {
    id = "rule-1"
    filter {
      prefix = ""
    }
    status = "Enabled"
    transition {
      days          = 30
      storage_class = "STANDARD_IA"
    }
    expiration {
      days = 365
    }
  }
}
resource "aws_s3_bucket_notification" "source_notification" {
  bucket = aws_s3_bucket.source_bucket.id
  depends_on = [aws_sns_topic_policy.allow_s3_publish]
  topic {
    topic_arn     = aws_sns_topic.topic_sns_s3.arn
    events        = ["s3:ObjectCreated:*", "s3:ObjectRemoved:*", "s3:Replication:*", "s3:ObjectRestore:*", "s3:LifecycleExpiration:*"]
    filter_suffix = ""
  }
}
resource "aws_s3_bucket_replication_configuration" "source_replication" {
  depends_on = [aws_s3_bucket_versioning.source_bucket_versioning]
  role   = aws_iam_role.replication.arn
  bucket = aws_s3_bucket.source_bucket.id
  rule {
    id = "examplerule"
    status = "Enabled"
    delete_marker_replication {
      status = "Disabled"
    }
    filter {
      prefix = "" # Si querés que se repliquen todos los objetos, simplemente usá un filtro vacío
    }
   
    destination {
      bucket        = aws_s3_bucket.destination_bucket.arn
      storage_class = "STANDARD"
    }
  }
}

5- En el archivo 03-iam.tf, definiremos todos los elementos relacionados con la gestión de identidad y acceso (IAM) en el proyecto. Esto incluirá la creación de roles específicos, la definición de políticas de permisos, y la asociación de estas políticas a los roles correspondientes.

resource "aws_iam_role" "replication" {
  name               = "tf-iam-role-replication-12345"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
resource "aws_iam_policy" "replication" {
  name   = "tf-iam-role-policy-replication-12345"
  policy = data.aws_iam_policy_document.replication.json
}
resource "aws_iam_role_policy_attachment" "replication" {
  role       = aws_iam_role.replication.name
  policy_arn = aws_iam_policy.replication.arn
}

6- En el archivo 04-sns.tf, definiremos todo lo necesario para crear el tema de SNS, que será utilizado por el bucket source para enviar notificaciones sobre los eventos que ocurran dentro de él. De esta forma, podremos monitorear y reaccionar fácilmente ante cualquier cambio o actividad en el bucket.

# Aqui estamos creado el topic SNS
resource "aws_sns_topic" "topic_sns_s3" {
  name = "topic-sns-s3"
  display_name = "Test From SNS"
}
resource "aws_sns_topic_subscription" "user_updates_mail_target" {
  topic_arn = aws_sns_topic.topic_sns_s3.arn
  protocol  = "email"
  endpoint  = "tucorreo@gmail.com"
}
resource "aws_sns_topic_policy" "allow_s3_publish" {
  arn    = aws_sns_topic.topic_sns_s3.arn
  policy = data.aws_iam_policy_document.topic_policy.json
}

7- En el archivo 05-variables.tf, definiremos todas las variables que necesitaremos para parametrizar el proyecto. Estas variables permitirán una mayor flexibilidad y reutilización del código, facilitando la personalización de los recursos y haciendo que el despliegue sea más adaptable a diferentes entornos y requisitos específicos.

variable "region" {
  description = "Region"
  type        = string
  default     = ""
}
variable "environment" {
  description = "Environment Name"
  type        = string
  default     = "Dev"
}
variable "project_name" {
  type        = string
  description = "Name of the project, will be used as prefix in resources names"
}
variable "tags" {
  description = "A map of tags that get added to all resources"
  type        = map(string)
  default     = {}
}

8- En el archivo 06-demo-eks.auto.tfvars, definiremos los valores de las variables según el entorno en el que estemos trabajando, lo que nos permite adaptar fácilmente la configuración del proyecto a distintos ambientes como desarrollo, pruebas o producción.

region        = "us-west-1"
environment   = "poc"
project_name  = "s3-demo"
tags = {
  Country  = "UY"
  Region   = "AMERICA"
}

10- En el archivo 07-output.tf, definiremos las salidas que Terraform nos mostrará tras ejecutar terraform apply. Estas salidas incluirán detalles de los recursos creados, que nos ayudarán a verificar rápidamente el estado del despliegue y a acceder fácilmente a los recursos configurados en el proyecto

output "source_bucket_id" {
  description = "ID of the source S3 bucket"
  value       = aws_s3_bucket.source_bucket.bucket
}
output "destination_bucket_id" {
  description = "ID of the destination S3 bucket"
  value       = aws_s3_bucket.destination_bucket.id
}
output "log_bucket_id" {
  description = "ID of the log S3 bucket"
  value       = aws_s3_bucket.log_bucket.id
}

11- En el archivo 08-data.tf, definiremos todos los data sources utilizados en el proyecto, los cuales nos permiten obtener información existente de AWS para integrarla dinámicamente en la creación de nuestros recursos.

data "aws_iam_policy_document" "assume_role" {
  statement {
    effect = "Allow"
    principals {
      type        = "Service"
      identifiers = ["s3.amazonaws.com"]
    }
    actions = ["sts:AssumeRole"]
  }
}
data "aws_iam_policy_document" "replication" {
  statement {
    effect = "Allow"
    actions = [
      "s3:GetReplicationConfiguration",
      "s3:ListBucket"
    ]
    resources = [
      aws_s3_bucket.source_bucket.arn
    ]
  }
  statement {
    effect = "Allow"
    actions = [
      "s3:GetObjectVersion",
      "s3:GetObjectVersionAcl",
      "s3:GetObjectVersionTagging",
      "s3:GetObjectLegalHold",
      "s3:GetObjectRetention",
      "s3:GetObject",
      "s3:GetObjectVersionForReplication"
    ]
    resources = [
      "${aws_s3_bucket.source_bucket.arn}/*"
    ]
  }
  statement {
    effect = "Allow"
    actions = [
      "s3:ReplicateObject",
      "s3:ReplicateDelete",
      "s3:ReplicateTags",
      "s3:PutObject"
    ]
    resources = [
      "${aws_s3_bucket.destination_bucket.arn}/*"
    ]
  }
}
data "aws_iam_policy_document" "topic_policy" {
  statement {
    effect = "Allow"
    principals {
      type        = "Service"
      identifiers = ["s3.amazonaws.com"]
    }
    actions   = ["SNS:Publish"]
    resources = [aws_sns_topic.topic_sns_s3.arn]
    condition {
      test     = "ArnLike"
      variable = "aws:SourceArn"
      values   = [aws_s3_bucket.source_bucket.arn]
    }
  }
}

Con todos los archivos configurados, ya estamos listos para ejecutar Terraform y desplegar la infraestructura.

  • terraform init
  • terraform plan
  • terraform apply

A continuación veremos algunas de las funcionalidades habilitadas y recursos creados.

  • Aquí podemos ver los 3 bucket creados.
  • Versionado
  • Notificación de eventos.
  • Reglas de ciclo de vida y replicación.
  • Bucket que configuramos para guardar los logs de S3.
  • Tema SNS generado.
  • Ejemplo de los correos recibidos al crear o eliminar objetos en el bucket, lo que demuestra cómo las notificaciones de S3 pueden alertarnos de forma inmediata sobre cualquier cambio realizado en el almacenamiento.
  • Evidencia de cómo se almacenan los registros de acceso en el bucket que configuramos para guardar los logs de S3, permitiéndonos auditar y monitorear todas las operaciones realizadas sobre el bucket principal.

Terraform destroy

  • Como siempre, al finalizar tus pruebas, recuerda eliminar todos los recursos creados ejecutando terraform destroy --auto-approve. Este paso es crucial para evitar sorpresas desagradables en tu factura a final del mes. No subestimes la importancia de esta tarea, ya que la acumulación de recursos no utilizados puede generar costos innecesarios. Tómate el tiempo para limpiar tu entorno, eso te ahorrará dolores de cabeza en el futuro 🙂

Conclusiones

En esta demo pudimos ver algunas de las potentes funcionalidades que ofrece Amazon S3, demostrando su gran flexibilidad y versatilidad dentro del ecosistema de AWS. Vimos cómo es posible replicar la información de un bucket principal hacia otro y recibir notificaciones automáticas de los eventos que ocurren en el bucket de origen, lo que evidencia el enorme potencial de S3 para integrarse en arquitecturas complejas y automatizadas.

Una vez más, Terraform nos permitió implementar todos estos recursos de forma rápida, ordenada y fácilmente replicable en distintos entornos.
Como recordatorio importante, el nombre de cada bucket debe ser único a nivel global en AWS, no puede existir otro bucket con el mismo nombre en ninguna región, ni cuenta de AWS.

¡Nos vemos en la próxima entrada!
Nota: Todo el código lo pueden encontrar en el siguiente repositorio.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.