aws, ia, segurança, sem servidor

Serviço de moderação de conteúdo impulsionado por IA sem servidor

2024-10-31
This post cover image
aws cloud serverless ai segurança

Este arquivo foi traduzido automaticamente por IA, erros podem ocorrer

Há cerca de um ano, criei um post no blog sobre a criação de um Serviço de Gerenciamento de Arquivos. Neste post, usaremos esse serviço como base e o estenderemos com moderação de conteúdo. Usaremos o GuardDuty e o Rekognition para ajudar nessa tarefa. Como de costume, tudo será sem servidor e orientado a eventos.

Recapitulação

Para refrescar a memória de todos, vamos começar com uma breve recapitulação.

O serviço armazenará arquivos no S3 e manterá um registro de todos os arquivos em uma tabela do DynamoDB. A visão geral do sistema é esta, com uma API expondo funcionalidade para o usuário e, em seguida, executando o trabalho de maneira sem servidor e orientada a eventos.

Visão geral da arquitetura para o gerenciador de arquivos original

O fluxo de upload é iniciado por um cliente chamando a API, onde uma função Lambda criará uma URL pré-assinada do S3 que o cliente pode usar para fazer upload do arquivo. Não fazemos upload de arquivos diretamente pela API, já que o Amazon API Gateway tem um tamanho máximo de carga de 10 MB, e para suportar todos os tipos de arquivos isso se tornará uma limitação.

Quando o cliente faz upload do arquivo para o S3, isso gera um evento, a parte abaixo da linha tracejada na imagem, que acionará um StepFunction que atualizará o inventário de arquivos.

Fluxo de upload para o gerenciador de arquivos original

Arquitetura estendida

Na arquitetura estendida, adicionamos funcionalidade para usar o escaneamento de malware do GuardDuty no S3 e o Rekognition para moderação de imagens. O GuardDuty escaneará novos arquivos que chegarem no bucket S3, que chamo de staging, uma tag será adicionada ao objeto e o resultado do escaneamento será publicado no bus de eventos padrão. O resultado do escaneamento, se OK, acionará um StepFunction que utilizará o Rekognition para moderação de imagens. Implementei a mesma lógica neste StepFunction e adiciono uma tag no objeto e publico um evento em um bus de eventos. Finalmente, os arquivos são movidos para um bucket de quarentena ou para o bucket de armazenamento.

Cada parte da solução é desacoplada e pode rodar independentemente, e um padrão saga é aplicado para mover a lógica para a próxima fase.

Visão geral estendida com moderação de conteúdo

Agora, vamos mergulhar um pouco mais fundo em cada uma das partes desta solução.

Escaneamento de malware

O escaneamento de malware do GuardDuty não requer muita configuração. Esta é uma funcionalidade totalmente gerenciada no GuardDuty e a única coisa necessária é configurá-la. O GuardDuty então pegará novos objetos automaticamente.

Fluxo de escaneamento de malware

Para alcançar esse fluxo, a única coisa que precisamos fazer é criar um S3MalwareProtectionPlan e atribuir permissões apropriadas a ele. Uma coisa importante a lembrar, se você criptografar seus objetos no S3 com uma Chave Gerenciada pelo Cliente, não se esqueça de dar permissões ao GuardDuty para descriptografar usando esta chave.

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

Moderação de imagens

A parte de moderação com o Rekognition envolve mais alguns passos. Um StepFunction é acionado pelo resultado do escaneamento de malware e chama o Rekognition para moderar a imagem. Este StepFunction, em seguida, marca o objeto e publica o resultado do escaneamento no bus de eventos personalizado do EventBridge. Uma coisa importante a lembrar, se você criptografar seus objetos no S3 com uma Chave Gerenciada pelo Cliente, não se esqueça de dar permissões para descriptografar usando esta chave. O Rekognition lhe dará um erro estranho Unsupported e não um erro claro sobre por que ele falhou neste caso.

Fluxo de moderação de imagens

Para alcançar este fluxo, a única coisa que precisamos fazer é criar a StateMachine e configurar os eventos em que ela deve ser acionada.

  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

A definição da StateMachine é bastante grande e há necessidade de um pouco de mágica. Como você não pode adicionar tags a um objeto S3, primeiro precisamos buscar todas as tags existentes, adicionar nossa nova tag e colocar o array inteiro de tags no objeto. Isso provavelmente seria mais fácil de fazer em uma função Lambda, mas onde está a diversão nisso. Funções intrínsecas para a vitória....

Comment: Moderar imagens usando o 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 o upload

A última parte desta solução é reagir à moderação e colocar o conteúdo seja no bucket de quarentena seja no bucket de armazenamento de longo prazo. Para isso, uso dois StepFunctions diferentes com algumas diferenças no evento que os aciona.

Fluxo concluído

Para alcançar este fluxo, uma nova StateMachine é criada.

  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 

Com uma definição de StateMachine que é um pouco mais fácil de seguir do que a parte de moderação de imagens.

Comment: Manipular o resultado e copiar arquivos para o bucket de armazenamento
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

Conclusão

Este foi um post curto sobre como estendi meu gerenciador de arquivos construído anteriormente com escaneamento de malware e moderação de imagens. O uso de serviços gerenciados tornou esta uma tarefa bastante fácil.

Para obter o código-fonte completo e implantá-lo você mesmo, visite Serverless-Handbook Image Moderation

Palavras Finais

Não se esqueça de me seguir no LinkedIn e X para mais conteúdo, e leia o restante dos meus Blogs

Como Werner diz! Agora vá construir!