AWS Client VPN を CDK で作ってみた

2021.12.20

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

この記事はアノテーション株式会社 AWS Technical Support Advent Calendar 2021のカレンダー | Advent Calendar 2021 - Qiita20日目の記事です。

はじめに

こんにちは。アノテーションの小川です。

以前、ちょっとした検証で AWS Client VPN を作成し、用済み後すぐに削除したことがありました。
作成自体は難しくありませんが、AWS Client VPN を使用するためにはクライアントVPNエンドポイントの作成だけで終わりではありません。
作成後、サブネットに関連付けたり、認証ルールの設定などを行う必要があります。

業務で AWS Client VPN を作成する機会はあまりありませんが、必要が出た際にコンソールで作業するのも時間がかかりそうです。
そのため、次回以降の作業を楽にするために、少ない記述でよしなにリソースを作成してくれる AWS CDK を使って、AWS Client VPN(相互認証) を作ってみました。

このブログでは、以下の手順で AWS Client VPN を作成し、疎通を確認します。

  1. 証明書の作成
  2. VPC、クライアントVPNエンドポイントの作成(CDK)
  3. 疎通確認(SSH)
  4. クライアントVPNエンドポイントの削除(CDK)

なお、環境は次のとおりです。

$ sw_vers
ProductName:    macOS
ProductVersion: 11.6
BuildVersion:   20G165
$ cdk --version
2.1.0 (build f4f18b1)

では、手順通りに進めていきます。

証明書の作成

クライアントVPNエンドポイントの認証は複数ありますが、今回は相互認証を使用します。
相互認証に必要な証明書の作成は、公式ドキュメント記載のコマンドを実行して作成します。

コマンドを打つだけとはいえ手間だったので、証明書の作成から AWS Certificate Manager へのインポートまでのコマンドを羅列したシェルスクリプトを実行しました。
インポートする証明書等を格納するディレクトリは事前に作成し、CERTIFICATE_DIRECTORY にそのパスを代入します。

#! /bin/bash
CERTIFICATE_DIRECTORY=$HOME/aws/certificate
git clone https://github.com/OpenVPN/easy-rsa.git
cd easy-rsa/easyrsa3
./easyrsa init-pki
echo \n | ./easyrsa build-ca nopass
./easyrsa build-server-full server nopass
./easyrsa build-client-full client1.domain.tld nopass
cp pki/ca.crt $CERTIFICATE_DIRECTORY 
cp pki/issued/server.crt $CERTIFICATE_DIRECTORY
cp pki/private/server.key $CERTIFICATE_DIRECTORY
cp pki/issued/client1.domain.tld.crt $CERTIFICATE_DIRECTORY
cp pki/private/client1.domain.tld.key $CERTIFICATE_DIRECTORY
cd $CERTIFICATE_DIRECTORY
aws acm import-certificate --certificate fileb://server.crt --private-key fileb://server.key --certificate-chain fileb://ca.crt

コマンドが正常終了すると CertificateArn が返却されるので、メモしておきます(CDKで使用)。

{
    "CertificateArn": "arn:aws:acm:ap-northeast-1:111111111111:certificate/8add79e2-8ede-4b6f-a1de-xxxxxxxxxxxx"
}

VPC、クライアントVPNエンドポイントの作成

まず、CDKプロジェクトを作成します。
CDK使用の前提条件はこちら をご覧ください。

mkdir vpn-resources
cd vpn-resources
cdk init app --language=typescript

異なるターミナルで以下を実行し(CDKプロジェクトのディレクトリで)、ファイルの変更があれば自動でコンパイルするようにしておきます。

npm run watch

以下の3つのファイルを作成します。

bin/vpn-resources.ts

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { VpcStack } from '../lib/vpc-stack';
import { VpnStack } from '../lib/vpn-stack';

const app = new cdk.App();

const env = { account: '111111111111', region: 'ap-northeast-1' }

const vpcStack = new VpcStack(app, 'VpcStack', {
  stackName: 'vpc-stack',
  env: env
  
});

new VpnStack(app, 'VpnStack', vpcStack.vpc, vpcStack.vpnAssociateSubnet, {
  stackName: 'vpn-stack',
  env: env
});

lib/vpc-stack.ts

import { aws_ec2 as ec2, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class VpcStack extends Stack {
  public readonly vpc: ec2.Vpc;
  public readonly vpnAssociateSubnet: ec2.Subnet;

  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    this.vpc = new ec2.Vpc(this, 'PrivateVPC', {
      cidr: '10.0.0.0/16',
      subnetConfiguration: []
    })

    this.vpnAssociateSubnet = new ec2.Subnet(this, 'PrivateSubnet1a', {
      cidrBlock: '10.0.0.0/24',
      vpcId: this.vpc.vpcId,
      availabilityZone: 'ap-northeast-1a'
    })

    new ec2.Subnet(this, 'PrivateSubnet1c', {
      cidrBlock: '10.0.10.0/24',
      vpcId: this.vpc.vpcId,
      availabilityZone: 'ap-northeast-1c'
    })
  }
}

lib/vpn-stack.ts

import { aws_ec2 as ec2, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class VpnStack extends Stack {
  constructor(scope: Construct, id: string, vpc: ec2.Vpc, associateSubnet: ec2.Subnet, props?: StackProps) {
    super(scope, id, props);

    const clientCidr = '10.100.0.0/16'
    const certificateArn = 'arn:aws:acm:ap-northeast-1:111111111111:certificate/8add79e2-8ede-4b6f-a1de-xxxxxxxxxxxx'
    const dnsServer = '10.0.0.2'

    new ec2.ClientVpnEndpoint(this, 'vpn', {
      vpc: vpc,
      cidr: clientCidr,
      serverCertificateArn: certificateArn,
      clientCertificateArn: certificateArn,
      dnsServers: [dnsServer],
      splitTunnel: true,
      vpcSubnets: {
        subnets: [ec2.Subnet.fromSubnetId(this, 'vpnPrivateSubnet1a', associateSubnet.subnetId)]
      }
    })
  }
}

以下のコマンドで、CloudFormation Stack を作成します。

$ cdk ls 
VpcStack
VpnStack
$ cdk deploy --all

作成完了後、クライアントVPNエンドポイントのコンソール画面に移り、クライアント設定のダウンロードをして設定ファイルを編集します。

  • 4行目の remote に記載されているクライアントVPNエンドポイントの DNS 名の先頭にランダムな文字列を追記(参考
  • client1.domain.tld.crt と client1.domain.tld.keyをそれぞれ cert と key に追記
client
dev tun
proto udp
remote {random_string}.cvpn-endpoint-xxxxxxxx.prod.clientvpn.ap-northeast-1.amazonaws.com 443
remote-random-hostname
resolv-retry infinite
nobind
remote-cert-tls server
cipher AES-256-GCM
verb 3
<ca>
-----BEGIN CERTIFICATE-----
xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-----END CERTIFICATE-----

</ca>

<cert>
-----BEGIN CERTIFICATE-----
Contents of client certificate (.crt) file
-----END CERTIFICATE-----
</cert>

<key>
-----BEGIN PRIVATE KEY-----
Contents of private key (.key) file
-----END PRIVATE KEY-----
</key>

reneg-sec 0

クライアントVPNエンドポイント接続に使用するクライアントのダウンロード・設定はこちらをご覧ください。

疎通確認(SSH)

クライアントの設定が完了後、接続を開始します。

接続が成功したので、SSHでの疎通確認を行います。
EC2は事前に作成しており、セキュリティグループのインバウンドルールでクライアントVPNエンドポイントのセキュリティグループを許可しています。

$ ssh -i key.pem ec2-user@10.0.10.251
The authenticity of host '10.0.10.251 (10.0.10.251)' can't be established.
ECDSA key fingerprint is SHA256:xxxxxxxxxx
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.0.10.251' (ECDSA) to the list of known hosts.

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
[ec2-user@ip-10-0-10-251 ~]$

疎通確認完了です。

クライアントVPNエンドポイントの削除

CDKでAWS Client VPN を作成し、疎通まで確認できたので削除します。
私の場合、VPCは別の用途でも使用するため残しますので、VpnStackのみ削除します。

$ cdk destroy VpnStack

おわりに

当初は通常の CloudFormation テンプレートで作成するつもりでしたが、クライアントVPNエンドポイントだけでなく関連付けるサブネットや接続ログのためのCloudWatch Logs ロググループなども記述する必要があったり、記述する量がどうしても増えてしまいます。
CDKはあまり使用したことがありませんでしたが、少ない記述で関連するリソースを自動で作成してくれるのは非常に便利だと思いました。

参考資料

クライアント VPN の開始方法

Working with the AWS CDK

アノテーション株式会社について

アノテーション株式会社は、クラスメソッド社のグループ企業として「オペレーション・エクセレンス」を担える企業を目指してチャレンジを続けています。「らしく働く、らしく生きる」のスローガンを掲げ、さまざまな背景をもつ多様なメンバーが自由度の高い働き方を通してお客様へサービスを提供し続けてきました。現在当社では一緒に会社を盛り上げていただけるメンバーを募集中です。少しでもご興味あれば、アノテーション株式会社WEBサイトをご覧ください。