Monitoreo de ACL y Policies en S3 con AWS Config + Lambda

portada

Objetivos

Utilizar el servicio AWS Config para monitorear y securizar las políticas y ACLs de nuestros Buckets S3, así como también la incorporación de acciones de remediación de manera automática utilizando Lambda y CloudWatch Events.

Pre-requisitos:

  • Conocimiento básico de Simple Storage Service (S3)
  • Conocimiento básico de Lambda Functions
  • Conocimiento básico de Cloudwatch
  • Conocimiento básico de IAM
  • Acceso a la consola de una cuenta AWS
  • Set de Keys para acceso programático a una cuenta de AWS
  • Buen conocimiento del Billing de AWS (para evitar sorpresas en dólares)

Intro

Gracias a un servicio como AWS Config, podemos tener control de todas las configuraciones de nuestros recursos levantados en AWS, al tener registro de todos los cambios en las configuraciones, podemos levantar una serie de reglas que reflejen un estado deseable acerca de la configuración de un servicio en particular. Todo esto llevado a grandes organizaciones que cuentan con decenas de cuentas quizás sobre algún esquema Organizations puede resultar muy útil a la hora de auditar las configuraciones de manera masiva de recursos o hacer evaluaciones de Compliance de cada una de las cuentas… Si es GENIAL.

ARQ

arq

Básicamente vamos a levantar una configuración en AWS Config para que el servicio se ponga a chequear constantemente nuestros Buckets S3, en busca de algún cambio que vaya en contra de las reglas de seguridad pre-definidas (en este caso, nadie puede configurar un Bucket con acceso público, sea READ o WRITE). Cuando AWS Config detecte alguna violación a las políticas que le definimos, mediante Cloudwatch Events vamos a triggerear una función Lambda en Python que sencillamente volverá a setear la Policy del Bucket a su estado inicial. Opcionalmente (y totalmente recomendado) se puede configurar una notificación vía SNS para informar al Team o a quien sea, cada vez que ocurra un cambio en las configuraciones de seguridad de nuestro Bucket.

Habilitamos y Configuramos AWS Config

Vamos a “PRENDER” AWS Config seteando dos Rules, una para READ y otra para WRITE, si te manejas con la consola de AWS simplemente hay que seleccionar estas dos reglas que vienen por default, y lo podes hacer filtrando por el parámetro S3 y luego seleccionando ambas, de la siguiente manera:

rules

En este caso, nosotros vamos a levantar las reglas con la CLI de AWS, cosa de practicar un poco de comandos.

jaquer

Nos conectamos a nuestra cuenta con el set de keys y si es la primera vez que usamos AWS Config en la Región en donde estamos trabajando las salidas de los comandos:

aws configservice --region TU-REGION get-status
aws configservice describe-config-rules    

deberían ser las siguientes:

status

Esto nos dice que el servicio AWS Config aún no esta escuchando ninguna configuración, y que no existen reglas configuradas.

Entonces, en primer lugar vamos a ejecutar el siguiente comando:

aws configservice put-config-rule --generate-cli-skeleton > putConfigRule.json

skeleton

Esto nos va a generar un file en formato JSON con la estructura básica para crear una Config Rule (todo servido), solo hay que reemplazar los valores que nos interesen, y poner un nombre a la Rule, así de sencillo:

Utilizaremos los siguientes valores para los permisos de tipo READ en nuestros Buckets:

{
    "ConfigRule": {
    "ConfigRuleName": "s3-bucket-public-read-prohibited",
    "Description": "Con esta regla vamos a checkear nuestros buckets S3 ",
    "Scope": {
        "ComplianceResourceTypes": [
            "AWS::S3::Bucket"
        ]
    },
    "Source": {
        "Owner": "AWS",
        "SourceIdentifier": "S3_BUCKET_PUBLIC_READ_PROHIBITED"
    },
        "InputParameters": "{}",
        "MaximumExecutionFrequency": "TwentyFour_Hours",
        "ConfigRuleState": "ACTIVE"
    },
    "Tags": [
    {
        "Key": "Owner",
        "Value": "mymware-blog"
    }
    ]
}

Ahora que ya tenemos listo nuestro “skeleton” lo usamos para levantar la ConfigRule de la siguiente manera:

aws configservice put-config-rule --cli-input-json file://putConfigRule.json

Y tiramos el describe para verificar que levantó la regla:

configRead

Como vemos, el describe también nos arroja a modo informativo el ARN y el ID de la ConfigRule.

Hacemos exactamente lo mismo para los permisos de tipo WRITE y verificamos que tenemos ambas reglas levantadas:

configWrite

Creamos un Role para Lambda

Como vimos en la arquitectura más arriba, la idea es que una función Lambda tenga la capacidad y los PERMISOS necesarios para realizar cambios en Policies y ACLs de Buckets S3, entonces, hace falta un ROLE para permitir este tipo de acciones.

Vamos a IAM y primero creamos la Policy con el siguiente contenido:

{
    "Version": "2012-10-17",
    "Statement": [
    {
        "Sid": "S3GetBucketACLandPolicy",
        "Effect": "Allow",
        "Action": [
            "s3:GetBucketAcl",
            "s3:GetBucketPolicy"
        ],
        "Resource": "*"
    },
    {
        "Sid": "S3PutBucketACLAccess",
        "Effect": "Allow",
        "Action": "s3:PutBucketAcl",
        "Resource": "arn:aws:s3:::*"
    },
    {
        "Sid": "LambdaBasicExecutionAccess",
        "Effect": "Allow",
        "Action": [
            "logs:CreateLogGroup",
            "logs:CreateLogStream",
            "logs:PutLogEvents"
        ],
        "Resource": "*"
    }
    ]
}

En el caso que opten por el envió de notificaciones, Lambda también debe ser poder capaz de crear SNS Topics por lo tanto, van a tener que agregar también esto:

    {
        "Sid": "SNSPublish",
        "Effect": "Allow",
        "Action": [
            "sns:Publish"
        ],
        "Resource": "*"
    }

Listo, ahora solo hay que crear el Role para Lambda y asignar la Policy que acabamos de crear.

Creamos la Funcion Lambda

Primero vamos a levantar la función Lambda, ya que si queremos levantar la regla de CloudWatch Event primero, este nos va a pedir el ARN/Nombre del Target (en este caso nuestra función)

Creamos una Función Lambda from stratch usando el runtime Python 3.6 y asignamos como Execution Role el role que creamos previamente

El código es el siguiente:

import boto3
from botocore.exceptions import ClientError
import json
import os

ACL_RD_WARNING = "The S3 bucket ACL allows public read access."
PLCY_RD_WARNING = "The S3 bucket policy allows public read access."
ACL_WRT_WARNING = "The S3 bucket ACL allows public write access."
PLCY_WRT_WARNING = "The S3 bucket policy allows public write access."
RD_COMBO_WARNING = ACL_RD_WARNING + PLCY_RD_WARNING
WRT_COMBO_WARNING = ACL_WRT_WARNING + PLCY_WRT_WARNING

def lambda_handler(event, context):
    # instantiate Amazon S3 client
    s3 = boto3.client('s3')
    resource = list(event['detail']['requestParameters']['evaluations'])[0]
    bucketName = resource['complianceResourceId']
    complianceFailure = event['detail']['requestParameters']['evaluations'][0]['annotation']
    if(complianceFailure == ACL_RD_WARNING or complianceFailure == ACL_WRT_WARNING):
    s3.put_bucket_acl(Bucket = bucketName, ACL = 'private')
    elif(complianceFailure == PLCY_RD_WARNING or complianceFailure == PLCY_WRT_WARNING):
    policyNotifier(bucketName, s3)
    elif(complianceFailure == RD_COMBO_WARNING or complianceFailure == WRT_COMBO_WARNING):
    s3.put_bucket_acl(Bucket = bucketName, ACL = 'private')
    policyNotifier(bucketName, s3)
    return 0  # done

Bien, antes de crear nuestro Trigger para esta función, vamos a CloudWatch (ya que el trigger nos va a pedir la Rule de CloudWatch).

Creamos una Custom Rule en CloudWatch Events

En CloudWatch Events creamos una custom rule con el siguiente contenido:

{
  "source": [
    "aws.config"
  ],
  "detail": {
    "requestParameters": {
      "evaluations": {
    "complianceType": [
      "NON_COMPLIANT"
    ]
      }
    },
    "additionalEventData": {
      "managedRuleIdentifier": [
    "S3_BUCKET_PUBLIC_READ_PROHIBITED",
    "S3_BUCKET_PUBLIC_WRITE_PROHIBITED"
      ]
    }

Y como Target el nombre de nuestra función Lambda:

target

Agregamos el Trigger

Finalmente vamos Lambda nuevamente y añadimos la Rule de CloudWatch Event que acabamos de crear, como Trigger de nuestra función.

trigger

lambdaEsquema

Listo, con este paso básicamente “cerramos el círculo” en nuestra arquitectura, cuando AWS Config verifique que los buckets no cumplen con la política, esto va a desencadenar un evento de CloudWatch que a su vez va a triggerear la función Lambda, la cual tendrá los permisos necesarios (role) para modificar la configuración del servicio S3.

Verificamos, a ver si anda todo esto…

Bien, tenemos 2 reglas levantadas en AWS Config, vamos a hacer una prueba simple, tomemos un Bucket y habilitemos el acceso público para que con el endpoint se puedan listar los objetos en su interior, esto debería saltar por la Rule de READ (en teoría), ahora, esperamos unos minutos (a veces son varios minutos jaja) vamos al dashboard de Config y efectivamente vemos que se activó y alertó en el dashboard una NO-CONFORMIDAD de las políticas que definimos:

noConforme

Si hacemos clic podremos ver el nombre del bucket que disparó esa no conformidad, y aún más detalles si desean seguir investigando:

detalle

En el momento en el que Config detecta el no cumplimiento de la regla, se produce el evento vía CloudWatch y el trigger de la función Lambda, que inicia las acciones de remediación de esta mala configuración de seguridad del Bucket, así que esperamos de nuevo unos minutos y refrescamos el dashboard de Config para verificar que nuestra función funciona, también podemos ver en CloudWatch las invocaciones que tuvo la función.

final

Perfecto, ya tenemos nuestro Config seteado y escuchando nuestros Buckets en materia de seguridad, con acciones de remediación automáticas incluidas, recuerden que una de las buenas prácticas de seguridad en S3, es justamente, no dejar ningún Bucket ni sus contenidos con acceso público, siempre considerando que forme parte de la política de compliance de la compañía no exponer algo en S3 de manera intencional obviamente, una clara excepción sería si queremos servir contenido a través de S3 (una web estática por ej), en fin, esto ya depende de cada compañía y de todas maneras pueden adaptar las ConfigRules a cada caso de uso .

Fuentes

Guía Oficial

Buenas Practicas de Seguridad en S3

AWS Config

Invitame un CaféInvitame un Café