方法
AWS ClientVPN と Azure Active Directory (Azure AD) を統合する
構成
IdP
-
Microsoft Azure
- AWS ClientVPNへの接続ユーザをAzureADで制御
- Azure ADアカウントを用いてAWS ClientVPN に自動的にサインインさせる
SP
-
AWS Client VPN
- セルフサービスポータル URLを作成することで運用コストを下げる
- 認証はfederated-authenticationを選択
- VPNを作成するVPCはDNS解決、DNSホストを有効にしておく
構築
Azure
Azure Active Directory シングル サインオン (SSO) と AWS ClientVPN の統合を参照する
AWS
ダウンロードしたFederation Metadata XMLでSAML用のIAM Identity Providerを作成する
- IAM設定ファイル
vpn-config
├── common
│ ├── iam
│ │ ├── azure.xml
│ │ ├── iam_saml_provider.tf
│ │ └── main.tf
│ └── server-crt
│ ├── README.md
(snip)
aws_iam_saml_provider.tf
resource "aws_iam_saml_provider" "example_azure" {
name = "azure"
saml_metadata_document = file("azure.xml")
}
- AWS ClientVPNで使用するサーバ証明書を作成
- OpenVPN easy-rsa リポジトリのクローンをローカルコンピュータに作成して、easy-rsa/easyrsa3 フォルダに移動させる
% git clone https://github.com/OpenVPN/easy-rsa.git
% cd easy-rsa/easyrsa3
% ./easyrsa init-pki
% ./easyrsa build-ca nopass
% ./easyrsa build-server-full server nopass
- サーバー証明書とキーをカスタムフォルダにコピーしてから、カスタムフォルダに移動させる
% mkdir ~/custom_folder/
% cp pki/ca.crt ~/custom_folder/
% cp pki/issued/server.crt ~/custom_folder/
% cp pki/private/server.key ~/custom_folder/
% cp pki/issued/client1.domain.tld.crt ~/custom_folder
% cp pki/private/client1.domain.tld.key ~/custom_folder/
% cd ~/custom_folder/
- サーバー証明書とキーを ACM にアップロードする
% aws acm import-certificate --certificate fileb://server.crt --private-key fileb://server.key --certificate-chain fileb://ca.crt
VPN設定ファイル
vpn-config
(snip)
│
└── network
├── cloudwatch_log_group.tf
├── cloudwatch_metric_alarm.tf
├── ec2_client_vpn_endpoint.tf
├── eip.tf
├── internet_gateway.tf
├── main.tf
├── nat_gateway.tf
├── route.tf
├── route_table.tf
├── route_table_association.tf
├── security_group.tf
├── subnet.tf
├── variables.tf
└── vpc.tf
cloudwatch_log_group.tf
resource "aws_cloudwatch_log_group" "client_vpn" {
name = "client-vpn"
retention_in_days = 365
tags = {
Name = "client-vpn"
}
}
resource "aws_cloudwatch_log_stream" "client_vpn" {
name = "client-vpn-logstream"
log_group_name = aws_cloudwatch_log_group.client_vpn.name
}
cloudwatch_metric_alarm.tf
resource "aws_cloudwatch_metric_alarm" "client_vpn" {
for_each = var.client_vpn_cloudwatch_metrics
alarm_name = each.value.alarm_name
comparison_operator = each.value.comparison_operator
evaluation_periods = "2"
treat_missing_data = "notBreaching"
metric_name = each.value.metric_name
namespace = "AWS/ClientVPN"
period = "60"
statistic = each.value.statistic
threshold = each.value.threshold
alarm_actions = ["arn:aws:sns:ap-northeast-1:account-id:alert-client-vpn"]
ok_actions = ["arn:aws:sns:ap-northeast-1:account-id:alert-client-vpn"]
dimensions = {
Endpoint = "cvpn-endpoint-*********"
}
}
ec2_client_vpn_endpoint.tf
resource "aws_ec2_client_vpn_endpoint" "example" {
client_cidr_block = "192.168.0.0/20"
description = "use tcp"
dns_servers = [
"1.1.1.1",
"8.8.8.8",
]
self_service_portal = "enabled"
server_certificate_arn = data.aws_acm_certificate.issued.arn
split_tunnel =
tags = {
Name = "example-vpn-federated-authentication"
}
transport_protocol = "tcp"
authentication_options {
saml_provider_arn = "arn:aws:iam::account-id:saml-provider/azure"
self_service_saml_provider_arn = "arn:aws:iam::account-id:saml-provider/azure"
type = "federated-authentication"
}
connection_log_options {
enabled = true
cloudwatch_log_group = aws_cloudwatch_log_group.client_vpn.name
cloudwatch_log_stream = aws_cloudwatch_log_stream.client_vpn.name
}
}
resource "aws_ec2_client_vpn_network_association" "example_assoc" {
for_each = var.vpn_client_nat_route_assoc
client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.example.id
subnet_id = aws_subnet.client_vpn[each.key].id
security_groups = [aws_security_group.client_vpn.id]
}
resource "aws_ec2_client_vpn_authorization_rule" "example_authorization" {
client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.example.id
target_network_cidr = "0.0.0.0/0"
authorize_all_groups = true
}
resource "aws_ec2_client_vpn_route" "example_route_1a" {
client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.example.id
destination_cidr_block = "0.0.0.0/0"
target_vpc_subnet_id = aws_subnet.client_vpn["private-1a"].id
}
resource "aws_ec2_client_vpn_route" "example_route_1c" {
client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.example.id
destination_cidr_block = "0.0.0.0/0"
target_vpc_subnet_id = aws_subnet.client_vpn["private-1c"].id
}
eip.tf
resource "aws_eip" "client_vpn" {
for_each = var.vpn_client_natgw
vpc = true
tags = {
Name = each.value.name
}
}
internet_gateway.tf
resource "aws_internet_gateway" "client_vpn" {
vpc_id = aws_vpc.client_vpn.id
tags = {
Name = "client-vpn"
}
}
nat_gateway.tf
resource "aws_nat_gateway" "client_vpn" {
for_each = var.vpn_client_natgw
allocation_id = aws_eip.client_vpn[each.key].id
subnet_id = aws_subnet.client_vpn[each.key].id
tags = {
Name = each.value.name
}
}
route.tf
locals {
public-names = [
"public-1a",
"public-1c"
]
}
resource "aws_route" "client_vpn_public" {
for_each = toset(local.public-names)
route_table_id = aws_route_table.client_vpn[each.key].id
gateway_id = aws_internet_gateway.client_vpn.id
destination_cidr_block = "0.0.0.0/0"
}
resource "aws_route" "client_vpn_private" {
for_each = var.vpn_client_nat_route_assoc
route_table_id = aws_route_table.client_vpn[each.key].id
nat_gateway_id = aws_nat_gateway.client_vpn[each.value.natgw-name].id
destination_cidr_block = "0.0.0.0/0"
}
route_table.tf
resource "aws_route_table" "client_vpn" {
for_each = var.client_vpn_subnets
vpc_id = aws_vpc.client_vpn.id
tags = {
Name = each.value.name
}
}
route_table_association.tf
resource "aws_route_table_association" "client_vpn" {
for_each = var.client_vpn_subnets
subnet_id = aws_subnet.client_vpn[each.key].id
route_table_id = aws_route_table.client_vpn[each.key].id
}
security_group.tf
resource "aws_security_group" "client_vpn" {
name = "client-vpn"
vpc_id = aws_vpc.client_vpn.id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "client-vpn"
}
}
resource "aws_security_group_rule" "client_vpn_80" {
type = "ingress"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.client_vpn.id
}
resource "aws_security_group_rule" "client_vpn_443" {
type = "ingress"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.client_vpn.id
}
subnet.tf
resource "aws_subnet" "client_vpn" {
for_each = var.client_vpn_subnets
vpc_id = aws_vpc.client_vpn.id
availability_zone = each.value.zone
cidr_block = each.value.cidr
map_public_ip_on_launch = each.value.launch
tags = {
Name = each.value.name
}
}
vpc.tf
resource "aws_vpc" "client_vpn" {
cidr_block = "10.0.0.0/16"
instance_tenancy = "default"
tags = {
Name = "client-vpn"
}
}
variables.tf
variable "vpn_client_nat_route_assoc" {
type = map(any)
default = {
private-1a = {
natgw-name = "public-1a"
}
private-1c = {
natgw-name = "public-1c"
}
}
}
variable "vpn_client_natgw" {
type = map(any)
default = {
public-1a = {
name = "client-vpn-public-1a"
}
public-1c = {
name = "client-vpn-public-1c"
}
}
}
variable "client_vpn_subnets" {
type = map(any)
default = {
public-1a = {
cidr = "10.0.0.0/24"
zone = "ap-northeast-1a"
launch = "true"
name = "client-vpn-public-1a"
}
public-1c = {
cidr = "10.0.1.0/24"
zone = "ap-northeast-1c"
launch = "true"
name = "client-vpn-public-1c"
}
private-1a = {
cidr = "10.0.2.0/24"
zone = "ap-northeast-1a"
launch = "false"
name = "client-vpn-private-1a"
}
private-1c = {
cidr = "10.0.3.0/24"
zone = "ap-northeast-1c"
launch = "false"
name = "client-vpn-private-1c"
}
}
}
variable "client_vpn_cloudwatch_metrics" {
type = map(any)
default = {
ActiveConnectionsCount = {
alarm_name = "cvpn-endpoint-******************-ActiveConnectionsCount"
metric_name = "ActiveConnectionsCount"
statistic = "Sum"
threshold = "100"
comparison_operator = "GreaterThanOrEqualToThreshold"
alarm_description = "クライアント VPN エンドポイントへのアクティブな接続数"
}
AuthenticationFailures = {
alarm_name = "cvpn-endpoint-******************-AuthenticationFailures"
metric_name = "AuthenticationFailures"
statistic = "Sum"
threshold = "10"
comparison_operator = "GreaterThanOrEqualToThreshold"
alarm_description = "クライアント VPN エンドポイントの認証失敗数"
}
CrlDaysToExpiry = {
alarm_name = "cvpn-endpoint-******************-CrlDaysToExpiry"
metric_name = "CrlDaysToExpiry"
statistic = "Sum"
threshold = "30"
comparison_operator = "GreaterThanOrEqualToThreshold"
alarm_description = "クライアント VPN エンドポイントで設定されている証明書失効リスト (CRL) の有効期限が切れるまでの日数"
}
SelfServicePortalClientConfigurationDownloads = {
alarm_name = "cvpn-endpoint-******************-SelfServicePortalClientConfigurationDownloads"
metric_name = "SelfServicePortalClientConfigurationDownloads"
statistic = "Sum"
threshold = "100"
comparison_operator = "GreaterThanOrEqualToThreshold"
alarm_description = "セルフサービスポータルからの Client VPN エンドポイント設定ファイルのダウンロード数"
}
ClientConnectHandlerTimeouts = {
alarm_name = "cvpn-endpoint-******************-ClientConnectHandlerTimeouts"
metric_name = "ClientConnectHandlerTimeouts"
statistic = "Sum"
threshold = "100"
comparison_operator = "GreaterThanOrEqualToThreshold"
alarm_description = "クライアント VPN エンドポイントへの接続用のクライアント接続ハンドラの呼び出し時のタイムアウト数"
}
ClientConnectHandlerInvalidResponses = {
alarm_name = "cvpn-endpoint-******************-ClientConnectHandlerInvalidResponses"
metric_name = "ClientConnectHandlerInvalidResponses"
statistic = "Sum"
threshold = "100"
comparison_operator = "GreaterThanOrEqualToThreshold"
alarm_description = "クライアント VPN エンドポイントへの接続用のクライアント接続ハンドラから返された無効な応答の数"
}
ClientConnectHandlerOtherExecutionErrors = {
alarm_name = "cvpn-endpoint-******************-ClientConnectHandlerOtherExecutionErrors"
metric_name = "ClientConnectHandlerOtherExecutionErrors"
statistic = "Sum"
threshold = "100"
comparison_operator = "GreaterThanOrEqualToThreshold"
alarm_description = "クライアント VPN エンドポイントへの接続用のクライアント接続ハンドラの実行中に発生した、予期されていなかったエラーの数"
}
ClientConnectHandlerThrottlingErrors = {
alarm_name = "cvpn-endpoint-******************-ClientConnectHandlerThrottlingErrors"
metric_name = "ClientConnectHandlerThrottlingErrors"
statistic = "Sum"
threshold = "100"
comparison_operator = "GreaterThanOrEqualToThreshold"
alarm_description = "クライアント VPN エンドポイントへの接続用のクライアント接続ハンドラの呼び出し時のスロットリングエラーの数"
}
ClientConnectHandlerDeniedConnections = {
alarm_name = "cvpn-endpoint-******************-ClientConnectHandlerDeniedConnections"
metric_name = "ClientConnectHandlerDeniedConnections"
statistic = "Sum"
threshold = "100"
comparison_operator = "GreaterThanOrEqualToThreshold"
alarm_description = "クライアント VPN エンドポイントへの接続用のクライアント接続ハンドラによって拒否された接続の数"
}
ClientConnectHandlerFailedServiceErrors = {
alarm_name = "cvpn-endpoint-******************-ClientConnectHandlerFailedServiceErrors"
metric_name = "ClientConnectHandlerFailedServiceErrors"
statistic = "Sum"
threshold = "100"
comparison_operator = "GreaterThanOrEqualToThreshold"
alarm_description = "クライアント VPN エンドポイントへの接続用のクライアント接続ハンドラの実行中に発生したサービス側のエラーの数"
}
}
}
main.tf
provider "aws" {
region = "ap-northeast-1"
default_tags {
tags = {
Service = "client-vpn"
Description = "Managed by Terraform"
}
}
}
terraform {
required_version = ">= 0.13"
backend "s3" {
bucket = "***********"
key = "network/terraform.tfstate"
region = "ap-northeast-1"
}
}
data "aws_acm_certificate" "issued" {
domain = "server"
statuses = ["ISSUED"]
}
VPN Client
-
AWS Client VPN Self-Service Portalにログインし、接続設定用ファイルと使用OS毎にClientVPNアプリケーションをダウンロードする
-
設定手順
-
- 3.[Add Profile (プロファイルの追加)] を選択します。の個所で保存したプロファイルを指定する
-
- 3.[Add Profile (プロファイルの追加)] を選択します。の個所で保存したプロファイルを指定する
-
- 3.[Add Profile (プロファイルの追加)] を選択します。の個所で保存したプロファイルを指定する
-
IPv6リーク対策のため、プロファイルの最終行に記述
-
federation-auth-config.ovpn
ifconfig-ipv6 fd15:53b6:dead::2 fd15:53b6:dead::1
route-ipv6 2000::/4
- 確認
- 使用中IPがNatGatewayに使用しているEIPであることが確認できればOKです