aws, ai, security, serverless

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

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

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.

Aperçu de l'architecture pour le gestionnaire de fichiers d'origine

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.

Flux d'upload pour le gestionnaire de fichiers d'origine

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.

Aperçu étendu avec modération de contenu

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.

Flux d'analyse de logiciels malveillants

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

Modé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.

Flux de modération d'images

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_FOUND

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

Flux terminé

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: null

Conclusion

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 !