[2019年度版] 自己署名証明書のRD GatewayでWindowsサーバに接続

しばたです。

2016年に弊社西澤によりシングルのRD Gateway環境を構築し自己証明書を使い接続する内容の記事が公開されましたが、諸事情により私も同様の環境を構築する必要があったためOSや作業手順を新たに更新してみました。

自己署名証明書のRD GatewayでWindowsサーバに接続

今回構築する環境

今回構築する環境は前回と同様にシングルのRD Gatewayサーバーと接続対象となるEC2インスタンス1台とします。
Active Directoryは使わないワークグループ環境、EC2インスタンスのOSはWindows Server 2019と最新のOSを採用します。
図としてはざっくり以下の様な感じです。

環境構築のためのCloudFormationテンプレート

今回の環境を作るためのCloudFormationテンプレートを公開します。
上図を構成する最低限の定義しかありませんのであくまでも参考情報とお考えください。

01. ネットワーク構成テンプレート

  • VPC
  • IGW
  • Subnet
  • NACL
  • RouteTable

の定義が記述されています。

01. ネットワーク構成テンプレート
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  SystemName:
    Description: "System name of each resource names."
    Type: String
    Default: "contoso"
  EnvironmentName:
    Description: "Environment name of each resource names."
    Type: String
    Default: "prd"
  AZ1:
    Description: "AZ1(1a)"
    Type: AWS::EC2::AvailabilityZone::Name
    Default: "ap-northeast-1a"
  AZ2:
    Description: "AZ2(1c)"
    Type: AWS::EC2::AvailabilityZone::Name
    Default: "ap-northeast-1c"
Outputs:
  VPC1:
    Value:
      Ref: VPC1
    Export:
      Name:
        Fn::Sub: "${SystemName}-${EnvironmentName}-vpc"
  PublicSubnet1:
    Value:
      Ref: PublicSubnet1
    Export:
      Name:
        Fn::Sub: "${SystemName}-${EnvironmentName}-public-subnet-a"
  PublicSubnet2:
    Value:
      Ref: PublicSubnet2
    Export:
      Name:
        Fn::Sub: "${SystemName}-${EnvironmentName}-public-subnet-c"
  PrivateSubnet1:
    Value:
      Ref: PrivateSubnet1
    Export:
      Name:
        Fn::Sub: "${SystemName}-${EnvironmentName}-private-subnet-a"
  PrivateSubnet2:
    Value:
      Ref: PrivateSubnet2
    Export:
      Name:
        Fn::Sub: "${SystemName}-${EnvironmentName}-private-subnet-c"
  IGW1:
    Value:
      Ref: IGW1
    Export:
      Name:
        Fn::Sub: "${SystemName}-${EnvironmentName}-vgw"
Resources:
  # VPC
  VPC1:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 192.168.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value:
            Fn::Sub: "${SystemName}-${EnvironmentName}-vpc"
  # Subnet
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: 
        Ref: AZ1
      VpcId: 
        Ref: VPC1
      CidrBlock: 192.168.11.0/24
      MapPublicIpOnLaunch: False
      Tags:
        - Key: Name
          Value: 
            Fn::Sub: "${SystemName}-${EnvironmentName}-public-subnet-a"
  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: 
        Ref: AZ2
      VpcId: 
        Ref: VPC1
      CidrBlock: 192.168.12.0/24
      MapPublicIpOnLaunch: False
      Tags:
        - Key: Name
          Value: 
            Fn::Sub: "${SystemName}-${EnvironmentName}-public-subnet-c"
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: 
        Ref: AZ1
      VpcId: 
        Ref: VPC1
      CidrBlock: 192.168.21.0/24
      MapPublicIpOnLaunch: False
      Tags:
        - Key: Name
          Value: 
            Fn::Sub: "${SystemName}-${EnvironmentName}-private-subnet-a"
  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: 
        Ref: AZ2
      VpcId: 
        Ref: VPC1
      CidrBlock: 192.168.22.0/24
      MapPublicIpOnLaunch: False
      Tags:
        - Key: Name
          Value: 
            Fn::Sub: "${SystemName}-${EnvironmentName}-private-subnet-c"
  # NACL (Default only)
  NACL1:
    Type: AWS::EC2::NetworkAcl
    Properties:
      Tags:
        - Key: Name
          Value:
            Fn::Sub: "${SystemName}-${EnvironmentName}-nacl"
      VpcId:
        Ref: VPC1
  NACLEntry1:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      Egress: true
      CidrBlock: 0.0.0.0/0
      Protocol: -1
      RuleAction : allow
      RuleNumber : 100
      NetworkAclId:
        Ref: NACL1
  NACLEntry2:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      Egress: false
      CidrBlock: 0.0.0.0/0
      Protocol: -1
      RuleAction : allow
      RuleNumber : 100
      NetworkAclId:
        Ref: NACL1
  NACLAssociation11:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId: 
        Ref: PublicSubnet1 
      NetworkAclId:
        Ref: NACL1
  NACLAssociation12:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId: 
        Ref: PublicSubnet2
      NetworkAclId:
        Ref: NACL1
  NACLAssociation21:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId: 
        Ref: PrivateSubnet1 
      NetworkAclId:
        Ref: NACL1
  NACLAssociation22:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId: 
        Ref: PrivateSubnet2
      NetworkAclId:
        Ref: NACL1
  # Public RouteTable (Default + IGW)
  PublicRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: 
        Ref: VPC1
      Tags:
        - Key: Name
          Value:
            Fn::Sub: "${SystemName}-${EnvironmentName}-public-rtb"
  PublicRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId:
        Ref: PublicRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: IGW1
  PublicRouteAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: 
      RouteTableId:
        Ref: PublicRouteTable1
      SubnetId:
        Ref: PublicSubnet1
  PublicRouteAssociation2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: 
      RouteTableId:
        Ref: PublicRouteTable1
      SubnetId:
        Ref: PublicSubnet2
  # Private RouteTable (Default)
  PrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: 
        Ref: VPC1
      Tags:
        - Key: Name
          Value:
            Fn::Sub: "${SystemName}-${EnvironmentName}-private-rtb"
  PrivateRouteAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: 
      RouteTableId:
        Ref: PrivateRouteTable1
      SubnetId:
        Ref: PrivateSubnet1
  PrivateRouteAssociation2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: 
      RouteTableId:
        Ref: PrivateRouteTable1
      SubnetId:
        Ref: PrivateSubnet2
  # Internet Gateway
  IGW1:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: "${CompanyPrefix}-${EnvironmentName}-igw"
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId:  
        Ref: VPC1
      InternetGatewayId: 
        Ref: IGW1

02. セキュリティグループ用テンプレート

  • RD Gateway用にHTTPSを開けるグループ(rdgateway-sg)
  • RD Gateway <--> 接続対象サーバー間の通信用グループ (rdsession-sg)
  • 初期環境構築用にRDPポートを開放するグループ (rdp-sg)

が定義されています。

02. セキュリティグループ用テンプレート
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  SystemName:
    Description: "System name of each resource names."
    Type: String
    Default: "contoso"
  EnvironmentName:
    Description: "Environment name of each resource names."
    Type: String
    Default: "prd"
Outputs:
  RDGatewaySG:
    Value:
      Ref: RDGatewaySG
    Export:
      Name:
        Fn::Sub: "${SystemName}-${EnvironmentName}-rdgateway-sg"
  RDSessionSG:
    Value:
      Ref: RDSessionSG
    Export:
      Name:
        Fn::Sub: "${SystemName}-${EnvironmentName}-rdsession-sg"
Resources:
  # RDGateway
  RDGatewaySG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName:
        Fn::Sub: "${SystemName}-${EnvironmentName}-rdgateway-sg"
      GroupDescription:
        Fn::Sub: "${SystemName}-${EnvironmentName}-rdgateway-sg"
      VpcId:
        Fn::ImportValue:
          Fn::Sub: "${SystemName}-${EnvironmentName}-vpc"
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value:
            Fn::Sub: "${SystemName}-${EnvironmentName}-rdgateway-sg"
  # 接続対象サーバー用
  RDSessionSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName:
        Fn::Sub: "${SystemName}-${EnvironmentName}-rdsession-sg"
      GroupDescription:
        Fn::Sub: "${SystemName}-${EnvironmentName}-rdsession-sg"
      VpcId:
        Fn::ImportValue:
          Fn::Sub: "${SystemName}-${EnvironmentName}-vpc"
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3389
          ToPort: 3389
          SourceSecurityGroupId:
            Ref: RDGatewaySG
        - IpProtocol: udp
          FromPort: 3389
          ToPort: 3389
          SourceSecurityGroupId:
            Ref: RDGatewaySG
      Tags:
        - Key: Name
          Value:
            Fn::Sub: "${SystemName}-${EnvironmentName}-rdsession-sg"
  # 環境構築時に一時的にRDPのポートを開くためのグループ
  RDPSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName:
        Fn::Sub: "${SystemName}-${EnvironmentName}-rdp-sg"
      GroupDescription:
        Fn::Sub: "${SystemName}-${EnvironmentName}-rdp-sg"
      VpcId:
        Fn::ImportValue:
          Fn::Sub: "${SystemName}-${EnvironmentName}-vpc"
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3389
          ToPort: 3389
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value:
            Fn::Sub: "${SystemName}-${EnvironmentName}-rdp-sg"

03. EC2インスタンス用テンプレート

  • RD Gateway + EIP
  • 接続対象サーバー

の定義が記述されています。 インスタンスタイプはt2.medium固定、EC2キーペアは事前に作成しておいてください。
どちらも最新の日本語版Windows Server 2019で、RD Gatewayの構築手順は次節で説明します。

03. EC2インスタンス用テンプレート
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  SystemName:
    Description: "System name of each resource names."
    Type: String
    Default: "contoso"
  EnvironmentName:
    Description: "Environment name of each resource names."
    Type: String
    Default: "prd"
  WindowsLatestAmi:
    Type : AWS::SSM::Parameter::Value<String>
    Default: /aws/service/ami-windows-latest/Windows_Server-2019-Japanese-Full-Base
    AllowedValues:
    - /aws/service/ami-windows-latest/Windows_Server-2016-Japanese-Full-Base
    - /aws/service/ami-windows-latest/Windows_Server-2019-Japanese-Full-Base
  KeyPairName:
    Description: "EC2 Keypair name."
    Default: "your-keypair"
    Type: String
Resources:
  # RDGateway
  RDGateway:
    Type: AWS::EC2::Instance
    Properties:
      ImageId:
        Ref: WindowsLatestAmi
      InstanceType: t2.medium
      KeyName:
        Fn::Sub: "${KeyPairName}"
      DisableApiTermination: False
      EbsOptimized: False
      BlockDeviceMappings:
        - DeviceName: /dev/sda1
          Ebs:
            VolumeSize: 30
            VolumeType: gp2
            Encrypted: False
      SecurityGroupIds:
        - Fn::ImportValue:
            Fn::Sub: "${SystemName}-${EnvironmentName}-rdgateway-sg"
      SubnetId:
        Fn::ImportValue:
          Fn::Sub: "${SystemName}-${EnvironmentName}-public-subnet-a"
      Tags:
        - Key: Name
          Value:
            Fn::Sub: "${SystemName}-${EnvironmentName}-rdgateweay"
  # EIP
  RDGatewayEIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      InstanceId:
        Ref: RDGateway
  # 接続対象サーバー
  RDSession:
    Type: AWS::EC2::Instance
    Properties:
      ImageId:
        Ref: WindowsLatestAmi
      InstanceType: t2.medium
      KeyName:
        Fn::Sub: "${KeyPairName}"
      DisableApiTermination: False
      EbsOptimized: False
      BlockDeviceMappings:
        - DeviceName: /dev/sda1
          Ebs:
            VolumeSize: 30
            VolumeType: gp2
            Encrypted: False
      SecurityGroupIds:
        - Fn::ImportValue:
            Fn::Sub: "${SystemName}-${EnvironmentName}-rdsession-sg"
      SubnetId:
        Fn::ImportValue:
          Fn::Sub: "${SystemName}-${EnvironmentName}-private-subnet-a"
      Tags:
        - Key: Name
          Value:
            Fn::Sub: "${SystemName}-${EnvironmentName}-rdsession"

RD Gatewayサーバーの環境設定

ここからRD Gatewayサーバーの環境構築を行います。
基本的に各種作業はすべてPowerShellで行い、設定した結果がどうなるかGUIで表示確認していきます。

作成したRD GatewayサーバーにRDPで接続します。
(前節で説明したrdp-sgセキュリティグループをよしなに設定して接続してください)

0. 事前準備

必須ではないのですがわかりやすさのためにコンピューター名を変えておきます。

# わかりやすさのためにコンピューター名を変更し再起動
Rename-Computer GATEWAY01 -Restart

1. 自己証明書の作成

前回の記事ではmakecertコマンドを使って自己証明書を作成していますが、Windows Server 2016以降であればNew-SelfSignedCertificateコマンドレットで証明書の有効期間も指定できる様になっています。
このため本記事ではNew-SelfSignedCertificateをつかって証明書の作成を行います。

SubjectやDNSNameにはインスタンスタイプのパブリックDNS名を指定しています。
こちらは環境に応じて変更してください。

# PowerShell 5.1以降
$params = @{
    Subject = 'CN=ec2-<your eip address>.ap-northeast-1.compute.amazonaws.com' # 今回はCNのみ設定。環境に合わせて変更してください
    DnsName = 'ec2-<your eip address>.ap-northeast-1.compute.amazonaws.com'    # DNS名は環境に合わせて変更してください
    TextExtension = @("2.5.29.37={text}1.3.6.1.5.5.7.3.1") # サーバー証明書のみ
    CertStoreLocation = "cert:\LocalMachine\My"
    KeyAlgorithm = 'RSA'
    KeyLength = 2048
    KeyExportPolicy = 'Exportable'
    NotAfter = (Get-Date).AddYears(5) # あまりよろしくないけど有効期間5年
}
$cert = New-SelfSignedCertificate @params
$cert

このコマンドにより証明書がローカルコンピューター\個人に作成されます。

また、この証明書をあとでクライアントで利用するため任意のディレクトリにエクスポートしておきます。

# 作成した証明書を rdgateway.cer という名前でエクスポート
Export-Certificate -FilePath '.\rdgateway.cer' -Cert "Cert:\LocalMachine\My\$($cert.Thumbprint)"

2. 接続用ユーザー、グループの作成

本来RD Gatewayを含めたRemote Desktop Serviceの各機能はActive Directory環境で使う前提となっています。
本記事の環境はワークグループ環境であるため、RD Gatewayに接続するためのユーザーと接続先サーバーに接続するためのユーザーを別々に管理する必要があります。

前回の記事ではRD Gatewayに接続するユーザーをローカルAdministratorsで設定していましたが、今回は接続専用のユーザーとグループを設けることにします。
Windows Server 2016以降であればローカルユーザー、グループを設定するコマンドレットが用意されていますので以下の様にして

  • グループ : GatewayUsers
  • ユーザー : User01

を作成することができます。

# 接続専用のグループとユーザーを作成
$password = ConvertTo-SecureString "P@ssw0rd" -AsPlainText -Force # 認証情報は適宜変更してください
New-LocalUser -Name User01 -Password $password
New-LocalGroup -Name GatewayUsers
Add-LocalGroupMember -Group GatewayUsers -Member User01

3. RD Gateway機能のインストール

ここから本題に入っていきます。
Install-WindowsFeatureコマンドレットを使いRD Gatewayの機能と管理ツールをインストールします。

# RD Gateway機能のインストール
Install-WindowsFeature RDS-Gateway -IncludeManagementTools

これによりRD Gatewayの機能とIISなどの周辺機能、RDゲートウェイマネージャーが追加されます。

機能の追加後に再起動は不要です。

4. RD Gateway SSL証明書の設定

次に「1. 自己証明書の作成」で作成した自己証明書をRD Gatewayに登録します。
前回はRDゲートウェイマネージャーのGUIから登録していましたが今回はPowerShellを使いコマンドで登録します。

RDSを管理するためのRemoteDesktopServicesモジュールをインポートするとRDSに関わる各種設定に対してRDS:\ドライブからアクセスできる様になります。
Set-Itemコマンドレットを使い以下の様にするとRD GatewayのSSL証明書を更新することができます。

# RDSモジュールのインポート
Import-Module RemoteDesktopServices

# 証明書のセット
Set-Item -Path 'RDS:\GatewayServer\SSLCertificate\Thumbprint' -Value $cert.Thumbprint

設定した結果をGUIから確認するとちゃんと反映されていることがわかります。

5. RD Gateway 接続承認ポリシー(CAP)の設定

次に接続承認ポリシー(CAP)を設定します。
CAPは「RD Gatewayに誰が接続できるか」を決めるポリシーとなります。

本記事では極力単純な設定とし、「2. 接続用ユーザー、グループの作成」で作成したGatewayUsersグループを接続可能にする設定にしておきます。
PowerShellで次の様してNew-Itemで新しいCAP設定を作成します。

# RDSモジュールのインポート (前項でしている場合は不要)
Import-Module RemoteDesktopServices

# 接続承認ポリシー作成
New-Item -Path 'RDS:\GatewayServer\CAP' -Name 'CAP01' -UserGroups "GatewayUsers@$(HOSTNAME)" -AuthMethod 1

登録した結果をGUIから確認してみると下図の様になります。

6. RD Gateway リソース承認ポリシー(RAP)の設定

続けてリソース承認ポリシー(RAP)を作成します。
RAPは「どのリソースにアクセス可能にするか」を決めるポリシーとなります。

こちらもCAPと同様にPowerShellで設定していきます。
単純にGatewayUsersグループがどのリソースにもアクセス可能な設定としています。

# RDSモジュールのインポート (前項でしている場合は不要)
Import-Module RemoteDesktopServices

# リソース承認ポリシー作成
New-Item -Path 'RDS:\GatewayServer\RAP' -Name 'RAP01' -UserGroups "GatewayUsers@$(HOSTNAME)" -ComputerGroupType 2

登録した結果をGUIから確認してみると下図の様になります。

以上でRD Gatewayの設定は完了です。
最後にRD Gatewayのサービス(TSGateway)を再起動して変更した設定を反映させます。

Stop-Service TSGateway
Start-Service TSGateway

クライアントからの接続確認

今回はWindows 10クライアントから実際に接続してみます。

1. 自己証明書のインポート

RD Gatewayを利用するには信頼されたサーバーに対してHTTPS接続する必要があり、本来であればSSL証明書は公に信頼されている必要があります。
今回は自己証明書を使っていますので、あまりよろしくないですが、自己証明書を信頼されたルート証明書機関にインポートして無条件に信頼する様にします。
本記事では細かい手順は端折りますが、接続元クライアントに「1. 自己証明書の作成」でエクスポートした証明書をインポートしておいてください。

2. 対象サーバーへ接続

これでクライアント側の準備は完了しましたので、実際に接続対象サーバーへ接続してみます。

リモートデスクトップクライアントを起動し、前節で構築したRDゲートウェイサーバーを使用する設定にします。

その上で接続先に接続対象サーバー(本記事では192.168.21.74)を指定します。
ユーザーは対象サーバーのユーザーを指定します。
今回は接続対象サーバーに一切設定をしていないため、デフォルトユーザーであるadministratorを指定しておきます。 

ここで「接続」ボタンをクリックし接続を開始すると最初にRD Gatewayサーバーに接続するための認証情報の入力を要求されます。

ここの認証情報は「2. 接続用ユーザー、グループの作成」で作成したユーザー(user01)を指定します。
何度か同様の入力を求められるので「このアカウントを記憶する」にチェックを付けておいた方が良いでしょう。
認証情報が間違っていなければ接続が継続され、

続けて対象サーバーに対する認証情報の入力を求められます。

  パスワードを入力し間違いがなければ下図の様にRDP接続できます。

これで無事接続が完了しました。

最後に

以上です。

Windows Server 2016以降であれば全ての作業をWindows Serverの標準機能でPowerShellから行うことが可能になっています。
本記事ではやりませんでしたがその気になればUserDataにスクリプトを仕込んですべての工程を自動化することもできるかと思います。

追記

自己証明書ではなくLet's Encryptの証明書を使ってRD Gatewayを構築する手順を別記事にまとめました。
よろしければこちらもご覧ください。

[2019年度版] Let’s Encryptの証明書を使ってRD GatewayでWindowsサーバに接続