[AWS]WorkSpaces環境構築

## はじめに

お久しぶりです、なじむです。
前回の更新からだいぶ間が空いてしまいました。端的に言うと凄い忙しく、休日はHPを回復するだけの日々を送っていました。
さてさて、コロナの影響を受けて弊社でも在宅勤務が始まりました。それに伴い、WorkSpacesを触ってみたので、その備忘録として記事を起こします。セキュリティ面は甘々かもしれませんが、先ずは動くよ、というところまでです。

## やりたいこと

今回は以下のような観点で構築しました。

- WorkSpacesを利用して、自宅からでも作業できる環境を構築したい！
- WorkSpacesにてブラウザからGoogle Drive等にアクセスされてしまうとデータの持ち出しにつながるため、URLフィルタリングをしたい！
- WorkSpacesから自宅PCへのクリップボードコピーがされてしまうとデータの持ち出しにつながる可能性があるため、それは防ぎたい

## 前提

作業者がAdministratorAccessを持っていることを想定しています。

## 全体概要図

![全体概要図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/396510/5c3a8efd-33af-15a9-c48d-608c32b7b3f6.png)

全体の構成としては以下のような形です。

- Private Subnet, Public Subnetはそれぞれ2つずつ作成
- WorkSpacesとそれに必要なAD環境（MSAD, 管理用Windows Server）はPrivate Subnetに構築
- クリップボードコピー等はMSADのグループポリシーで制御
- WorkSpacesから外部接続する場合はProxyサーバを経由して接続
- URLフィルタリングはProxyサーバにSquidを用いて実装

## 実装の流れ

通知までの大きな流れとしては以下です（絵を描けば良かった…）

1. VPCを構築する
1. AWS Management Microsoft ADを構築する
1. 管理用Windows Serverを構築する
1. Security Groupsの作成（Proxyサーバ、WorkSpaces用）
1. Proxyサーバ(Squid)を構築する
1. WorkSpacesを構築する
1. セキュリティ周りの実装

## 実装

### VPCを構築する

VPCの構築に関しては以下のyamlを流してください。
※使いまわしなのでNATGateway等々があるのはご容赦ください。

決めるパラメータは以下の通りです。
東京リージョンでWorkSpacesを使用する場合、ap-northeast-1dは使用できないので注意が必要です。AWS Developer Forumにも上がっていましたが、実際、私も構築できずVPCから再作成することになりました。
※[TokyoリージョンでWorkSpacesが構築できない](https://forums.aws.amazon.com/thread.jspa?messageID=853401&tstart=0)


|パラメータ名|説明|記入例|
|:---|:---|:---|
|Product|製品名を入力。特になければtest等を入力。|test|
|Environment|dev, stg, prdから選択|dev|
|VPCCidrBlock|VPCのCidrを入力|10.0.0.0/16|
|PubSubnet1Cidr|Public Subnet1のCidrを入力|10.0.0.0/24|
|PubSubnet1AZ|Public Subnet1のAZを入力|ap-northeast-1a|
|PubSubnet2Cidr|Public Subnet2のCidrを入力|10.0.1.0/24|
|PubSubnet2AZ|Public Subnet2のAZを入力|ap-northeast-1c|
|PriSubnet1Cidr|Private Subnet1のCidrを入力|10.2.0.0/24|
|PriSubnet1AZ|Private Subnet1のAZを入力|ap-northeast-1a|
|PriSubnet2Cidr|Private Subnet2のCidrを入力|10.4.0.0/24|
|PriSubnet2AZ|Private Subnet2のAZを入力|ap-northeast-1c|


<details><summary>【yaml】VPC.yaml</summary><div>

````yaml
---
AWSTemplateFormatVersion: 2010-09-09
Description: VPC

#------------------------------
# Parameters: Set your argv.
#------------------------------
Parameters:
  # Common
  Product:
    Type: String
    Description: "[Required]Specify the product name"
  Environment:
    Type: String
    Description: "[Required]Specify the environment name(prd, stg, dev)"
    AllowedValues:
    - prd
    - stg
    - dev
  # VPC parameter
  VPCCidrBlock:
    Type: String
    Description: VPC Cidr Block. (Ex) 10.0.0.0/16
    AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}'
    Default: 10.0.0.0/16
  # Public Subnet1 parameter
  PubSubnet1Cidr:
    Type: String
    Description: Public Subnet Cidr Block. (Ex) 10.0.0.0/24
    AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}'
    Default: 10.0.0.0/24
  PubSubnet1AZ:
    Type: AWS::EC2::AvailabilityZone::Name
    Description: Select the AvailabilityZone of Public Subnet.
    Default: ap-northeast-1a
  # Public Subnet2 parameter
  PubSubnet2Cidr:
    Type: String
    Description: Public Subnet Cidr Block. (Ex) 10.0.1.0/24
    AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}'
    Default: 10.0.1.0/24
  PubSubnet2AZ:
    Type: AWS::EC2::AvailabilityZone::Name
    Description: Select the AvailabilityZone of Public Subnet.
    Default: ap-northeast-1c
  # Private Subnet1 parameter
  PriSubnet1Cidr:
    Type: String
    Description: Private Subnet Cidr Block. (Ex) 10.0.2.0/23
    AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}'
    Default: 10.0.2.0/23
  PriSubnet1AZ:
    Type: AWS::EC2::AvailabilityZone::Name
    Description: Select the AvailabilityZone of Private Subnet.
    Default: ap-northeast-1a
  # Private Subnet2 parameter
  PriSubnet2Cidr:
    Type: String
    Description: Private Subnet Cidr Block. (Ex) 10.0.4.0/23
    AllowedPattern: '((\d{1,3})\.){3}\d{1,3}/\d{1,2}'
    Default: 10.0.4.0/23
  PriSubnet2AZ:
    Type: AWS::EC2::AvailabilityZone::Name
    Description: Select the AvailabilityZone of Private Subnet.
    Default: ap-northeast-1c

#------------------------------
# Metadata: Your resource list
#------------------------------
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
    - Label:
        default: System Configuration
      Parameters:
      - Product
      - Environment
    - Label:
        default: Network Configuration
      Parameters:
      - VPCCidrBlock
      - PubSubnet1Cidr
      - PubSubnet1AZ
      - PubSubnet2Cidr
      - PubSubnet2AZ
      - PriSubnet1Cidr
      - PriSubnet1AZ
      - PriSubnet2Cidr
      - PriSubnet2AZ

#------------------------------
# Resources: Your resource list
#------------------------------
Resources:
  # IAM
  ## Role - VPC FlowLogs
  RoleVPCFlowLogs:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${Product}-${Environment}-iam-role-vpcflowlogs
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
        - Effect: Allow
          Action: sts:AssumeRole
          Principal:
            Service: vpc-flow-logs.amazonaws.com
      ManagedPolicyArns:
      - !Ref ManagedPolicy1
      Path: "/"

  ## IAM Policy
  ManagedPolicy1:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      PolicyDocument:
        Version: 2012-10-17
        Statement:
        - Effect: Allow
          Action:
          - logs:CreateLogGroup
          - logs:CreateLogStream
          - logs:PutLogEvents
          - logs:DescribeLogGroups
          - logs:DescribeLogStreams
          Resource:
          - '*'

  # VPC
  ## VPC
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Sub ${VPCCidrBlock}
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
      - Key: Name
        Value: !Sub ${Product}-${Environment}-vpc

  ## LogGroup
  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /aws/vpc/FlowLogs/${Product}-${Environment}-vpc
      RetentionInDays: 30

  ## VPCFlowLogs
  VPCFlowLogs:
    Type: AWS::EC2::FlowLog
    Properties:
      DeliverLogsPermissionArn: !GetAtt RoleVPCFlowLogs.Arn
      LogGroupName: !Ref LogGroup
      ResourceId: !Ref VPC
      ResourceType: VPC
      TrafficType: ALL

  #------------------------------
  # Subnet
  #------------------------------
  # Public Subnet1
  PubSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: !Ref PubSubnet1Cidr
      AvailabilityZone: !Ref PubSubnet1AZ
      MapPublicIpOnLaunch: true
      Tags:
      - Key: Name
        Value: !Sub ${Product}-${Environment}-pubsubnet1

  # Public Subnet2
  PubSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: !Ref PubSubnet2Cidr
      AvailabilityZone: !Ref PubSubnet2AZ
      MapPublicIpOnLaunch: true
      Tags:
      - Key: Name
        Value: !Sub ${Product}-${Environment}-pubsubnet2

  # Private Subnet1
  PriSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: !Ref PriSubnet1Cidr
      AvailabilityZone: !Ref PriSubnet1AZ
      MapPublicIpOnLaunch: false
      Tags:
      - Key: Name
        Value: !Sub ${Product}-${Environment}-prisubnet1

  # Private Subnet2
  PriSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: !Ref PriSubnet2Cidr
      AvailabilityZone: !Ref PriSubnet2AZ
      MapPublicIpOnLaunch: false
      Tags:
      - Key: Name
        Value: !Sub ${Product}-${Environment}-prisubnet2

  #------------------------------
  # DHCP Options
  #------------------------------
  DHCPOptions:
    Type: AWS::EC2::DHCPOptions
    Properties: 
      DomainName: ap-northeast-1.compute.internal
      DomainNameServers:
      - AmazonProvidedDNS
      Tags: 
      - Key: Name
        Value: !Sub ${Product}-${Environment}-dopt

  DHCPOptionsAssociation1:
    Type: AWS::EC2::VPCDHCPOptionsAssociation
    Properties:
      VpcId: !Ref VPC
      DhcpOptionsId: !Ref DHCPOptions

  #------------------------------
  # Internet Gateway
  #------------------------------
  # Internetgateway
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
      - Key: Name
        Value: !Sub ${Product}-${Environment}-igw

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

  #------------------------------
  # NAT Gateway
  #------------------------------
  # EIP for NATGatewayA
  Pub1NATGatewayEIP:
    Type: AWS::EC2::EIP
    Properties: 
      Domain: vpc

  # NATGateway for privatesubnet
  Pub1NATGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt Pub1NATGatewayEIP.AllocationId
      SubnetId: !Ref PubSubnet1
      Tags:
      - Key: Name
        Value: !Sub ${Product}-${Environment}-natgwpri1
      - Key: Stack
        Value: !Sub ${Product}-${Environment}

  #------------------------------
  # Route Table
  #------------------------------
  # Public Subnet Route Table
  PubRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
      - Key: Name
        Value: !Sub ${Product}-${Environment}-pubrttbl

  # PrivateRoute Update
  PubRoute1: 
    Type: AWS::EC2::Route
    Properties: 
      RouteTableId: !Ref PubRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  # Route Table association for Public Subnet
  PubRouteTableAssociation3:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PubRouteTable
      SubnetId: !Ref PubSubnet1
  PubRouteTableAssociation4:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PubRouteTable
      SubnetId: !Ref PubSubnet2

  # Private Subnet Route Table
  PriRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
      - Key: Name
        Value: !Sub ${Product}-${Environment}-prirttbl

  # PrivateRoute Update
  PriRoute1:
    Type: AWS::EC2::Route
    Properties: 
      RouteTableId: !Ref PriRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref Pub1NATGateway

  # Route Table association for Private Subnet
  PriRouteTableAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PriRouteTable
      SubnetId: !Ref PriSubnet1
  PriRouteTableAssociation2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PriRouteTable
      SubnetId: !Ref PriSubnet2
````
</div></details>

### AWS Management Microsoft ADを構築する

WorkSpacesの利用にはAD環境が必要になります。既存のADを使用することもできますが、今回は新規でDirectory ServiceのManaged Microsoft ADを使用してAD環境を構築します。この手順まで実施できれば、最低限、WorkSpacesを使用する条件は整います。

MSADの構築に関しては以下のyamlを流してください。
決めるパラメータは以下の通りです。ドメインAdminのパスワード(AdminPassword)はSecrets Managerに保存するようにしています。

|パラメータ名|説明|記入例|
|:---|:---|:---|
|Product|製品名を入力。特になければtest等を入力。|test|
|Environment|dev, stg, prdから選択|dev|
|VpcId|先のVPC.yamlで作成したVPCIDを選択してください|vpc-xxxxxxxxxx|
|SubnetIds|先のVPC.yamlで作成したPrivate Subnetを2つ選択してください|subnet-xxxxxx, subnet-xxxxxx|
|DirectoryName|作成するドメイン名を入力してください|test.local|
|AdminPassword|ドメインAdminのパスワードを入力してください|p@ssw0rd|

<details><summary>DirectoryService-MSAD.yaml</summary><div>

````yaml
---
AWSTemplateFormatVersion: 2010-09-09
Description: Directory Service - Microsoft Active Directory

#------------------------------
# Parameters: Set your argv.
#------------------------------
Parameters:
  # Common
  Product:
    Type: String
    Description: "[Required]Specify the product name"
  Environment:
    Type: String
    Description: "[Required]Specify the environment name(prd, stg, dev)"
    AllowedValues:
    - prd
    - stg
    - dev
  # Network
  VpcId:
    Type: AWS::EC2::VPC::Id
    Description: "[Required]Specify the VPC ID."
  SubnetIds:
    Type: List<AWS::EC2::Subnet::Id>
    Description: The subnets where Directory Service can be created.
  # MSAD Settings
  DirectoryName:
    Type: String
    Description: The Active Directory name.
  AdminPassword:
    Type: String
    Description: The Active Directory Admin's password.
    NoEcho: true

#------------------------------
# Metadata: Your resource list
#------------------------------
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
    - Label:
        default: System Configuration
      Parameters:
      - Product
      - Environment
    - Label:
        default: Network Configuration
      Parameters:
      - VpcId
      - SubnetIds
    - Label:
        default: MSAD Configuration
      Parameters:
      - DirectoryName
      - AdminPassword

#------------------------------
# Resources: Your resource list
#------------------------------
Resources:
  # Secrets Manager
  MSADPassword:
    Type: AWS::SecretsManager::Secret
    Properties: 
      Name: !Sub "/${Product}/${Environment}/MSADPassword"
      Description: !Sub "Active Directory(${DirectoryName}) Admin's password"
      SecretString: !Ref AdminPassword

  # Directory Service
  MSAD:
    Type: AWS::DirectoryService::MicrosoftAD
    Properties: 
      Edition: Standard
      EnableSso: false
      Name: !Ref DirectoryName
      CreateAlias: true
      Password: !Sub "{{resolve:secretsmanager:${MSADPassword}:SecretString}}"
      VpcSettings: 
        VpcId: !Ref VpcId
        SubnetIds: !Ref SubnetIds
````
</div></details>

### 管理用Windows Serverを構築する

MSADを構築した後は、MSADの設定を実施する管理用Windowsが必要になります。その管理用端末をWindows Server 2019で構築します。

Windows Serverの構築に関しては以下のyamlを流してください。
決めるパラメータは以下の通りです。今回はSSM経由でログインするため、セキュリティグループはdefault(defaultのSGに所属しているものからのみアクセスを許可)を使用します。
※t3.mediummは小さすぎるのでもう少し大きくても良いかもしれません。

|パラメータ名|説明|記入例|
|:---|:---|:---|
|Product|製品名を入力。特になければtest等を入力。|test|
|Environment|dev, stg, prdから選択|dev|
|SubnetId|先のVPC.yamlで作成したPrivate Subnetを1つ選択してください|subnet-xxxxxx|
|ImageId|Windows Server 2019のAMI IDを入力してください|ami-0677267cf17ab376d|
|KeyName|Administatorのパスワード復号に使用するキーペアを選択してください|test.pem|
|SecurityGroups|VPCを作成した際に作成されるdefaultのSGを選択してください|sg-xxxxxxxxxx|

<details><summary>EC2-MSADManage.yaml</summary><div>

````yaml
---
AWSTemplateFormatVersion: 2010-09-09
Description: EC2 - Windows Server - Manage MSAD

#------------------------------
# Parameters: Set your argv.
#------------------------------
Parameters:
  # Common
  Product:
    Type: String
    Description: "[Required]Specify the product name"
  Environment:
    Type: String
    Description: "[Required]Specify the environment name(prd, stg, dev)"
    AllowedValues:
    - prd
    - stg
    - dev
  # EC2
  SubnetId:
    Type: AWS::EC2::Subnet::Id
    Description: The subnet where the windows server can be created.
  ImageId:
    Type: AWS::EC2::Image::Id
    Description: The image id.
    Default: ami-0677267cf17ab376d # Microsoft Windows Server 2019 Base
  KeyName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: The name of an existing Amazon EC2 key pair to enable RDP.
  SecurityGroups:
    Type: List<AWS::EC2::SecurityGroup::Id>
    Description: The list of SecurityGroupIds in your Virtual Private Cloud (VPC)

#------------------------------
# Metadata: Your resource list
#------------------------------
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
    - Label:
        default: System Configuration
      Parameters:
      - Product
      - Environment
    - Label:
        default: EC2 Configuration
      Parameters:
      - SubnetId
      - ImageId
      - KeyName
      - SecurityGroups

#------------------------------
# Resources: Your resource list
#------------------------------
Resources:
  # IAM
  ## Instance Profile
  InstanceProfileMSADManage:
    Type: AWS::IAM::InstanceProfile
    Properties: 
      InstanceProfileName: !Sub "${Product}-${Environment}-iam-MSADManage"
      Roles: 
      - !Ref IAMRoleMSADManage

  ## IAMRole
  IAMRoleMSADManage:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${Product}-${Environment}-iam-role-MSADManage"
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
        - Effect: Allow
          Action: sts:AssumeRole
          Principal:
            Service: ec2.amazonaws.com
      ManagedPolicyArns:
      - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
      Path: "/"

  # EC2
  WindowsServer:
    Type: AWS::EC2::Instance
    Metadata:
      AWS::CloudFormation::Init:
        config:
          files:
            c:\\cfn\\cfn-hup.conf:
              content: !Sub |
                [main]
                stack=${AWS::StackId}
                region=${AWS::Region}
            c:\\cfn\\hooks.d\\cfn-auto-reloader.conf:
              content: !Sub |
                [cfn-auto-reloader-hook]
                triggers=post.update
                path=Resources.WindowsServer.Metadata.AWS::CloudFormation::Init
                action=cfn-init.exe -v --stack ${AWS::StackName} --resource WindowsServer --region ${AWS::Region}
          services:
            windows:
              cfn-hup:
                enabled: true
                ensureRunning: true
                files:
                - c:\\cfn\\cfn-hup.conf
                - c:\\cfn\\hooks.d\\cfn-auto-reloader.conf
    Properties:
      BlockDeviceMappings:
      - DeviceName: /dev/sda1
        Ebs:
          VolumeSize: 30
          VolumeType: gp2
          Encrypted: true
      DisableApiTermination: true
      CreditSpecification:
        CPUCredits: standard
      ImageId: !Ref ImageId
      InstanceInitiatedShutdownBehavior: stop
      InstanceType: t3.medium
      IamInstanceProfile: !Ref InstanceProfileMSADManage
      KeyName: !Ref KeyName
      SecurityGroupIds: !Ref SecurityGroups
      SubnetId: !Ref SubnetId
      Tags:
      - Key: Name
        Value: !Sub "${Product}-${Environment}-MSADManage"
      UserData:
        Fn::Base64: !Sub |
          <powershell>
          #  Rename Hostname
          Rename-Computer -NewName "${Product}-${Environment}-MSADManage" -Force -Restart
          cfn-init.exe -v --stack ${AWS::StackName} --resource WindowsServer --region ${AWS::Region}
          cfn-signal.exe -e $lastexitcode --stack ${AWS::StackName} --resource WindowsServer --region ${AWS::Region}
          </powershell>
````
</div></details>

#### 管理用Windows Serverへのログイン

セキュリティグループでインバウンド通信を許可していないため、SSM経由でのログインとなります。SSMでWindowsにログインする際はポートフォワーディングが必要になるため注意してください。またローカルのpowershellでssm agentを操作するためには別途プラグインが必要になります。
(参考)[Install the Session Manager Plugin on Windows](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html#install-plugin-windows)

````powershell
PS> aws ssm start-session --target i-xxxxxxxxxx `
--document-name AWS-StartPortForwardingSession `
--parameters '{\"portNumber\":[\"3389\"],\"localPortNumber\":[\"13389\"]}'

Starting session with SessionId: username-xxxxxxxxxx
Port 13389 opened for sessionId username-xxxxxxxxxx
````

ポートフォワーディングをした後、先ほど設定したポート（今回はlocalhost:13389）にRDP接続すると、管理用Windows Serverにログインできます。

![20200414_222207.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/396510/57acf7c2-1ef6-7ff3-76f2-3a814e34c590.jpeg)

#### 管理用Windows Serverの設定

詳細な手順は省略しますが、実施することは以下2点です。グループポリシーの設定などは後述します。

- 管理用Windows Serverのドメイン参加（作成したMSADのドメインに参加する）
- 役割と機能の追加
   - Group Policy Management
   - Remote Server Administration Tools
      - Role Administration Tools
         - AD DS and AD LDS Tools
         - DNS

(参考)[Install the Active Directory Administration Tools on Windows Server 2019](https://docs.aws.amazon.com/ja_jp/directoryservice/latest/admin-guide/ms_ad_install_ad_tools.html#install_ad_tools_win2019)


### Security Groupsの作成（Proxyサーバ、WorkSpaces用）

※この項目は[Amazon WorkSpaces の IP アドレスとポートの要件](https://docs.aws.amazon.com/ja_jp/workspaces/latest/adminguide/workspaces-port-requirements.html)から読んだ情報を自分なりに整理してみたものですが、完璧かどうか自信はありません。

Proxyサーバ、WorkSpacesに設定するセキュリティグループを設定します。
WorkSpacesに関してはここで新規で作成し、アウトバウンドを制限しますが、デフォルトで付与されているグループ(d-xxxxxxxxxx_workspacesMembers)でアウトバウンド通信を全て許可しているため、アウトバウンドのルールを削除してください。
ここで新規で作成しますが、既存の

- Proxyサーバ
   - インバウンド通信　：WorkSpaceからの3128のみを許可
   - アウトバウンド通信：全て許可
- WorkSpaces
   - インバウンド通信　：基本的に許可しない
   - アウトバウンド通信：[Amazon WorkSpaces の IP アドレスとポートの要件](https://docs.aws.amazon.com/ja_jp/workspaces/latest/adminguide/workspaces-port-requirements.html)の通信を許可、Proxyサーバへの3128を許可

セキュリティグループの構築に関しては以下のyamlを流してください。
決めるパラメータは以下の通りです。

|パラメータ名|説明|記入例|
|:---|:---|:---|
|Product|製品名を入力。特になければtest等を入力。|test|
|Environment|dev, stg, prdから選択|dev|
|VpcId|先のVPC.yamlで作成したVPCIDを選択してください|vpc-xxxxxxxxxx|
|MSADSecurityGroup|MSADを構築した際に作成されるデフォルトのセキュリティグループ(d-xxxxxxxxxx_controllers)を指定してください|sg-xxxxxxxxxx|

<details><summary>SecurityGroup.yaml</summary><div>

````yaml
---
AWSTemplateFormatVersion: 2010-09-09
Description: SecurityGroups - WorkSpaces

#------------------------------
# Parameters: Set your argv.
#------------------------------
Parameters:
  # Common
  Product:
    Type: String
    Description: "[Required]Specify the product name"
  Environment:
    Type: String
    Description: "[Required]Specify the environment name(prd, stg, dev)"
    AllowedValues:
    - prd
    - stg
    - dev
  # Network
  VpcId:
    Type: AWS::EC2::VPC::Id
    Description: "[Required]Specify the VPC ID."
  MSADSecurityGroup:
    Type: AWS::EC2::SecurityGroup::Id
    Description: Directory Service securityGroup Id.

#------------------------------
# Metadata: Your resource list
#------------------------------
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
    - Label:
        default: System Configuration
      Parameters:
      - Product
      - Environment
    - Label:
        default: Network Configuration
      Parameters:
      - VpcId
      - MSADSecurityGroup

#------------------------------
# Resources: Your resource list
#------------------------------
Resources:
  # Squid
  SecurityGroupSquid:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub "${Product}-${Environment}-ec2-securitygroup-squid"
      GroupDescription: WorkSpaces access for Squid.
      SecurityGroupIngress:
      - SourceSecurityGroupId: !Ref SecurityGroupWorkSpaces
        Description: TCP Access from WorkSpaces
        IpProtocol: TCP
        FromPort: 3128
        ToPort: 3128
      VpcId: !Ref VpcId
      Tags:
      - Key: Name
        Value: !Sub "${Product}-${Environment}-ec2-securitygroup-squid"

  # WorkSpaces
  SecurityGroupWorkSpaces:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub "${Product}-${Environment}-ec2-securitygroup-workspaces"
      GroupDescription: WorkSpaces securitygroup.
      SecurityGroupEgress:
      # workspaces client access
      - CidrIp: 18.178.102.247/32
        Description: PCoIP health check drp-nrt.amazonworkspaces.com.
        IpProtocol: TCP
        FromPort: 4172
        ToPort: 4172
      - CidrIp: 18.178.102.247/32
        Description: PCoIP health check drp-nrt.amazonworkspaces.com.
        IpProtocol: UDP
        FromPort: 4172
        ToPort: 4172
      - CidrIp: 54.250.251.0/24
        Description: PCoIP gateway.
        IpProtocol: TCP
        FromPort: 4172
        ToPort: 4172
      - CidrIp: 54.250.251.0/24
        Description: PCoIP gateway.
        IpProtocol: UDP
        FromPort: 4172
        ToPort: 4172
      # Directory Service access
      - DestinationSecurityGroupId: !Ref MSADSecurityGroup
        Description: DNS access to Directory Service.
        IpProtocol: TCP
        FromPort: 53
        ToPort: 53
      - DestinationSecurityGroupId: !Ref MSADSecurityGroup
        Description: DNS access to Directory Service.
        IpProtocol: UDP
        FromPort: 53
        ToPort: 53
      - DestinationSecurityGroupId: !Ref MSADSecurityGroup
        Description: Kerberos authentication access to Directory Service.
        IpProtocol: TCP
        FromPort: 88
        ToPort: 88
      - DestinationSecurityGroupId: !Ref MSADSecurityGroup
        Description: Kerberos authentication access to Directory Service.
        IpProtocol: UDP
        FromPort: 88
        ToPort: 88
      - DestinationSecurityGroupId: !Ref MSADSecurityGroup
        Description: NTP access to Directory Service.
        IpProtocol: UDP
        FromPort: 123
        ToPort: 123
      - DestinationSecurityGroupId: !Ref MSADSecurityGroup
        Description: RPC access to Directory Service.
        IpProtocol: TCP
        FromPort: 135
        ToPort: 135
      - DestinationSecurityGroupId: !Ref MSADSecurityGroup
        Description: Netlogon access to Directory Service.
        IpProtocol: UDP
        FromPort: 137
        ToPort: 138
      - DestinationSecurityGroupId: !Ref MSADSecurityGroup
        Description: Netlogon access to Directory Service.
        IpProtocol: TCP
        FromPort: 139
        ToPort: 139
      - DestinationSecurityGroupId: !Ref MSADSecurityGroup
        Description: LDAP access to Directory Service.
        IpProtocol: TCP
        FromPort: 389
        ToPort: 389
      - DestinationSecurityGroupId: !Ref MSADSecurityGroup
        Description: LDAP access to Directory Service.
        IpProtocol: UDP
        FromPort: 389
        ToPort: 389
      - DestinationSecurityGroupId: !Ref MSADSecurityGroup
        Description: SMB access to Directory Service.
        IpProtocol: TCP
        FromPort: 445
        ToPort: 445
      - DestinationSecurityGroupId: !Ref MSADSecurityGroup
        Description: SMB access to Directory Service.
        IpProtocol: UDP
        FromPort: 445
        ToPort: 445
      - DestinationSecurityGroupId: !Ref MSADSecurityGroup
        Description: Dynamic ports for RPC access to Directory Service.
        IpProtocol: TCP
        FromPort: 1024
        ToPort: 65535
      # Security Group info
      VpcId: !Ref VpcId
      Tags:
      - Key: Name
        Value: !Sub "${Product}-${Environment}-ec2-securitygroup-workspaces"

  SecurityGroupWorkSpacesEgress:
    Type: AWS::EC2::SecurityGroupEgress
    Properties:
      GroupId: !Ref SecurityGroupWorkSpaces
      SourceSecurityGroupId: !Ref SecurityGroupSquid
      Description: Proxy access.
      IpProtocol: TCP
      FromPort: 3128
      ToPort: 3128
````
</div></details>

#### WorkSpaces環境へのセキュリティグループの適用

WorkSpacesには追加のセキュリティグループを設定することが可能です。先ほど作成したWorkSpaces用のセキュリティグループを以下の手順を参考にしながら付与します。
[WorkSpaces のセキュリティグループ](https://docs.aws.amazon.com/ja_jp/workspaces/latest/adminguide/amazon-workspaces-security-groups.html)


### Proxyサーバ(Squid)を構築する

Proxyサーバとして使用するSquidを構築します。事前準備として、S3バケット（${Product}-${Environment}-squidlog）を作成しておいてください。Squidのaccess.logをS3に保存するためのバケットとなります。

Proxyサーバの構築に関しては以下のyamlを流してください。
決めるパラメータは以下の通りです。
各WorkSpacesからProxyサーバに対してアクセスが必要なため、セキュリティグループは事前に作成したものを指定します。
SquidのログをS3に保存しておきたかったため、インスタンスプロファイルにはAmazonSSMManagedInstanceCore以外にも、S3へのアクセス許可を付与しました。
EIPはWorkSpacesのProxyに指定するIPを固定するために指定しました。本来ならProxyは冗長化してELBやRoute53を指定した方が良いと思います。

|パラメータ名|説明|記入例|
|:---|:---|:---|
|Product|製品名を入力。特になければtest等を入力。|test|
|Environment|dev, stg, prdから選択|dev|
|SubnetId|先のVPC.yamlで作成したPublic Subnetを1つ選択してください|subnet-xxxxxx|
|ImageId|Amazon Linux 2のAMI IDを入力してください|ami-052652af12b58691f|
|SecurityGroupId|Squidに付与するSGを選択してください|sg-xxxxxxxxxx|

<details><summary>EC2-Squid.yaml</summary><div>

````yaml
---
AWSTemplateFormatVersion: 2010-09-09
Description: EC2 - Squid

#------------------------------
# Parameters: Set your argv.
#------------------------------
Parameters:
  # Common
  Product:
    Type: String
    Description: "[Required]Specify the product name"
  Environment:
    Type: String
    Description: "[Required]Specify the environment name(prd, stg, dev)"
    AllowedValues:
    - prd
    - stg
    - dev
  # EC2
  SubnetId:
    Type: AWS::EC2::Subnet::Id
    Description: The subnet where the windows server can be created.
  ImageId:
    Type: AWS::EC2::Image::Id
    Description: The AmazonLinux2 image id.
    Default: ami-052652af12b58691f # Amazon Linux 2 AMI (HVM), SSD Volume Type
  SecurityGroupId:
    Type: AWS::EC2::SecurityGroup::Id
    Description: The SecurityGroupId for squid server.

#------------------------------
# Metadata: Your resource list
#------------------------------
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
    - Label:
        default: System Configuration
      Parameters:
      - Product
      - Environment
    - Label:
        default: EC2 Configuration
      Parameters:
      - SubnetId
      - ImageId
      - SecurityGroupId

#------------------------------
# Resources: Your resource list
#------------------------------
Resources:
  # IAM
  ## Instance Profile
  InstanceProfileSquid:
    Type: AWS::IAM::InstanceProfile
    Properties: 
      InstanceProfileName: !Sub "${Product}-${Environment}-iam-WorkSpaces-Squid"
      Roles: 
      - !Ref IAMRoleSquid

  ## IAMRole
  IAMRoleSquid:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${Product}-${Environment}-iam-WorkSpaces-Squid"
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
        - Effect: Allow
          Action: sts:AssumeRole
          Principal:
            Service: ec2.amazonaws.com
      ManagedPolicyArns:
      - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
      - !Ref ManagedPolicy1
      Path: "/"

  ## S3 put policy
  ManagedPolicy1:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      PolicyDocument:
        Version: 2012-10-17
        Statement:
        - Effect: Allow
          Action:
          - s3:ListAllMyBuckets
          - s3:AbortMultipartUpload
          - s3:RestoreObject
          - s3:GetBucketNotification
          - S3:GetBucketPolicy
          Resource: arn:aws:s3:::*
        - Effect: Allow
          Action:
          - s3:ListBucket
          - s3:GetBucketLocation
          - s3:PutBucketNotification
          Resource: !Sub "arn:aws:s3:::${Product}-${Environment}-squidlog"
        - Effect: Allow
          Action:
          - s3:PutObject
          - s3:GetObject
          Resource: !Sub "arn:aws:s3:::${Product}-${Environment}-squidlog/*"

  # Squid
  SquidServer:
    Type: AWS::EC2::Instance
    Metadata:
      AWS::CloudFormation::Init:
        config:
          packages:
            yum:
              squid: []
          files:
            /etc/cfn/cfn-hup.conf:
              content: !Sub |
                [main]
                stack=${AWS::StackId}
                region=${AWS::Region}
            /etc/cfn/hooks.d/cfn-auto-reloader.conf:
              content: !Sub |
                [cfn-auto-reloader-hook]
                triggers=post.update
                path=Resources.SquidServer.Metadata.AWS::CloudFormation::Init
                action=/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource SquidServer --region ${AWS::Region}
          services:
            sysvinit:
              cfn-hup:
                enabled: true
                ensureRunning: true
                files:
                - /etc/cfn/cfn-hup.conf
                - /etc/cfn/hooks.d/cfn-auto-reloader.conf
              squid:
                enabled: true
                ensureRunning: true
    Properties:
      BlockDeviceMappings:
      - DeviceName: /dev/xvda
        Ebs:
          VolumeSize: 8
          VolumeType: gp2
          Encrypted: true
      DisableApiTermination: true
      CreditSpecification:
        CPUCredits: standard
      ImageId: !Ref ImageId
      InstanceInitiatedShutdownBehavior: stop
      InstanceType: t3.small
      IamInstanceProfile: !Ref InstanceProfileSquid
      SecurityGroupIds:
      - !Ref SecurityGroupId
      SubnetId: !Ref SubnetId
      Tags:
      - Key: Name
        Value: !Sub "${Product}-${Environment}-Squid"
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash

          # set hostname
          hostnamectl set-hostname "${Product}-${Environment}-Squid"

          # start cloud Init
          /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource SquidServer --region ${AWS::Region}
          /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource SquidServer --region ${AWS::Region}

  # EIP for Squid
  SquidEIP:
    Type: AWS::EC2::EIP
    Properties: 
      Domain: vpc

  EIPAssociation:
    Type: AWS::EC2::EIPAssociation
    Properties:
      InstanceId: !Ref SquidServer
      EIP: !Ref SquidEIP
````
</div></details>

#### Proxy(Squid)の設定

上述のCFnでProxyサーバを構築すると、Squidの設定ファイルは/etc/squid/squid.confにあるかと思います。Blacklistの設定やログフォーマット、世代数の設定を追記していきます。

##### squidの設定

/etc/squid/acl/blacklist.txtを作成し、その中にアクセスを拒否したいドメイン名を記載します。その後、squid.confにてhttp_access denyの設定やsshもproxy経由で許可する設定を加えます。
ログに関してはフォーマットの修正と、ローカルに保存する世代数の設定を実施しています。ローテーション自体は次のcronの設定で実施しています。

````bash
# cd /etc/squid/
# pwd
/etc/squid/
# mkdir acl
# vi acl/blacklist.txt
接続を拒否したいサイトのドメイン名を記載。
(例)
example.com
# vi squid.conf
# diff squid.conf squid.conf.default
14,17d13
< # user settings: listfile
< acl Blacklist dstdomain "/etc/squid/acl/blacklist.txt"      # Blacklistを定義
<
< acl SSL_ports port 22           # user settings: ssh        # sshもproxy経由でアクセスできるよう許可
21d16
< acl Safe_ports port 22          # user settings: ssh        # sshもproxy経由でアクセスできるよう許可
41,43d35
< # user settings: Blacklist
< http_access deny Blacklist      # Blacklistで定義したファイルに記載されているドメインへのアクセスを拒否
<
82,87d73
<
< # user settings: logformat
< logformat squid %tl %>a %Ss/%03>Hs %<st %rm %ru %[un %Sh/%<a %mt        # Squidのログフォーマットを修正
<
< # user settings: logrotate
< logfile_rotate 8                                                        # Squidのログのローカルに保存する世代数を設定
# systemctl restart squid
# 
````

##### cronの設定

cronで日次でsquidログのローテーション、ローテーションした後に、ローテーションした5分後にログファイルをS3にコピーしています。

````bash
# crontab -l
0 0 * * * /usr/sbin/squid -k rotate
5 0 * * * /usr/bin/aws s3 cp /var/log/squid/access.log.0 s3://your_bucket_name/$(date +\%Y)/$(date +\%m)/$(date +\%d)/access.log
#
````

### WorkSpacesを構築する

### セキュリティ周りの実装

#### WorkSpacesにアクセスできるIPを制限する

WorkSpacesにアクセスできるIPを制限することができます。こちらは以下が参考になりました。
いつも拝見させて頂いているClassmethod社のDevelopers.IOの記事を参考リンクとして記載させて頂きます。

(AWS)[WorkSpaces の IP アクセスコントロールグループ](https://docs.aws.amazon.com/ja_jp/workspaces/latest/adminguide/amazon-workspaces-ip-access-control-groups.html)
(Developers.IO)[【新機能】WorkSpacesにIPベースのアクセス制御機能が追加されました！](https://dev.classmethod.jp/articles/workspaces-ip-base-access-control/)

#### グループポリシーの制御

WorkSpaces環境におけるグループポリシーの制御は以下が参考になりました。
Classmethod社のDevelopers.IOはクリップボードコピーの制御のみの記載でしたが、ここで勘所を掴めたら、その他の設定もすぐにできるかと思います。

(AWS)[グループポリシーを使用した Windows WorkSpaces の管理](https://docs.aws.amazon.com/ja_jp/workspaces/latest/adminguide/group_policy.html)
(Developers.IO)[グループポリシーを使用してWorkSpacesの制御をしてみた](https://dev.classmethod.jp/articles/setting_grouppolicy_to_workspaces/)

#### WorksDocsを使用するのであれば：WorkDocsにアクセスできるIPを制限する

WorkSpaces上でのファイル共有などを考えた場合、WorkDocsを使用するのが簡単だと思います。
しかし、WorkSpacesからWorkDocsにデータを格納し、自宅PCからWorkDocsにアクセスして情報漏洩なんてことになったら目も当てられません。WorkDocsにもIP制限をすることができるため、もし使用する場合は制限すると良いかもしれません。

(AWS)[IP allow list](https://docs.aws.amazon.com/workdocs/latest/adminguide/manage-sites.html#ipfiltering)
(Developers.IO)[Amazon WorkDocsでIPアドレス制限が可能になりました](https://dev.classmethod.jp/articles/amazon-workdocs-ip-allow-list/)

### まとめ

今回はWorkSpacesを用いた環境構築というテーマで記事を書きました。
最後がリンクを張っただけじゃん、というのは大目に見て頂ければと思います。

- セキュリティ面で穴があるかもしれない
- 耐障害性をもっと考えた方が良いかもしれない
- コストを抑えられる方法があるかもしれない
- もっと楽に構築できる方法があるかもしれない

…etc
考え出すともっと良い方法があるのではないかと考えられますが、自分の構築メモと頭の整理用にまとめてみました。
在宅勤務等を駆使して、コロナを乗り切りましょう！