aws, ai, security, serverless

Servicio de moderación de contenido impulsado por IA sin servidor

2024-10-31
This post cover image
aws cloud serverless ai security

Este archivo ha sido traducido automaticamente por IA, pueden ocurrir errores

Hace aproximadamente un año creé una publicación sobre la creación de un servicio de Administrador de archivos. En esta publicación usaremos este servicio como base y lo extenderemos con moderación de contenido. Usaremos GuardDuty y Rekognition para ayudar en esta tarea. Como siempre, todo será sin servidor y basado en eventos.

Resumen

Para refrescar la memoria de todos, comencemos con un breve resumen.

El servicio almacenará archivos en S3 y mantendrá un registro de todos los archivos en una tabla de DynamoDB. La visión general del sistema se ve así, con una API que expone funcionalidad al usuario y luego ejecuta el trabajo de manera sin servidor y basada en eventos.

Visión general de la arquitectura para el administrador de archivos original

El flujo de carga se inicia cuando un cliente llama a la API, donde una función Lambda creará una URL pre-firmada de S3 que el cliente puede usar para cargar el archivo. No subimos el archivo directamente a través de la API, ya que Amazon API Gateway tiene un tamaño máximo de carga de 10 mb, y para admitir todo tipo de archivos esto se convertirá en una limitación.

Cuando el cliente luego carga el archivo a S3, esto generará un evento, la parte debajo de la línea punteada en la imagen, lo que activará una StepFunction que actualizará el inventario de archivos.

Flujo de carga para el administrador de archivos original

Arquitectura extendida

En la arquitectura extendida agregamos funcionalidad para usar el escaneo de malware de GuardDuty S3 y Rekognition para la moderación de imágenes. GuardDuty escaneará los nuevos archivos que lleguen al bucket de S3, que llamo staging, se agregará una etiqueta al objeto y el resultado del escaneo se publicará en el event-bus predeterminado. El resultado del escaneo, si está OK, activará una StepFunction que utilizará Rekognition para la moderación de imágenes. He implementado la misma lógica en esta StepFunction y agrego una etiqueta en el objeto y publico un evento en un event-bus. Finalmente, los archivos se mueven a un bucket de almacenamiento de cuarentena u otro.

Cada parte de la solución está desacoplada y puede ejecutarse independientemente, y se aplica un patrón saga para mover la lógica a la siguiente fase.

Visión general extendida con moderación de contenido

Ahora profundicemos un poco más en cada una de las partes de esta solución.

Escaneo de malware

El escaneo de malware de GuardDuty no requiere mucha configuración. Esta es una característica completamente administrada en GuardDuty y lo único que se requiere es configurarla. GuardDuty luego recogerá automáticamente los nuevos objetos.

Flujo de escaneo de malware

Para lograr este flujo, lo único que necesitamos hacer es crear un S3MalwareProtectionPlan y asignarle los permisos apropiados. Una cosa importante a recordar, si cifras tus objetos en S3 con una Clave administrada por el cliente, no olvides darle permisos a GuardDuty para descifrar usando esta clave.

  S3MalwareProtectionPlan:
    Type: AWS::GuardDuty::MalwareProtectionPlan
    Properties:
      Actions:
        Tagging:
          Status: ENABLED
      ProtectedResource:
        S3Bucket:
          BucketName:
            Fn::ImportValue: !Sub ${CommonInfraStackName}:staging-bucket-name
      Role: !GetAtt S3MalwareProtectionPlanRole.Arn

Moderación de imágenes

La parte de moderación con Rekognition implica un par de pasos más. Una StepFunction es activada por el resultado del escaneo de malware, y llama a Rekognition para moderar la imagen. Esta StepFunction luego etiquetará el objeto y publicará el resultado del escaneo en el event-bus personalizado de EventBridge. Una cosa importante a recordar, si cifras tus objetos en S3 con una Clave administrada por el cliente, no olvides darle permisos para descifrar usando esta clave. Rekognition te dará un error extraño Unsupported y no un error claro sobre por qué falló en este caso.

Flujo de moderación de imágenes

Para lograr este flujo, lo único que necesitamos hacer es crear la StateMachine y configurar los eventos en los que debe activarse.

  ModerateImageStateMachineStandard:
    Type: AWS::Serverless::StateMachine
    Properties:
      DefinitionUri: StateMachine/moderation.asl.yaml
      DefinitionSubstitutions:
        EventBusName: 
          Fn::ImportValue: !Sub ${CommonInfraStackName}:event-bus-name
      Tracing:
        Enabled: true
      Policies:
        - Statement:
            - Effect: Allow
              Action:
                - logs:*
              Resource: "*"
        - S3FullAccessPolicy:
            BucketName: 
              Fn::ImportValue: !Sub ${CommonInfraStackName}:staging-bucket-name
        - EventBridgePutEventsPolicy:
            EventBusName: 
              Fn::ImportValue: !Sub ${CommonInfraStackName}:event-bus-name
        - RekognitionDetectOnlyPolicy: {}
      Events:
        GuardDutyMalwareScanResult:
          Type: EventBridgeRule
          Properties:
            InputPath: $.detail
            Pattern:
              source:
                - aws.guardduty
              detail-type:
                - GuardDuty Malware Protection Object Scan Result
              detail:
                scanResultDetails:
                  scanResultStatus:
                    - NO_THREATS_FOUND

La definición de StateMachine es bastante grande, y hay necesidad de algo de magia. Dado que no puedes agregar etiquetas a un objeto de S3, primero necesitamos obtener todas las etiquetas existentes, agregar nuestra nueva etiqueta y poner la matriz completa de etiquetas en el objeto. Esto probablemente sería más fácil de hacer en una función Lambda, pero dónde está la diversión en eso. Funciones intrínsecas para la victoria....

Comment: Moderate images using Rekognition
StartAt: Debug
States:
  Debug:
    Type: Pass
    Next: Get Object Metadata
  Get Object Metadata:
    Type: Task
    Parameters:
      Bucket.$: $.s3ObjectDetails.bucketName
      Key.$: $.s3ObjectDetails.objectKey
    Resource: arn:aws:states:::aws-sdk:s3:headObject
    Next: Get Object Tags
    ResultPath: $.S3MetaData
  Get Object Tags:
    Type: Task
    Parameters:
      Bucket.$: $.s3ObjectDetails.bucketName
      Key.$: $.s3ObjectDetails.objectKey
    Resource: arn:aws:states:::aws-sdk:s3:getObjectTagging
    Next: Is File Supported?
    ResultPath: $.s3Tags
  Is File Supported?:
    Type: Choice
    Choices:
      - Or:
          - Variable: $.S3MetaData.ContentType
            StringMatches: image/png
          - Variable: $.S3MetaData.ContentType
            StringMatches: image/jpeg
        Next: Moderate Image
    Default: File Not Supported
  File Not Supported:
    Type: Pass
    Next: Add FILE_NOT_SUPPORTED to Object Tags Array
    Parameters:
      ContentTypes: []
      ModerationLabels: []
      ModerationModelVersion: "7.0"
      ThreatsFound: "-1"
    ResultPath: $.RekognitionModeration
  Add FILE_NOT_SUPPORTED to Object Tags Array:
    Type: Pass
    ResultPath: $.scanResult
    Parameters:
      status: FILE_NOT_SUPPORTED
      newTagSet.$: >-
        States.StringToJson(States.Format('[{},{}]',
        States.ArrayGetItem(States.StringSplit(States.JsonToString($.s3Tags.TagSet),
        '[]'),0), '{"Key":"ImageModerationStatus","Value":"FILE_NOT_SUPPORTED"}'))
    Next: Tag S3 Object
  Moderate Image:
    Type: Task
    Parameters:
      Image:
        S3Object:
          Bucket.$: $.s3ObjectDetails.bucketName
          Name.$: $.s3ObjectDetails.objectKey
    Resource: arn:aws:states:::aws-sdk:rekognition:detectModerationLabels
    Next: File Supported
    ResultPath: $.RekognitionModeration
  File Supported:
    Type: Pass
    Parameters:
      ThreatsFound.$: States.ArrayLength($.RekognitionModeration.ModerationLabels)
      ContentTypes.$: $.RekognitionModeration.ContentTypes
      ModerationLabels.$: $.RekognitionModeration.ModerationLabels
      ModerationModelVersion.$: $.RekognitionModeration.ModerationModelVersion
    ResultPath: $.RekognitionModeration
    Next: Was Threats Found?
  Was Threats Found?:
    Type: Choice
    Choices:
      - Variable: $.RekognitionModeration.ThreatsFound
        NumericGreaterThan: 0
        Next: Add THREATS_DETECTED to Object Tags Array
    Default: Add NO_THREATS to Object Tags Array
  Add THREATS_DETECTED to Object Tags Array:
    Type: Pass
    Parameters:
      status: THREATS_FOUND
      newTagSet.$: >-
        States.StringToJson(States.Format('[{},{}]',
        States.ArrayGetItem(States.StringSplit(States.JsonToString($.s3Tags.TagSet),
        '[]'),0), '{"Key":"ImageModerationStatus","Value":"THREATS_FOUND"}'))
    ResultPath: $.scanResult
    Next: Tag S3 Object
  Add NO_THREATS to Object Tags Array:
    Type: Pass
    Next: Tag S3 Object
    Parameters:
      status: NO_THREATS_FOUND
      newTagSet.$: >-
        States.StringToJson(States.Format('[{},{}]',
        States.ArrayGetItem(States.StringSplit(States.JsonToString($.s3Tags.TagSet),
        '[]'),0), '{"Key":"ImageModerationStatus","Value":"NO_THREATS_FOUND"}'))
    ResultPath: $.scanResult
  Tag S3 Object:
    Type: Task
    Parameters:
      Bucket.$: $.s3ObjectDetails.bucketName
      Key.$: $.s3ObjectDetails.objectKey
      Tagging:
        TagSet.$: $.scanResult.newTagSet
    Resource: arn:aws:states:::aws-sdk:s3:putObjectTagging
    ResultPath: null
    Next: Post Scan Result Event
  Post Scan Result Event:
    Type: Task
    Resource: arn:aws:states:::events:putEvents
    Parameters:
      Entries:
        - Detail:
            metadata: {}
            data:
              id.$: $.s3ObjectDetails.objectKey
              status.$: $.scanResult.status
              scanData.$: $.RekognitionModeration
          DetailType: Moderation Scan Completed
          EventBusName: ${EventBusName}
          Source: ImageModeration
    End: true
    ResultPath: null

Finalizar la carga

La última parte de esta solución es reaccionar a la moderación y colocar el contenido ya sea en el bucket de cuarentena o en el bucket de almacenamiento a largo plazo. Para esto uso dos StepFunction diferentes con algunas diferencias en el evento que los activa.

Flujo completado

Para lograr este flujo se crea una nueva StateMachine.

  MoveFilesToPermanentStorageStateMachineStandard:
    Type: AWS::Serverless::StateMachine
    Properties:
      DefinitionUri: StateMachine/move-to-permanent-storage.asl.yaml
      DefinitionSubstitutions:
        EventBusName:
          Fn::ImportValue: !Sub ${CommonInfraStackName}:event-bus-name
        StorageBucketName:
          Fn::ImportValue: !Sub ${CommonInfraStackName}:storage-bucket-name
        StagingBucketName:
          Fn::ImportValue: !Sub ${CommonInfraStackName}:staging-bucket-name
      Tracing:
        Enabled: true
      Policies:
        - Statement:
            - Effect: Allow
              Action:
                - logs:*
              Resource: "*"
        - S3FullAccessPolicy:
            BucketName:
              Fn::ImportValue: !Sub ${CommonInfraStackName}:staging-bucket-name
        - S3FullAccessPolicy:
            BucketName:
              Fn::ImportValue: !Sub ${CommonInfraStackName}:storage-bucket-name
        - EventBridgePutEventsPolicy:
            EventBusName:
              Fn::ImportValue: !Sub ${CommonInfraStackName}:event-bus-name
      Events:
        NoModerationThreatsFoundEvent:
          Type: EventBridgeRule
          Properties:
            EventBusName:
              Fn::ImportValue: !Sub ${CommonInfraStackName}:event-bus-name
            InputPath: $.detail
            Pattern:
              source:
                - ImageModeration
              detail-type:
                - Moderation Scan Completed
              detail:
                data:
                  status:
                    - NO_THREATS_FOUND 

Con una definición de StateMachine que es un poco más fácil de seguir que la parte de moderación de imágenes.

Comment: Handle result and copy files to storage bucket
StartAt: Debug
States:
  Debug:
    Type: Pass
    Next: Get Object Metadata
  Get Object Metadata:
    Type: Task
    Parameters:
      Bucket: ${StagingBucketName}
      Key.$: $.data.id
    Resource: arn:aws:states:::aws-sdk:s3:headObject
    Next: CopyObject
    ResultPath: $.S3MetaData
  CopyObject:
    Type: Task
    Parameters:
      Bucket: ${StorageBucketName}
      CopySource.$: >-
        States.Format('${StagingBucketName}/{}',$.data.id)
      Key.$: $.data.id
    Resource: arn:aws:states:::aws-sdk:s3:copyObject
    ResultPath: null
    Next: DeleteObject
  DeleteObject:
    Type: Task
    Parameters:
      Bucket: ${StagingBucketName}
      Key.$: $.data.id
    Resource: arn:aws:states:::aws-sdk:s3:deleteObject
    ResultPath: null
    Next: Post Event File Moved
  Post Event File Moved:
    Type: Task
    Resource: arn:aws:states:::events:putEvents
    Parameters:
      Entries:
        - Detail:
            metadata: {}
            data:
              id.$: $.data.id
              status: STORED
              contentType.$: $.S3MetaData.ContentType
              fileSize.$: $.S3MetaData.ContentLength
          DetailType: Completed
          EventBusName: ${EventBusName}
          Source: ImageModeration
    End: true
    ResultPath: null

Conclusión

Esta fue una breve publicación sobre cómo extendí mi administrador de archivos construido previamente con escaneo de malware y moderación de imágenes. El uso de servicios administrados hizo que esta fuera una tarea bastante fácil.

Para obtener el código fuente completo y desplegarlo usted mismo, visite Serverless-Handbook Image Moderation

Palabras finales

No olvide seguirme en LinkedIn y X para más contenido, y lea el resto de mis Blogs

Como dice Werner! ¡Ahora ve a construir!