Service de modération de contenu alimenté par l'IA sans serveur

Ce fichier a ete traduit automatiquement par IA, des erreurs peuvent survenir
Il y a environ un an, j'ai créé un article de blog sur la création d'un service de gestion de fichiers. Dans cet article, nous utiliserons ce service comme base et l'étendrons avec la modération de contenu. Nous utiliserons GuardDuty et Rekognition pour aider dans cette tâche. Comme d'habitude, tout sera sans serveur et piloté par des événements.
Recap
Pour rafraîchir la mémoire de tout le monde, commençons par un bref rappel.
Le service stockera les fichiers dans S3 et gardera un enregistrement de tous les fichiers dans une table DynamoDB. L'aperçu du système ressemble à ceci, avec une API exposant les fonctionnalités à l'utilisateur, puis effectuant le travail de manière sans serveur et pilotée par des événements.

Le flux d'upload est initié par un client appelant l'API où une fonction Lambda créera une URL pré-signée S3 que le client peut utiliser pour télécharger le fichier. Nous ne téléchargeons pas les fichiers directement via l'API, car Amazon API Gateway a une taille de payload maximale de 10 Mo, et pour prendre en charge tous les types de fichiers, cela deviendra une limitation.
Lorsque le client télécharge ensuite le fichier vers S3, cela génère un événement, la partie sous la ligne pointillée dans l'image, ce qui déclenchera un StepFunction qui mettra à jour l'inventaire des fichiers.

Architecture étendue
Dans l'architecture étendue, nous ajoutons la fonctionnalité d'utiliser l'analyse de logiciels malveillants S3 de GuardDuty et Rekognition pour la modération d'images. GuardDuty analysera les nouveaux fichiers qui arrivent dans le bucket S3, que j'appelle staging, une étiquette sera ajoutée à l'objet et le résultat de l'analyse sera publié sur le bus d'événements par défaut. Le résultat de l'analyse, s'il est OK, déclenchera un StepFunction qui utilisera Rekognition pour la modération d'images. J'ai implémenté la même logique dans ce StepFunction et j'ajoute une étiquette sur l'objet et je publie un événement sur un bus d'événements. Enfin, les fichiers sont déplacés vers un bucket de quarantaine ou un bucket de stockage.
Chaque partie de la solution est découplée et peut fonctionner indépendamment, et un modèle saga est appliqué pour déplacer la logique vers la phase suivante.

Maintenant, plongeons un peu plus profondément dans chacune des parties de cette solution.
Analyse de logiciels malveillants
L'analyse de logiciels malveillants de GuardDuty ne nécessite pas beaucoup de configuration. C'est une fonctionnalité entièrement gérée dans GuardDuty et la seule chose requise est de la configurer. GuardDuty récupérera alors automatiquement les nouveaux objets.

Pour atteindre ce flux, la seule chose que nous devons faire est de créer un S3MalwareProtectionPlan et lui attribuer les autorisations appropriées. Une chose importante à retenir, si vous chiffrez vos objets dans S3 avec une clé gérée par le client, n'oubliez pas de donner à GuardDuty les autorisations de déchiffrement utilisant cette clé.
S3MalwareProtectionPlan:
Type: AWS::GuardDuty::MalwareProtectionPlan
Properties:
Actions:
Tagging:
Status: ENABLED
ProtectedResource:
S3Bucket:
BucketName:
Fn::ImportValue: !Sub ${CommonInfraStackName}:staging-bucket-name
Role: !GetAtt S3MalwareProtectionPlanRole.ArnModération d'images
La partie de modération avec Rekognition implique quelques étapes supplémentaires. Un StepFunction est déclenché par le résultat de l'analyse de logiciels malveillants, et appelle Rekognition pour modérer l'image. Ce StepFunction étiquettera ensuite l'objet et publiera le résultat de l'analyse sur le bus de service personnalisé EventBridge. Une chose importante à retenir, si vous chiffrez vos objets dans S3 avec une clé gérée par le client, n'oubliez pas de donner les autorisations de déchiffrement utilisant cette clé. Rekognition vous donnera une erreur étrange Unsupported et pas une erreur claire expliquant pourquoi cela a échoué dans ce cas.

Pour atteindre ce flux, la seule chose que nous devons faire est de créer la StateMachine et configurer les événements sur lesquels elle doit être déclenchée.
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_FOUNDLa définition de la StateMachine est plutôt grande, et il y a besoin de quelques magie. Comme vous ne pouvez pas ajouter des étiquettes à un objet S3, nous devons d'abord récupérer toutes les étiquettes existantes, ajouter notre nouvelle étiquette et mettre le tableau entier d'étiquettes sur l'objet. Cela serait probablement plus facile à faire dans une fonction Lambda, mais où est le plaisir dans cela. Les fonctions intrinsèques pour la victoire....
Comment: Modérer les images avec 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
Finaliser l'upload
La dernière partie de cette solution est de réagir à la modération et de placer le contenu soit dans le bucket de quarantaine, soit dans le bucket de stockage à long terme. Pour cela, j'utilise deux StepFunction différents avec quelques différences dans l'événement qui les déclenche.

Pour atteindre ce flux, une nouvelle StateMachine est créée.
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 Avec une définition de StateMachine qui est un peu plus facile à suivre que la partie de modération d'images.
Comment: Gérer le résultat et copier les fichiers dans le bucket de stockage
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: nullConclusion
Ceci était un court article sur la façon dont j'ai étendu mon gestionnaire de fichiers précédemment construit avec une analyse de logiciels malveillants et une modération d'images. L'utilisation de services gérés uniquement a rendu cette tâche assez facile.
Pour obtenir le code source complet et le déployer vous-même, visitez Serverless-Handbook Image Moderation
Derniers mots
N'oubliez pas de me suivre sur LinkedIn et X pour plus de contenu, et lisez le reste de mes Blogs
Comme dit Werner ! Maintenant, allez construire !