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

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.

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.

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.

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.

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.ArnModeraçã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.

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_FOUNDA 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.

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: nullConclusã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!