AWSのClient VPNを利用する

利用する機会があったので、触った時のメモです。AWSのClinet VPNサービスとは、Managed OpenVPNサービスと呼べるものです。参考ドキュメントはこちら。

クライアント VPN の使用開始

構成

こんな感じです。VPNで接続するユーザーを、Simple ADのActiveDirectoryで管理する事になります。

f:id:goodbyegangster:20200209235600p:plain

認証の仕組み

Clinet VPNの認証処理は、下記2つの方法が用意されています。今回の例では、下記2つを併用します。

  • ActiveDirectory認証
  • 相互認証(Mutual TLS

ActiveDirectory認証は、後述のSimpleADを利用します。

相互認証とは、一般のHTTPS認証と、クライアントTLS認証を合わせた仕組みです。

相互認証とは、認証方式の一つで、双方の当事者が互いに相手の正当性を認証する方式。サーバやサービス提供者と、クライアントやサービス利用者が、相互に相手が正当な相手かどうか検証すること。通常の認証(片方向認証、単方向認証)では、クライアントがサーバの正当な利用者であることを検証するが、相互認証はクライアントから見て接続しているサーバが信用できる相手かどうか確認できない可能性がある状況で利用される。

相互認証 【 mutual authentication 】 双方向認証 / two-way authentication

f:id:goodbyegangster:20200209203858p:plain

【図解】クライアント証明書の仕組み~シーケンス、クライアント認証、メリット 〜

そのため、サーバーとクライアント双方それぞれで利用される証明書と鍵を作成する事になります。

ActiveDirectory認証の設定(Simple ADの作成)

Simple ADと、Simple ADを管理用のWindowsサーバーを作成します。

Simple ADとは、機能を制限されたAWS ManagedなActive Directoryサービスです。

Simple AD では、AWS Managed Microsoft AD の機能のサブセットを使用できます。たとえば、ユーザーアカウントやグループメンバーシップの管理、グループポリシーの作成および適用、Amazon EC2 インスタンスへの安全な接続を行うことができるだけでなく、Kerberos ベースのシングルサインオン (SSO) を使用できます。ただし、Simple AD では、他ドメインとの信頼関係、Active Directory 管理センター、PowerShell サポート、Active Directory のごみ箱、グループが管理するサービスアカウント、POSIX および Microsoft アプリケーションのスキーマ拡張などの機能はサポートされていません。

Simple Active Directory

Simple ADは、ユーザーアカウントを管理するためのコンソール画面が用意されておらず、通常のWindowsサーバー上にインストールされた リモートサーバー管理ツール から操作することになります。そのためのWindowsサーバーも用意します。

作成

Simple ADと管理用Windowsサーバーを作成するCloudFormationテンプレートを置いておきます。

https://github.com/goodbyegangster/cloudformation/tree/master/simple-ad

今回の例では sample.com というドメインを作成しています。

管理用Windowsサーバのドメイン参加

作成したWindowsサーバーを、Simple ADで作成したドメインに参加させます。今回はWindows2019のサーバーを作成しています。

ネットワーク接続設定画面を開いて、IPv4のプロパティ画面まで進みます。

> %SystemRoot%\system32\control.exe ncpa.cpl

参照するDNSサーバー先として、SimpleADが提供するDNSサーバーのIPアドレスを入力します。

f:id:goodbyegangster:20200209203929p:plain

システムプロパティの設定画面を開いて、参加ドメイン変更画面まで進みます。

> %SystemRoot%\system32\control.exe sysdm.cpl

参加するドメインを、SimpleADで作成したドメイン名に変更します。

f:id:goodbyegangster:20200209203944p:plain

設定を登録すると、ドメイン参加のための認証情報を求められるので、以下と入力します。

設定
ユーザー名 (ドメイン)\administrator ※今回の例では「example.com\administrator」
パスワード SimpleAD作成時に指定したもの ※上記CloudFormationテンプレートでは「Passw0rd」

OSを再起動します。

Windows インスタンスを手動で参加させる

ClinetVPN接続可能ユーザーの作成

管理可能となったSimpleADのドメイン内に、Clinet VPNで接続可能とするユーザーを追加します。まずドメインのadministratorユーザーで、Windowsサーバーにログインします。

管理者権限で、Powershellのプロンプトを開き、以下を実行します。リモートサーバー管理ツールたるRemote Server Access Tool(RSAT)は、Windowsコンポーネントとして用意されているため、それら一式をインストールします。

> Install-WindowsFeature -name RSAT -IncludeAllSubFeature

インストール後はOSを再起動します。

再起動後に再度サーバーにドメインadminユーザーでログインすると、Windows管理ツール内に ActiveDirectory ユーザーとコンピューター があるので、SimpleAD内のドメインを操作できるようなります。

f:id:goodbyegangster:20200209203956p:plain

ここより、ClinetVPNで利用するユーザーを作成します。なお、初期ログイン時にパスワード変更を求めるユーザー設定を実施すると、ClinetVPNへの接続をできなくなるので注意。

相互認証用の設定

各証明書と鍵の作成

相互認証で利用される各証明書と鍵を作成します。作成には easy-rsa を利用します。

easy-rsa is a CLI utility to build and manage a PKI CA. In laymen's terms, this means to create a root certificate authority, and request and sign certificates, including sub-CAs and certificate revocation lists (CRL).

OpenVPN/easy-rsa

easy-rsa 実行用のモジュールをダウンロードしてきます。

$ git clone https://github.com/OpenVPN/easy-rsa.git
$ cd easy-rsa/easyrsa3

PKI(Public Key Infrastructure)環境の初期化を行います。

$ ./easyrsa init-pki
init-pki complete; you may now create a CA or requests.
Your newly created PKI dir is: /home/zunda/openvpn/easy-rsa/easyrsa3/pki

証明書を発行するための認証局(Certification Authority)を作成します。作成途中で求められるCommon Nameは、ブランクで問題ありません。

$ ./easyrsa build-ca nopass
Using SSL: openssl OpenSSL 1.1.0g  2 Nov 2017
Generating RSA private key, 2048 bit long modulus
..........................+++
..................+++
e is 65537 (0x010001)
You are about to be asked to enter information that will be incorporatedinto your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank for some fields there will be a default value, If you enter '.', the field will be left blank.
-----
Common Name (eg: your user, host, or server name) [Easy-RSA CA]:

CA creation complete and you may now import and sign cert requests.
Your new CA certificate file for publishing is at:
/home/zunda/openvpn/easy-rsa/easyrsa3/pki/ca.crt

サーバー向けの証明書と鍵を作成します。デフォルトでは有効期限が825日で作成されるため、 EASYRSA_CERT_EXPIRE にて有効期限を365日に変更しています。また、AWS Clinet VPNで利用する証明書はパスワード不要で作成する必要があります。

$  export EASYRSA_CERT_EXPIRE=365
$ ./easyrsa build-server-full server nopass
Using SSL: openssl OpenSSL 1.1.0g  2 Nov 2017 (Library: OpenSSL 1.1.1  11 Sep 2018)
Generating a 2048 bit RSA private key
.........+++++
.........................+++++
writing new private key to '/home/zunda/easy-rsa/easyrsa3/pki/easy-rsa-506.K4EXuV/tmp.qwMm7I'
-----
Using configuration from /home/zunda/easy-rsa/easyrsa3/pki/easy-rsa-506.K4EXuV/tmp.Sy0ERj
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :ASN.1 12:'server'
Certificate is to be certified until Feb  8 01:28:14 2021 GMT (365 days)

Write out database with 1 new entries
Data Base Updated

続いて同様に、クライアント向けの証明書と鍵を作成します。

$ ./easyrsa build-client-full client nopass
Using SSL: openssl OpenSSL 1.1.0g  2 Nov 2017 (Library: OpenSSL 1.1.1  11 Sep 2018)
Generating a 2048 bit RSA private key
......+++++
...............................+++++
writing new private key to '/home/zunda/easy-rsa/easyrsa3/pki/easy-rsa-581.vaGaDx/tmp.jBGMpb'
-----
Using configuration from /home/zunda/easy-rsa/easyrsa3/pki/easy-rsa-581.vaGaDx/tmp.jNtfDm
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :ASN.1 12:'client'
Certificate is to be certified until Feb  8 01:29:34 2021 GMT (365 days)

Write out database with 1 new entries
Data Base Update

作成された以下ファイルを、一時フォルダにコピーします。

$ mkdir /tmp/zunda
$ cp pki/ca.crt /tmp/zunda/
$ cp pki/issued/server.crt /tmp/zunda/
$ cp pki/private/server.key /tmp/zunda/
$ cp pki/issued/client.crt /tmp/zunda/
$ cp pki/private/client.key /tmp/zunda/

クライアント認証と認可

ACMへのインポート

作成されたCA証明書、およびサーバー用証明書と鍵をACMにインポートします。

$ aws acm import-certificate \
--certificate-chain file:///tmp/zunda/ca.crt \
--certificate file:///tmp/zunda/server.crt \
--private-key file:///tmp/zunda/server.key

aws acm import-certificate

今回は、クライアント用証明と鍵も、easy-rsaで作成した同じ認証局(CA)で作成しているため、クライアント用証明と鍵ファイルをACMにアップロードする必要はありません。

クライアント証明書が、サーバー証明書と同じ認証機関 (発行者) によって発行されている場合、そのクライアント証明書 ARN に対してサーバー証明書 ARN を使用することができます。

クライアント VPN エンドポイント

Client VPNの作成

Clinet VPNエンドポイントの作成

Clinet VPNエンドポイントを作成します。作成用CloudFormationのテンプレートはこちら。

AWSTemplateFormatVersion: 2010-09-09
Description: "CFn for Client VPN"
Metadata: 
  AWS::CloudFormation::Interface:
    ParameterGroups:
      -
        Label:
          default: "CFn for Client VPN"
        Parameters:
          - ClinetVpnName
          - DirectoryId
          - CertArn
          - VpcCidr
          - Subnet
          - VpnCidr

Parameters: 
  ClinetVpnName:
    Type: "String"
  DirectoryId:
    Type: "String"
  CertArn:
    Type: "String"
  VpcCidr:
    Type: "String"
  Subnet:
    Type: "AWS::EC2::Subnet::Id"
  VpnCidr:
    Type: "String"

Resources:
  CloudwatchLogGroup:
    Type: "AWS::Logs::LogGroup"
    # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html
    DeletionPolicy: "Delete"
    Properties:
      LogGroupName: !Sub /aws/clientvpn/${ClinetVpnName}
      RetentionInDays: 30
  CloudwatchLogStream:
    Type: "AWS::Logs::LogStream"
    # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-logs-logstream.html
    DeletionPolicy: "Delete"
    DependsOn: "CloudwatchLogGroup"
    Properties:
      LogGroupName: !Ref CloudwatchLogGroup
      LogStreamName: !Ref ClinetVpnName
  Endpoint:
    Type: "AWS::EC2::ClientVpnEndpoint"
    # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-clientvpnendpoint.html
    DependsOn: "CloudwatchLogStream"
    DeletionPolicy: "Delete"
    Properties:
      AuthenticationOptions:
        - Type: "directory-service-authentication"
          ActiveDirectory:
            DirectoryId: !Ref DirectoryId
        - Type: "certificate-authentication"
          MutualAuthentication:
            ClientRootCertificateChainArn: !Ref CertArn
      ClientCidrBlock: !Ref VpnCidr
      ConnectionLogOptions:
        Enabled: True
        CloudwatchLogGroup: !Ref CloudwatchLogGroup
        CloudwatchLogStream: !Ref CloudwatchLogStream
      Description: "sample"
      # DnsServers:
      #   - String
      ServerCertificateArn: !Ref CertArn
      SplitTunnel: False
      TagSpecifications:
        - ResourceType: "client-vpn-endpoint"
          Tags:
          - Key: "Name"
            Value: !Ref ClinetVpnName
      TransportProtocol: "udp"
  Association:
    Type: "AWS::EC2::ClientVpnTargetNetworkAssociation"
    # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-clientvpntargetnetworkassociation.html
    DependsOn: "Endpoint"
    DeletionPolicy: "Delete"
    Properties:
      ClientVpnEndpointId: !Ref Endpoint
      SubnetId: !Ref Subnet
  Authorization1:
    Type: AWS::EC2::ClientVpnAuthorizationRule
    # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-clientvpnauthorizationrule.html
    DependsOn: "Endpoint"
    DeletionPolicy: "Delete"
    Properties:
      # AccessGroupId: String
      AuthorizeAllGroups: true
      ClientVpnEndpointId: !Ref Endpoint
      Description: "for vpc"
      TargetNetworkCidr: !Ref VpcCidr
  Authorization2:
    Type: AWS::EC2::ClientVpnAuthorizationRule
    # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-clientvpnauthorizationrule.html
    DependsOn: "Endpoint"
    DeletionPolicy: "Delete"
    Properties:
      # AccessGroupId: String
      AuthorizeAllGroups: true
      ClientVpnEndpointId: !Ref Endpoint
      Description: "for internet"
      TargetNetworkCidr: "0.0.0.0/0"
  VpnRoute:
    Type: "AWS::EC2::ClientVpnRoute"
    # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-clientvpnroute.html
    DependsOn: "Association"
    DeletionPolicy: "Delete"
    Properties:
      ClientVpnEndpointId: !Ref Endpoint
      Description: "for internet"
      DestinationCidrBlock: "0.0.0.0/0"
      TargetVpcSubnetId: !Ref Subnet

設定上の注意点はこちら。

ClientCidrBlock で指定するCIDRは、プレフィックスが/16から/22の間である必要あります。

ClientRootCertificateChainArnServerCertificateArn は、同じ認証局を利用しているため、ACMに登録した同じ設定情報を登録します。

ClientVpnAuthorizationRule の設定で、アクセス先とするVPCのCIDR情報、およびVPN接続時にもInternetに接続可能とするため、 0.0.0.0/0 を登録します。合わせて、 ClientVpnRoute にも、 0.0.0.0/0Destinationとする経路情報を登録しておきます。

インターネットへのアクセス

接続してみる

ClientにあたるWindows10のPCから、接続してみます。

まず、OpenVPNのClientをインストールします。

OpenVPN GUI for Windows - インストール

作成されたClient VPNよりコンフィグファイルをダウンロードします。ダウンロードは、ClinentVPNのコンソール上より実施できます。

f:id:goodbyegangster:20200209204027p:plain

ダウンロードしたコンフィグファイルと、作成したクライアント向け証明書・鍵ファイルを、以下に配置します。

C:\Users\zunda\OpenVPN\config\sample
├── client.crt
├── client.key
└── downloaded-client-config.ovpn

ダウンロードしたコンフィグファイルの末尾に、 cert client.crtkey client.key という設定を追加します。下記みたく。

...
auth-user-pass
reneg-sec 0
cert client.crt
key client.key

以上で必要設定は完了です。VPNに接続可能となります。なお、VPN接続中にVPC内リソースにアクセスする場合、SourceとなるIPアドレスは、ClinetVPNで利用されているENIのIPアドレスとなっているので、その点は注意です。