この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちはコカコーラ大好きカジです。
今日はサーバーレスやDockerコンテナのWebアプリケーション保護するCloud One Application Security(Preview)をECS-Fargate上で試してみました。 ECS-Fargateでのセキュリティ対策についてお問い合わせ頂くことがあるため、注目している製品となります。
Cloud One Application Securityとは?
トレンドマイクロのアプリケーション組み込み型のセキュリティ対策で、今まで直接保護ができなかったサーバーレス環境のAWS Lambdaやマネージドのコンテナ環境のECS-Fargateに使えるセキュリティ対策となります。
上記引用元の図はトレンドマイクロサイトの図
サーバーレス環境のやマネージドのコンテナ環境でのアプリケーションにおけるセキュリティ脅威は、OWASP Serverless Top 10や、OWASP API Security Projectに記載されています。
Cloud One Application Securityは、アプリケーション組込型のセキュリティ対策のため、対象言語やバージョンの確認が必要です。
テストする前にTrend Micro Cloud One™ Application Security Documentationをご確認ください。
動作イメージの図は以下となります。
上記の図は動画トレンドマイクロの「Cloud One Application Securityのご紹介」のYoutube動画の内の図となります。
前提条件
- 現在(2020/08/22時点)はPreview版であり、今後変更される予定があるため、ご注意ください。
- Cloud One トライアル版アカウント登録し、30日無償にてトライアルで簡単にテストしています。
- 動画トレンドマイクロの「Cloud One Application Securityのご紹介」のYoutube動画をECS-Fargate上で試してみました。
検証環境の構成図
私の検証環境は以下の構成でECS-Fargate上で動作させて試してみました。
テスト用Webアプリケーションについて
OWASP Juice Shop Projectを利用しています。 詳細は以下のブログで紹介しています。
OWASP Juice ShopをDockerコンテナで起動しています。詳細は以下のサイトとなります。
Cloud One トライアル版アカウント登録
30日無償にてトライアルが可能です。
https://cloudone.trendmicro.com/でアカウントを作成し、登録します。
Cloud Oneにログインできると以下の画面になります。そこからApplication Securityをクリックします。
Application Security導入前作業
ここで言うSecurity Groupは、Application Security上のSecurity Group作成となります。注:AWSのSecurity Groupとは異なります。 「Create New Group」をクリック
「Group Name」に一意の名前(今回は、OWASP-JUICE-SHOP-PROTECT)を入れてCreate Groupをクリックします。
ダッシュボードの左側ペインにグループ名が作成されるため、そこの歯車マークを押して、GROUP CREDENTIALSをメモします。
- キー:保護するアプリケーショングループに関連付けられた一意のキー
- シークレット:保護するアプリケーショングループに関連付けられた一意のシークレット。
- 詳細説明はこちら
ブロックするか検知のみにするかは以下の画面で設定します。SQLインジェクションの詳細設定をクリックして設定を追加します。
準備作業はこれだけなので、すごく簡単です。
Dockerコンテナの準備
コンテナファイルの環境設定に上記でメモしたキーとシークレットを記入します。
Dokcerfile
FROM node:13-alpine
WORKDIR /usr/src/app/
RUN mkdir juice-shop
WORKDIR /usr/src/app/juice-shop
RUN wget https://github.com/bkimminich/juice-shop/releases/download/v10.3.2/juice-shop-10.3.2_node13_linux_x64.tgz
RUN tar -xzvf juice-shop-10.3.2_node13_linux_x64.tgz -C /usr/src/app/juice-shop
RUN pwd
RUN ls -al
WORKDIR /usr/src/app/juice-shop/juice-shop_10.3.2
RUN npm install
RUN apk add --update python
RUN apk add make gcc g++
RUN npm i trend_app_protect
RUN sed -i -e "1i require('trend_app_protect');" app.js
ENV TREND_AP_KEY xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxx(メモしたキー値を記入)
ENV TREND_AP_SECRET xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx(メモしたシークレット値を記入)
ENTRYPOINT ["npm", "start"]
ECRとECS-Fargateの構築
具体的なECRとFargateの構築手順は以下のページを参考にして下さい。
実際に利用したCloudFormationのテンプレートは本ページの下部に記載しています。
SQL Injectionの検知テスト
正常に起動し、ALBのDNS名へブラウザで接続すると、以下の「OWASP Juice shop」の画面が表示されます。
右上のAccountのところをクリックし、Loginをクリックしてユーザー名は、以下パスワードは任意で入力すると管理者権限でログインでき、SQLインジェクションが発生します。 (OWASP Juice shopはハードニングのトレーニング用のツールのため、本来は、SQLインジェクション部分も問題の一部となりネタバレとなります。)
ユーザ名に入力する値の詳細はこちらを参考にしています。結論は、入力値はユーザ名に以下を入力、パスワードは任意でログインとなります。
' or 1=1;--
Cloud Oneの管理画面での確認
1件目はローカルPC上でDockerコンテナを起動して試したものとなり、2件目の検知がECS-Fargate上で検知したものとなります。 どこでも試せるのが良いですね。
時間あたりの発生数表示の画面
最後に
まだ簡単な検知テストのみのため、他にも保護機能もありますので今後、試してしていきたいと思います。
今回の手順ですとnode.jsには導入しやすいのではないかと推測しておりますが、いかがでしょうか?
(私は開発者ではないため自信がないです。最初テストした際に、キーとシークレットの入力間違いで動作しない状態になり、すこしだけ試行錯誤したぐらいでした。)
気になった点としては、Deep Securityのように、エージェントの正常/異常のステータスが管理画面側で見れるわけではないため、動作しているかは検知テストを試してみないとわかりませんでした。
そのため導入する際にはアプリケーションデプロイ後に、アプリケーションの動作テストだけじゃなく、Application Securityの検知テストも一緒に実施して動作確認し運用することが必要と感じました。
構築に使用したCloudFormationのテンプレート
sample-ecr-owasp-juice-shop.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: Create ECR
Parameters:
ProjectName:
Default: owasp-juice-shop
Type: String
Resources:
ECR:
Type: AWS::ECR::Repository
Properties:
RepositoryName: !Sub ${ProjectName}-ecr
sample-fargate-owasp-juice-shop.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: Fargate and ALB Create
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: "Project Name Prefix"
Parameters:
- ProjectName
- Label:
default: "InternetALB Configuration"
Parameters:
- InternetALBName
- TargetGroupName
- Label:
default: "Fargate for ECS Configuration"
Parameters:
- ECSClusterName
- ECSTaskName
- ECSTaskCPUUnit
- ECSTaskMemory
- ECSContainerName
- ECSImageName
- ECSServiceName
- ECSTaskDesiredCount
- Label:
default: "Netowork Configuration"
Parameters:
- VpcId
- ALBSecurityGroupId
- ALBSubnetId1
- ALBSubnetId2
- ECSSecurityGroupId
- ECSSubnetId1
- ECSSubnetId2
- Label:
default: "Scaling Configuration"
Parameters:
- ServiceScaleEvaluationPeriods
- ServiceCpuScaleOutThreshold
- ServiceCpuScaleInThreshold
- TaskMinContainerCount
- TaskMaxContainerCount
ParameterLabels:
InternetALBName:
default: "InternetALBName"
TargetGroupName:
default: "TargetGroupName"
ECSClusterName:
default: "ECSClusterName"
ECSTaskName:
default: "ECSTaskName"
ECSTaskCPUUnit:
default: "ECSTaskCPUUnit"
ECSTaskMemory:
default: "ECSTaskMemory"
ECSContainerName:
default: "ECSContainerName"
ECSImageName:
default: "ECSImageName"
ECSServiceName:
default: "ECSServiceName"
ECSTaskDesiredCount:
default: "ECSTaskDesiredCount"
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
ProjectName:
Default: owasp-juice-shop
Type: String
#VPCID
VpcId:
Description: "VPC ID"
Type: AWS::EC2::VPC::Id
#ALBSecurity Group
ALBSecurityGroupId:
Type: AWS::EC2::SecurityGroup::Id
#ALBSubnet1
ALBSubnetId1:
Description: "ALB Subnet 1st"
Type: AWS::EC2::Subnet::Id
#ALBSubnet2
ALBSubnetId2:
Description: "ALB Subnet 2st"
Type: AWS::EC2::Subnet::Id
#ECSSecurity Group
ECSSecurityGroupId:
Type: AWS::EC2::SecurityGroup::Id
#ECSSubnet1
ECSSubnetId1:
Description: "ECS Subnet 1st"
Type: AWS::EC2::Subnet::Id
#ECSSubnet2
ECSSubnetId2:
Description: "ECS Subnet 2st"
Type: AWS::EC2::Subnet::Id
#InternetALB
InternetALBName:
Type: String
Default: "alb"
#TargetGroupName
TargetGroupName:
Type: String
Default: "tg"
#ECSClusterName
ECSClusterName:
Type: String
Default: "cluster"
#ECSTaskName
ECSTaskName:
Type: String
Default: "task"
#ECSTaskCPUUnit
ECSTaskCPUUnit:
AllowedValues: [256, 512, 1024, 2048, 4096]
Type: String
Default: "256"
#ECSTaskMemory
ECSTaskMemory:
AllowedValues: [256, 512, 1024, 2048, 4096]
Type: String
Default: "512"
#ECSContainerName
ECSContainerName:
Type: String
Default: "container"
#ECSImageName
ECSImageName:
Type: String
Default: "xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/owasp-juice-shop-ecr"
#ECSServiceName
ECSServiceName:
Type: String
Default: "service"
#ECSTaskDesiredCount
ECSTaskDesiredCount:
Type: Number
Default: 1
# Scaling params
ServiceScaleEvaluationPeriods:
Description: The number of periods over which data is compared to the specified threshold
Type: Number
Default: 1
MinValue: 1
ServiceCpuScaleOutThreshold:
Type: Number
Description: Average CPU value to trigger auto scaling out
Default: 50
MinValue: 0
MaxValue: 100
ConstraintDescription: Value must be between 0 and 100
ServiceCpuScaleInThreshold:
Type: Number
Description: Average CPU value to trigger auto scaling in
Default: 25
MinValue: 0
MaxValue: 100
ConstraintDescription: Value must be between 0 and 100
TaskMinContainerCount:
Type: Number
Description: Minimum number of containers to run for the service
Default: 1
MinValue: 1
ConstraintDescription: Value must be at least one
TaskMaxContainerCount:
Type: Number
Description: Maximum number of containers to run for the service when auto scaling out
Default: 1
MinValue: 1
ConstraintDescription: Value must be at least one
Resources:
# ------------------------------------------------------------#
# Target Group
# ------------------------------------------------------------#
TargetGroup:
Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
Properties:
VpcId: !Ref VpcId
Name: !Sub "${ProjectName}-${TargetGroupName}"
Protocol: HTTP
Port: 80
TargetType: ip
# ------------------------------------------------------------#
# Internet ALB
# ------------------------------------------------------------#
InternetALB:
Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
Properties:
Name: !Sub "${ProjectName}-${InternetALBName}"
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${InternetALBName}"
Scheme: "internet-facing"
LoadBalancerAttributes:
- Key: "deletion_protection.enabled"
Value: false
- Key: "idle_timeout.timeout_seconds"
Value: 60
- Key: "access_logs.s3.enabled"
Value: true
- Key: "access_logs.s3.bucket"
Value: !Sub "alb-log-${AWS::AccountId}"
SecurityGroups:
- !Ref ALBSecurityGroupId
Subnets:
- !Ref ALBSubnetId1
- !Ref ALBSubnetId2
ALBListener:
Type: "AWS::ElasticLoadBalancingV2::Listener"
Properties:
DefaultActions:
- TargetGroupArn: !Ref TargetGroup
Type: forward
LoadBalancerArn: !Ref InternetALB
Port: 80
Protocol: HTTP
# ------------------------------------------------------------#
# ECS Cluster
# ------------------------------------------------------------#
ECSCluster:
Type: "AWS::ECS::Cluster"
Properties:
ClusterName: !Sub "${ProjectName}-${ECSClusterName}"
# ------------------------------------------------------------#
# ECS LogGroup
# ------------------------------------------------------------#
ECSLogGroup:
Type: "AWS::Logs::LogGroup"
Properties:
LogGroupName: !Sub "/ecs/logs/${ProjectName}-ecs-group"
# ------------------------------------------------------------#
# ECS Task Execution Role
# ------------------------------------------------------------#
ECSTaskExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${ProjectName}-ECSTaskExecutionRolePolicy"
Path: /
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
# ------------------------------------------------------------#
# ECS TaskDefinition
# ------------------------------------------------------------#
ECSTaskDefinition:
Type: "AWS::ECS::TaskDefinition"
Properties:
Cpu: !Ref ECSTaskCPUUnit
ExecutionRoleArn: !Ref ECSTaskExecutionRole
Family: !Sub "${ProjectName}-${ECSTaskName}"
Memory: !Ref ECSTaskMemory
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
#ContainerDefinitions
ContainerDefinitions:
- Name: !Sub "${ProjectName}-${ECSContainerName}"
Image: !Ref ECSImageName
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: !Ref ECSLogGroup
awslogs-region: !Ref "AWS::Region"
awslogs-stream-prefix: !Ref ProjectName
MemoryReservation: 128
PortMappings:
- HostPort: 3000
Protocol: tcp
ContainerPort: 3000
# ------------------------------------------------------------#
# ECS Service
# ------------------------------------------------------------#
ECSService:
Type: AWS::ECS::Service
DependsOn: ALBListener
Properties:
Cluster: !Ref ECSCluster
DesiredCount: !Ref ECSTaskDesiredCount
LaunchType: FARGATE
LoadBalancers:
- TargetGroupArn: !Ref TargetGroup
ContainerPort: 3000
ContainerName: !Sub "${ProjectName}-${ECSContainerName}"
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: ENABLED
SecurityGroups:
- !Ref ECSSecurityGroupId
Subnets:
- !Ref ECSSubnetId1
- !Ref ECSSubnetId2
ServiceName: !Sub "${ProjectName}-${ECSServiceName}"
TaskDefinition: !Ref ECSTaskDefinition
PlatformVersion: 1.4.0
# ------------------------------------------------------------#
# Auto Scaling Service
# ------------------------------------------------------------#
ServiceAutoScalingRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: application-autoscaling.amazonaws.com
Action: sts:AssumeRole
Path: /
Policies:
- PolicyName: !Sub "${ProjectName}-${ECSContainerName}-autoscaling"
PolicyDocument:
Statement:
- Effect: Allow
Action:
- application-autoscaling:*
- cloudwatch:DescribeAlarms
- cloudwatch:PutMetricAlarm
- ecs:DescribeServices
- ecs:UpdateService
Resource: "*"
ServiceScalingTarget:
Type: AWS::ApplicationAutoScaling::ScalableTarget
Properties:
MinCapacity: !Ref TaskMinContainerCount
MaxCapacity: !Ref TaskMaxContainerCount
ResourceId: !Sub
- service/${EcsClusterName}/${EcsDefaultServiceName}
- EcsClusterName: !Ref ECSCluster
EcsDefaultServiceName: !Sub "${ProjectName}-${ECSServiceName}"
RoleARN: !GetAtt ServiceAutoScalingRole.Arn
ScalableDimension: ecs:service:DesiredCount
ServiceNamespace: ecs
DependsOn:
- ECSService
- ServiceAutoScalingRole
ServiceScaleOutPolicy:
Type: AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyName: !Sub "${ProjectName}-${ECSServiceName}-ScaleOutPolicy"
PolicyType: StepScaling
ScalingTargetId: !Ref ServiceScalingTarget
StepScalingPolicyConfiguration:
AdjustmentType: ChangeInCapacity
Cooldown: 60
MetricAggregationType: Average
StepAdjustments:
- ScalingAdjustment: 1
MetricIntervalLowerBound: 0
DependsOn: ServiceScalingTarget
ServiceScaleInPolicy:
Type: AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyName: !Sub "${ProjectName}-${ECSServiceName}-ScaleInPolicy"
PolicyType: StepScaling
ScalingTargetId: !Ref ServiceScalingTarget
StepScalingPolicyConfiguration:
AdjustmentType: ChangeInCapacity
Cooldown: 60
MetricAggregationType: Average
StepAdjustments:
- ScalingAdjustment: -1
MetricIntervalUpperBound: 0
DependsOn: ServiceScalingTarget
ServiceScaleOutAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub "${ProjectName}-${ECSServiceName}-ScaleOutAlarm"
EvaluationPeriods: !Ref ServiceScaleEvaluationPeriods
Statistic: Average
TreatMissingData: notBreaching
Threshold: !Ref ServiceCpuScaleOutThreshold
AlarmDescription: Alarm to add capacity if CPU is high
Period: 60
AlarmActions:
- !Ref ServiceScaleOutPolicy
Namespace: AWS/ECS
Dimensions:
- Name: ClusterName
Value: !Ref ECSCluster
- Name: ServiceName
Value: !Sub "${ProjectName}-${ECSServiceName}"
ComparisonOperator: GreaterThanThreshold
MetricName: CPUUtilization
DependsOn:
- ECSService
- ServiceScaleOutPolicy
ServiceScaleInAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub "${ProjectName}-${ECSServiceName}-ScaleInAlarm"
EvaluationPeriods: !Ref ServiceScaleEvaluationPeriods
Statistic: Average
TreatMissingData: notBreaching
Threshold: !Ref ServiceCpuScaleInThreshold
AlarmDescription: Alarm to reduce capacity if container CPU is low
Period: 300
AlarmActions:
- !Ref ServiceScaleInPolicy
Namespace: AWS/ECS
Dimensions:
- Name: ClusterName
Value: !Ref ECSCluster
- Name: ServiceName
Value: !Sub "${ProjectName}-${ECSServiceName}"
ComparisonOperator: LessThanThreshold
MetricName: CPUUtilization
DependsOn:
- ECSService
- ServiceScaleInPolicy