0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Amazon FSx for Windows File Server のマルウェアスキャンを GuardDuty × Lambda でAWSリソースのみで実現する

0
Last updated at Posted at 2026-03-25

本記事では、マルウェアスキャン機能を標準搭載しない Amazon FSx for Windows File Server に対し、GuardDuty Malware Protection for S3 と Lambda を組み合わせてAWSリソースだけで自動スキャン・自動隔離を実現する手順をハンズオン形式で解説します。

サードパーティ製ウイルス対策ソフト不要で、CloudFormation によるインフラ構築からテストファイルによる動作確認まで一通り行います。

AWSのサンプルアーキテクチャをもとに進めていきます。
元記事は以下の記事となります。
[aws-samples/sample-fsx-for-windows-malware-scan]

Amazon FSx for Windows File Server とは

Amazon FSx for Windows File Serverは、フルマネージド型の Windows ネイティブファイルシステムサービスです。オンプレミスの Windows ファイルサーバーと同様に SMB プロトコルや NTFS、Active Directory 統合をそのまま利用できるため、既存の Windows アプリケーションをほぼ変更なしに移行できます。

Amazon FSx for Windows File Serverでのマルウェアスキャンの課題

ただmAmazon FSx for Windows File Serverでマルウェアスキャン(ウイルス対策)機能は標準で備わっていません。
また、フルマネージドサービスであり、OSレベルへのアクセスが制限されているため、エージェント型のアンチウイルスソフトをそのままインストールできない課題があります。

そのためファイルサーバーに対して格納されるデータへスキャンを実施し、マルウェア検知を行いたい場合は別にEC2を立ち上げて、サードパーティーの製品を導入するなどを行う必要がありました。

Amazon FSx for NetApp ONTAPであればvscan機能が備わっており、サードパーティー製品にオフロードすることで実施できていましたが、Amazon FSx for Windows File Serverはあまり事例がなくマルウェアスキャンのハードルが高かったです。

Amazon FSx for Windows File Server のマルウェアスキャンをAWS内で完結させる

今回は、AWSのBlogでサンプルソリューションとして公開された sample-fsx-for-windows-malware-scan を使って、Amazon FSx for Windows File Server へのファイルアップロードを自動でマルウェアスキャンする仕組みを構築するハンズオンを紹介します。

サーバーレスアーキテクチャで実装されているため、製品EoLを気にしなくてよい、EC2と比較して可用性が高いなどのメリットがあります。

アーキテクチャ

FSx for Windows マルウェアスキャン アーキテクチャ全体図
https://github.com/aws-samples/sample-fsx-for-windows-malware-scanより引用)

今回のアーキテクチャは図の通りです。
FSx for Windows File Serverに格納されたデータをS3にコピーを行い、GuardDuty Malware Protection for S3によるマルウェアスキャンを行い、マルウェアが検知された場合は隔離します。

GuardDuty Malware Protection for S3とは

Amazon GuardDuty Malware Protection for S3 は、S3 バケットにアップロードされたオブジェクトをリアルタイムで自動スキャンし、マルウェア・ランサムウェア・スパイウェアなどの脅威を検知するフルマネージドサービスです。エージェントのインストールや定義ファイルの管理は不要で、AWS が提供する脅威インテリジェンスと機械学習モデルを使って未知の脅威にも対応します。スキャン結果はオブジェクトにタグとして付与され、EventBridge 経由で後続処理(隔離・通知など)へ連携できます。
詳細は以下記事を参照ください。
AWS公式ブログ:FSx for Windows の自動マルウェアスキャン

すなわち、Amazon FSx for Windows File Serverでマルウェアスキャンができないため、S3とGuardDuty Malware Protection for S3を使用したマルウェア検知機能と連携させてスキャンを行おうという仕組みです。

環境構築

それでは環境構築を行っていきます。
サンプルアーキテクチャ内にCloudFormationのyamlファイルは格納されていますが、事前準備としてADのドメイン環境やVPCなどの作成が必要です。

事前準備

今回は事前のCloudFormationを実行します。

AWSTemplateFormatVersion: '2010-09-09'

Parameters:

  DomainName:
    Type: String
    Default: test.internal
    Description: Managed AD domain name (FQDN)
    AllowedPattern: '^([a-zA-Z0-9]+\.)+[a-zA-Z]{2,}$'

  DomainShortName:
    Type: String
    Default: TEST
    Description: NetBIOS domain name (up to 15 characters)
    MaxLength: 15

  DomainAdminPassword:
    Type: String
    NoEcho: true
    MinLength: 8
    MaxLength: 64
    Description: >
      AD Admin password (8-64 characters).
      Must NOT contain the word "admin".
      Must include at least 3 of the following 4 categories:
      uppercase (A-Z), lowercase (a-z), digits (0-9), special characters (!@#$%^ etc.)
      Example: MyP@ssw0rd123

  KeyPairName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: Key pair for Windows EC2 instance

  WindowsAmiId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-windows-latest/Windows_Server-2022-Japanese-Full-Base
    Description: Windows Server AMI (no change required)

  WindowsInstanceType:
    Type: String
    Default: t3.medium
    AllowedValues:
      - t3.medium
      - t3.large
      - m5.large
    Description: Windows EC2 instance type

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Active Directory Settings
        Parameters:
          - DomainName
          - DomainShortName
          - DomainAdminPassword
      - Label:
          default: EC2 Settings
        Parameters:
          - KeyPairName
          - WindowsAmiId
          - WindowsInstanceType
    ParameterLabels:
      DomainName:
        default: AD Domain Name (FQDN)
      DomainShortName:
        default: NetBIOS Domain Name
      DomainAdminPassword:
        default: AD Admin Password
      KeyPairName:
        default: Key Pair Name
      WindowsAmiId:
        default: Windows AMI ID
      WindowsInstanceType:
        default: Instance Type

Resources:

  # ----------------------------------------------------------
  # VPC
  # ----------------------------------------------------------
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-vpc

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-igw

  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  # ----------------------------------------------------------
  # Subnets
  #   PublicSubnet1  (AZ-a) : 10.0.0.0/24  - NAT Gateway / Windows EC2
  #   PrivateSubnet1 (AZ-a) : 10.0.10.0/24 - Managed AD / FSx / Lambda
  #   PrivateSubnet2 (AZ-b) : 10.0.11.0/24 - Managed AD (requires 2 AZs)
  # ----------------------------------------------------------
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.0.0/24
      AvailabilityZone: !Select [0, !GetAZs '']
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-public-1

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.10.0/24
      AvailabilityZone: !Select [0, !GetAZs '']
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-private-1

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.11.0/24
      AvailabilityZone: !Select [1, !GetAZs '']
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-private-2

  # ----------------------------------------------------------
  # Public Route Table (via Internet Gateway)
  # ----------------------------------------------------------
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-public-rt

  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: VPCGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1
      RouteTableId: !Ref PublicRouteTable

  # ----------------------------------------------------------
  # NAT Gateway
  # Required for Lambda in private subnet to reach S3 / Secrets Manager
  # ----------------------------------------------------------
  NatGatewayEIP:
    Type: AWS::EC2::EIP
    DependsOn: VPCGatewayAttachment
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-nat-eip

  NatGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGatewayEIP.AllocationId
      SubnetId: !Ref PublicSubnet1
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-nat

  # ----------------------------------------------------------
  # Private Route Table (via NAT Gateway)
  # ----------------------------------------------------------
  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-private-rt

  PrivateRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway

  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet1
      RouteTableId: !Ref PrivateRouteTable

  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet2
      RouteTableId: !Ref PrivateRouteTable

  # ----------------------------------------------------------
  # AWS Managed Microsoft Active Directory
  # Used for ManagedADId in fsx-malware-scan.yaml
  # ----------------------------------------------------------
  ManagedAD:
    Type: AWS::DirectoryService::MicrosoftAD
    Properties:
      Name: !Ref DomainName
      ShortName: !Ref DomainShortName
      Password: !Ref DomainAdminPassword
      Edition: Standard
      VpcSettings:
        VpcId: !Ref VPC
        SubnetIds:
          - !Ref PrivateSubnet1
          - !Ref PrivateSubnet2

  # ----------------------------------------------------------
  # Windows EC2 (FSx mount / test client)
  # ----------------------------------------------------------
  WindowsInstanceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${AWS::StackName}-windows-role
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
        - arn:aws:iam::aws:policy/AmazonSSMDirectoryServiceAccess
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-windows-role

  WindowsInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref WindowsInstanceRole

  WindowsClientSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for Windows EC2 client
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3389
          ToPort: 3389
          CidrIp: 0.0.0.0/0
          Description: RDP access (restrict source IP in production)
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-windows-sg

  WindowsEC2:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref WindowsAmiId
      InstanceType: !Ref WindowsInstanceType
      SubnetId: !Ref PublicSubnet1
      SecurityGroupIds:
        - !Ref WindowsClientSG
      IamInstanceProfile: !Ref WindowsInstanceProfile
      KeyName: !Ref KeyPairName
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-windows-client

  # Auto domain join via SSM after Managed AD is ready
  DomainJoinAssociation:
    Type: AWS::SSM::Association
    DependsOn: ManagedAD
    Properties:
      Name: AWS-JoinDirectoryServiceDomain
      Targets:
        - Key: InstanceIds
          Values:
            - !Ref WindowsEC2
      Parameters:
        directoryId:
          - !Ref ManagedAD
        directoryName:
          - !Ref DomainName
        dnsIpAddresses:
          - !Select [0, !GetAtt ManagedAD.DnsIpAddresses]
          - !Select [1, !GetAtt ManagedAD.DnsIpAddresses]

# ============================================================
# Outputs - use these values as parameters for fsx-malware-scan.yaml
# ============================================================
Outputs:

  OutputVpcId:
    Description: "fsx-malware-scan.yaml > VpcId"
    Value: !Ref VPC
    Export:
      Name: !Sub ${AWS::StackName}-VpcId

  OutputSubnetId:
    Description: "fsx-malware-scan.yaml > SubnetId (private subnet)"
    Value: !Ref PrivateSubnet1
    Export:
      Name: !Sub ${AWS::StackName}-SubnetId

  OutputManagedADId:
    Description: "fsx-malware-scan.yaml > ManagedADId"
    Value: !Ref ManagedAD
    Export:
      Name: !Sub ${AWS::StackName}-ManagedADId

  OutputVpcCidr:
    Description: "fsx-malware-scan.yaml > VpcCidr"
    Value: 10.0.0.0/16
    Export:
      Name: !Sub ${AWS::StackName}-VpcCidr

  OutputLayerBucketName:
    Description: "fsx-malware-scan.yaml > LayerBucketName (created by prepare-layer.sh)"
    Value: !Sub lambda-layer-smbprotocol-${AWS::AccountId}
    Export:
      Name: !Sub ${AWS::StackName}-LayerBucketName

  WindowsEC2PublicIp:
    Description: "Windows EC2 public IP address (RDP)"
    Value: !GetAtt WindowsEC2.PublicIp

  ManagedADDnsIp1:
    Description: "Managed AD DNS IP address 1"
    Value: !Select [0, !GetAtt ManagedAD.DnsIpAddresses]

  ManagedADDnsIp2:
    Description: "Managed AD DNS IP address 2"
    Value: !Select [1, !GetAtt ManagedAD.DnsIpAddresses]

こちらの実行後、VPCやManagedAD、操作用のEC2を作成します。

その後はサンプルアーキテクチャに沿って環境構築を行います。

smbprotocol.zipの格納

まずはprepare-layer.shを実行します。
prepare-layer.shをよく見るとap-southeast-2とリージョンがハードコーディングされているため、シェルの実行前にデプロイしたいリージョンに書き換えてから実行してください。(この検証では東京リージョンで検証しています。)

sed -i 's/ap-southeast-2/ap-northeast-1/g' prepare-layer.sh

ただ、リージョンの置き換えをしても今回うまくいかなかったためprepare-layer.shをあきらめて手動で以下のコマンドを実行しています。
東京リージョンでうまくいかない場合は参考にしてください。

git clone https://github.com/aws-samples/sample-fsx-for-windows-malware-scan
cd sample-fsx-for-windows-malware-scan/scripts


sudo yum install python3.12 -y
python3.12 -m venv venv
source venv/bin/activate
mkdir python && cd python
pip install smbprotocol cffi==1.17.1 -t .
cd ..
zip -r smbprotocol.zip python


ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
BUCKET_NAME="fsx-malware-layer-${ACCOUNT_ID}"
aws s3api create-bucket --bucket ${BUCKET_NAME} --region ap-northeast-1 --create-bucket-configuration LocationConstraint=ap-northeast-1
aws s3 cp smbprotocol.zip s3://${BUCKET_NAME}/
aws s3 ls s3://${BUCKET_NAME}/

smbprotocol.zipがアップロードされていれば問題ありません。
S3バケットへのsmbprotocol.zipアップロード完了画面

このリポジトリは 2025 年 11 月に作成されましたが、本記事執筆時点(2026 年 3 月)では cffi 2.0.0 が最新版となっており、Lambda の Python 3.12 ランタイムと非互換です。prepare-layer.shを直接実行しても内部で pip install smbprotocol -t . を実行しているため、バージョンを固定しない限り同様の問題が発生します。cffi==1.17.1 を明示的に指定することで回避できました。

fsx-malware-scan.yamlのデプロイ

続いて以下のfsx-malware-scan.yamlをデプロイしていきます。
fsx-malware-scan.yaml(GitHub)

パラメータは以下の通りです。
CloudFormation パラメータ入力画面

事前に作成したCloudFormationスタックから必要な情報を取得してパラメータに格納していきます。
必要な情報はアウトプットで指定しているため出力タブから確認しCloudFormationを実行します。
メールアドレスは検知時の通知先のものとしてご自身のアドレスを入力してください。

デプロイ後、SNS から確認メールが届くので 「Confirm subscription」をクリックすることで環境準備は完了です。

デプロイ後設定

それではデプロイ後の準備を行っていきます。

Amazon FSx for Windows File Server

まずはAmazon FSx for Windows File ServerのDNS名を控えます。
リソースを開いて、ネットワークとセキュリティタブのDNS名を控えてください。

Secrets Manager の認証情報の設定

次にLambdaが取得するSecrets Managerの値です。
デフォルトではSecretString: '{"username":"username","password":"password"}'の設定のため、使用するユーザー、パスワードを設定します。今回は最初に使用したCloudFormationでの認証情報を入れました。
Secrets Manager の認証情報の設定

タグ付の有効化

GuardDuty Malware Protection for S3のタグ付が有効になっていないため、有効化します。
タグ付けが無効のままでは、GuardDuty によるスキャンは行われますが、スキャン結果がオブジェクトに付与されないため、後続の隔離処理やメール通知が機能しません。
GuardDuty Malware Protection for S3 タグ付け有効化設定画面

EC2への接続

続いて、EC2に接続をしていきます。
上記のCloudFormationを使用していた場合、パブリックサブネットに検証用のEC2があります。
IPアドレスを控えて、RDPクライアントで接続します。

  • ユーザー名: スタック作成時に指定したドメイン\Admin
  • パスワード: スタック作成時に指定したパスワード

ユーザー名はCloudFormationを書き換えて任意のユーザー名にすることが可能です。

Windows Defender のリアルタイム保護の無効化

今回はEICARのテストファイルを用いてマルウェア検知時の動作確認を行います。
EICARファイルを作成時にOS標準で保護されているWindows Defenderが検知を行うため、テスト中のみ無効化します。
手順

  • スタートメニュー → Windows Security を開く
  • ウイルスと脅威の防止 → ウイルスと脅威の防止の設定項目の設定の管理
  • リアルタイム保護 を オフ にする
    確認がありますが、はいをクリックして設定を変えてください。

Windows Security ウイルスと脅威の防止 設定画面
Windows Defender リアルタイム保護をオフにした確認画面
上記の画面のようになっていれば対応完了です。
テストが終わればこちらは再度有効化しておきます。

Amazon FSx for Windows File Serverにファイル共有をマウント

Powershellを管理者権限で開き以下のPowershellを実行します。
の箇所は控えていたDNS名を使用してください。

# FSx DNS 名を変数に設定(Step 1 で確認した値に置き換える)
$FsxDnsName = "<FSX-DNS-NAME>"

# ファイル共有をマウント
net use Z: \\$FsxDnsName\share

# 監査ルールを設定
$sharePath = "\\$FsxDnsName\share"
$auditUser = "Everyone"
$auditRights = "CreateFiles,WriteData"
$auditType = "Success"

$acl = Get-Acl $sharePath
$auditRule = New-Object System.Security.AccessControl.FileSystemAuditRule(
    $auditUser, $auditRights, "ContainerInherit,ObjectInherit", "None", $auditType)
$acl.SetAuditRule($auditRule)
Set-Acl -Path $sharePath -AclObject $acl

Write-Host "Auditing configured for $sharePath"

 FSxファイル共有マウント PowerShell実行結果

コマンドを実行が完了するとエクスプローラーからAmazon FSx for Windows File Serverにアクセスを行い正常に接続できたことが確認できました。

エクスプローラーでZドライブのFSxファイル共有に接続できた画面

長くなりましたが、以上で環境構築などの事前作業は完了です。

テストファイルのダウンロード

それではテストファイルとEICARのダウンロードを行います。
今回はAWSのサンプルアーキテクチャのgitにテスト用のファイルも格納されているためそのファイルを使用していきます。

clean.txtをFSxファイル共有にコピーするPowerShell操作

ファイルがダウンロードできていることが確認できました。
S3スキャンバケットにclean.txtがコピーされた確認画面
EICARがダウンロードできない場合は、セキュリティ設定が影響している可能性があるため環境を確認ください。

正常系動作確認

では、clean.txtから動作確認を行います。
clean.txtをAmazon FSx for Windows File Serverのファイル共有にコピーを行います。

今回のclean.txtは無害なファイルのため、通常の動作を確認していきます。
確認結果
clean.txtをFSxファイル共有にコピーする操作

S3スキャンバケットにclean.txtがコピーされた確認画面

S3オブジェクトにGuardDutyMalwareScanStatus: NO_THREATS_FOUNDタグが付与された画面

オブジェクトがS3にコピーされ、問題ないことを示すNO_THREATS_FOUNDが付与されていることが確認できました。

異常系動作確認

続いてマルウェア検知時の動作を確認します。
EICARをAmazon FSx for Windows File Serverに格納します。
EICARテストファイルをFSxにコピーする
30秒ほどで、EICARが隔離を示すquarantineフォルダに移動しました。
マルウェア検知後にquarantineフォルダへ自動隔離された結果
quarantineフォルダ内のeicar.txtを確認した画面

作成された quarantine フォルダのアクセス権限は制限されていないため、実運用では セキュリティ担当者のみがアクセスできるよう権限を絞ることを推奨します。
quarantineフォルダ内のeicar.txtを確認した画面

ちなみにフォルダを作成した場合でもマルウェアスキャンは実施されます。
サブフォルダ内EICARファイルも自動隔離された確認画面

S3バケット内から元々どのAmazon FSx for Windows File Serverのフォルダに格納されていたか確認ができます。
S3バケット内で隔離されたオブジェクトのメタデータ確認画面

まとめ

以上、Amazon FSx for Windows File Serverでサードパーティー製品を用いず、AWSリソースのみでマルウェアスキャンを行いました。
監査ログをCloudWatch Logsに出力しているため、追加でどのユーザーが該当のファイルをアップロードしたかなど追跡も可能です。

AWSで完結しているためマルウェアスキャン用のEC2のメンテナンスコストや可用性、サーバーレス構成によるコスト最適化など様々な面でメリットはありそうです。
ただ、LambdaやGuardDuty Malware Protection for S3の都合上、従量課金になるため、事前に料金計算などは必要になります。GuardDuty Malware Protection for S3もサービスリリース直後に比べて料金が安くなっていたりとするため、小中規模環境では候補になりえるかと思います。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?