Servir contenido con seguridad: CloudFront con OAC y S3

En esta ocasión vamos a ver cómo mejorar la seguridad de un bucket S3 usado por WordPress, integrándolo con CloudFront mediante Origin Access Control (OAC) y ajustando la configuración de Block Public Access. Partimos de una alerta del control [S3.8] que indicaba que el bucket no cumplía con las políticas recomendadas, y aprovechamos para reforzar la arquitectura manteniendo la funcionalidad del plugin de subida de imágenes. Este control está generado por el AWS Foundational Security Best practices.

Este es el estado inicial desde el que partimos:

Contexto

Vamos a hablar un poquito de CloudFront, S3, OAC y mejorar un poquito la seguridad. Lo que se pueda. En el contexto de ir revisando mis controles alertando de posibles mejoras, el capítulo de S3 es uno de los más importantes. En concreto, el titulado “S3 general purpose buckets should block public access” me señalaba que tenía varios buckets donde ese control estaba en estado “Failed”. Y uno de ellos el que uso para servir imágenes desde WordPress. (Para más info creo que por aquí hablaba de ello). Qué dice el control? Dice esto:

“[S3.8] This control checks whether an Amazon S3 general purpose bucket blocks public access at the bucket level. The control fails if any of the following settings are set to false: ignorePublicAcls, blockPublicPolicy, blockPublicAcls, restrictPublicBuckets.

Y nos deja un link a las instrucciones para remediarlo.

security hub control

Qué NO tiene que pasar para que este control no alerte sobre un bucket? Pues que al ir a la consola de AWS y mires los detalles del bucket veas algo así:

s3 block public access

Objetivo

Configurar con Terraform un bucket S3 seguro para servir contenido con CloudFront mediante OAC, y ajustar la configuración para que WordPress pueda subir imágenes sin errores. Voy a detallar los pasos que hice usando el código en Terraform como ejemplo.

1. Configuración de Origin Access Control (OAC)

Creamos un recurso aws_cloudfront_origin_access_control que permite a CloudFront acceder de forma segura al bucket S3 sin necesidad de URLs públicas ni configuración de public-read.

resource "aws_cloudfront_origin_access_control" "oac_s3_static_bucket" {
  name                              = "oac_s3_static"
  description                       = "OAC for s3 static bucket"
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}

2. Política del bucket S3

La política del bucket se actualizó para:

  • Bloquear conexiones que no usen HTTPS (aws:SecureTransport).
  • Permitir acceso de lectura únicamente a CloudFront mediante OAC.
  • Permitir al usuario IAM usado por WordPress subir y gestionar objetos.
resource "aws_s3_bucket_policy" "bucket_s3_static_policy" {
  bucket = aws_s3_bucket.bucket_static.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid       = "RequireSSL"
        Effect    = "Deny"
        Principal = "*"
        Action    = "s3:*"
        Resource  = [
          "arn:aws:s3:::${aws_s3_bucket.bucket_static.id}/*"
        ]
        Condition = {
          Bool = {
            "aws:SecureTransport" = "false"
          }
        }
      },
      {
        Sid    = "AllowCloudFrontServicePrincipalReadOnly"
        Effect = "Allow"
        Principal = {
          Service = "cloudfront.amazonaws.com"
        }
        Action = [
          "s3:GetObject"
        ]
        Resource = [
          "arn:aws:s3:::${aws_s3_bucket.bucket_static.id}/*"
        ]
        Condition = {
          StringEquals = {
            "AWS:SourceArn" = "arn:aws:cloudfront::${data.aws_caller_identity.current.account_id}:distribution/${aws_cloudfront_distribution.my_cloudfront.id}"
          }
        }
      },
      {
        Sid       = "AllowWordPressPluginUploads"
        Effect    = "Allow"
        Principal = {
          AWS = "arn:aws:iam::0123456789:user/users/my_awsome_user"
        }
        Action    = [
          "s3:PutObject",
          "s3:GetObject",
          "s3:ListBucket",
          "s3:DeleteObject",
          "s3:GetBucketLocation",
          "s3:PutObjectAcl"
        ]
        Resource  = [
          "arn:aws:s3:::bucket-static",
          "arn:aws:s3:::bucket-static/*"
        ]
        Condition = {
          Bool = { "aws:SecureTransport" = "true" }
        }
      }
    ]
  })
}

3. Bloqueo de acceso público al bucket

resource "aws_s3_bucket_public_access_block" "s3_static_block" {
  bucket = aws_s3_bucket.my_bucket.id

  block_public_acls       = false
  ignore_public_acls      = true
  block_public_policy     = true
  restrict_public_buckets = true
}

Resumen

Este es el resultado final:

Debido a ciertas limitaciones en la configuración de WordPress y en el funcionamiento del plugin usado para offload de medios a S3, no ha sido posible aplicar al 100 % las medidas de bloqueo público recomendadas por AWS. Aunque se ha reforzado la seguridad mediante el uso de CloudFront con Origin Access Control (OAC) y una bucket policy restrictiva, hemos tenido que mantener el parámetro block_public_acls = false para asegurar la compatibilidad y el correcto funcionamiento de las subidas desde WordPress.

  • El bucket sigue protegido contra accesos no autorizados.
  • CloudFront es la única vía de acceso público
  • WP puede subir y gestionar medios sin errores.

Bueno, y por último marcaremos el finding como “Supressed” porque en nuestro caso y por lo antes mencionado, no podemos dejarlo totalmente arreglado como quiere el control.

cloudfron oac security hub control

Tardará 24 h en actualizarlo.

Recursos

Leave a Reply

Your email address will not be published. Required fields are marked *