TerraformでSAMLによるSSO認証が可能なAWS Client VPNを構築する
何気にプラットフォームに記事を書くのは初めてになります。
不束者ですが、よろしくお願いいたします。
今回は諸々の条件を満たしたVPNを構築するため、TerraformでAWS Client VPNを構築する必要性に駆られた筆者ことニシキが試行錯誤をした学習内容を記録として残しました。
同じことをしようとして困っている誰かの役に立てれば幸いです。
概要
AWSを利用するという前提のもと、Terraformで以下の条件を満たすVPNを構築する必要がありました。
- 固定IPが付与されている
- セキュリティパッチの適用などの運用の手間をできる限り減らしたい
- SAML連携を利用してSSOを行いたい
- VPN経由でインターネットに接続したい
- イントラのシステムと通信するためのVPNではない
- 必要な時だけ構築を行い利用が終われば破棄したい。
これらを満たしたVPNを作成するために、以下の手段を用いることができそうだという結論になりました。
- AWS Client VPNを採用する
- IaC化(Terraform)することで必要な時だけ固定IPのVPNを作成できるようにする。
- その場合のアカウント管理はIAM Identity Center(SAML連携)でアカウントを発行する
- AWSコンソールでいつでもアカウントの無効化が可能
この記事ではこれらを実現するために記述したソースコードや、その際に詰まったところなどを書いていこうと考えています。
成果物
以下が今回の成果物になります。(動作確認済み)
記事中に記載しているソースコードは単体では動かない場合がありますので、
全体のソースコードにつきましてはこちらを参照していただけるとありがたいです。
参考
構築にあたっては以下の記事を参考にさせていただきました。
おかげ様で無事構築することができました。ありがとうございました。
構築内容
VPC + Subnet + NAT Gateway
まずはVPNを設置するVPCを構築します。
VPNでDNSの名前解決を可能にするために、enable_dns_support
をtrue
にしておく必要があります。
この設定はデフォルトでtrue
になっているので記述しなくても問題ないようです。
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
tags = {
Name = "${var.prefix} VPN VPC"
}
}
続いてサブネットとNAT Gatewayを作成します。
プライベートサブネットからのインターネットへの通信をNAT Gatewayにルーティングすることで、プライベートサブネットからインターネットへの全ての通信を同じ固定グローバルIPで行うことができます。
(別の話ですが、Lambdaに対して固定IPを付与するときなどにも同じ方法を使いました。)
resource "aws_subnet" "private" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
tags = {
Name = "${var.prefix} VPN Private Subnet"
}
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
tags = {
Name = "${var.prefix} VPN Public Subnet"
}
}
resource "aws_internet_gateway" "gw" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.prefix} VPN IGW"
}
}
resource "aws_route_table" "vpc_to_internet" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.gw.id
}
tags = {
Name = "${var.prefix} VPN Public Route Table"
}
}
resource "aws_route_table_association" "public_to_internet" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.vpc_to_internet.id
}
resource "aws_nat_gateway" "nat" {
allocation_id = var.aws_eip_nat_id
subnet_id = aws_subnet.public.id
tags = {
Name = "${var.prefix} VPN NAT Gateway"
}
}
resource "aws_route_table" "private_to_nat" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat.id
}
tags = {
Name = "${var.prefix} VPN Private Route Table"
}
}
resource "aws_route_table_association" "private_to_nat" {
subnet_id = aws_subnet.private.id
route_table_id = aws_route_table.private_to_nat.id
}
SAML Provider
最初こちらについては、以下の理由で取り組んでいませんでした。
- 過去に一つのAWSアカウントに対してTerraformで複数の環境を構築する際に、GitHubのOIDC連携において環境ごとにIAM Identity Providerを作ろうとすると同じものは作れないというエラーが発生していた[1]
- SAMLも下手に作ると環境を分ける必要がある際に問題になってしまう?という懸念
ですが、SAMLはアプリケーション毎にIdPに登録しメタデータを発行するため、そのようなことは起きないと解釈しこちらのIaC化にも取り組んでみました。
今回はVPNクライアントとセルフサービスポータルの二つのアプリケーションにSAMLを利用するため、それぞれにIAM Saml Providerを作成します。
resource "aws_iam_saml_provider" "main" {
name = "${random_id.main.hex}-saml-provider"
saml_metadata_document = file("${path.root}/saml-metadata.xml")
tags = {
Name = "${var.prefix} SAML Provider"
}
}
resource "aws_iam_saml_provider" "self_service_portal" {
name = "${random_id.main.hex}-self-service-portal-saml-provider"
saml_metadata_document = file("${path.root}/self-service-portal-saml-metadata.xml")
tags = {
Name = "${var.prefix} Self Service Portal SAML Provider"
}
}
今回はSAML IdPとしてIAM Identity Centerを利用します。
参考にした以下の記事に従って、カスタムSAML2.0アプリケーションの追加からVPNクライアントとセルフサービスポータルを追加します。
IAM Identity Center SAMLメタデータファイルもダウンロードしておきます。セルフサービスポータルのほうのファイル名はself-service-portal-saml-metadata.xmlに変更しておきました。
Terraform環境のルートディレクトリにて、以下のようにファイルを設置することでSAMLメタデータファイルが参照されリソースが作成されます。
.
…(省略)
├ main.tf
├ saml-metadata.xml
└ self-service-portal-saml-metadata.xml
Client VPN
Client VPN Endpointに適用するセキュリティグループを作成します。
こちらに関しては頭を振り絞って考えてみてたのですが、なぜプライベートサブネットのCIDRを許可してるのかはっきりとは理解していません。Client VPN Endpointのネットワーク周りの理解が進めばわかると思うので、今はそれが必要であるというところで思考を止めています。
resource "aws_security_group" "vpn" {
vpc_id = aws_vpc.main.id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = [aws_subnet.private.cidr_block]
}
}
次にClient VPN Endpointを作成します。
今回は前述のとおりSAMLプロバイダーを用いて認証を行うようにしました。
authentication_options
のsaml_*
に、IAM Identity Providerに登録した各種アプリケーションのARNを登録することで連携が可能です。
dns_servers
構築中で躓いたこととして、dns_servers
の設定があります。
こちらの指定がない場合、基本的にはクライアントPCに指定されているプロバイダーDNSが利用されるようなのですが、環境によってドメイン名の解決ができないことがあるようです。(プロバイダー側でブロックされている?)
今回は回避策として外部のDNSサーバー(1.1.1.1
)を指定しました。これでVPNからの名前解決が行えるようになりました。
また、接続のログをCloudWatch Logsに記録するようにしています。
resource "aws_ec2_client_vpn_endpoint" "main" {
server_certificate_arn = var.server_certificate_arn
client_cidr_block = "10.10.0.0/22"
vpc_id = var.vpc_id
split_tunnel = false
session_timeout_hours = 24
vpn_port = 443
self_service_portal = "enabled"
dns_servers = var.dns_servers
authentication_options {
type = "federated-authentication"
saml_provider_arn = aws_iam_saml_provider.main.arn
self_service_saml_provider_arn = aws_iam_saml_provider.self_service_portal.arn
}
connection_log_options {
enabled = true
cloudwatch_log_group = aws_cloudwatch_log_group.vpn.name
}
security_group_ids = [var.security_group_id]
tags = {
Name = "${var.prefix} VPN Endpoint"
}
}
resource "aws_cloudwatch_log_group" "vpn" {
name = "/aws/ec2/client-vpn-connections-${random_id.main.hex}"
retention_in_days = 90
}
resource "aws_ec2_client_vpn_network_association" "vpn_to_private" {
client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.main.id
subnet_id = var.subnet_id
}
resource "aws_ec2_client_vpn_authorization_rule" "vpn_to_private" {
client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.main.id
target_network_cidr = "0.0.0.0/0"
authorize_all_groups = true
}
resource "aws_ec2_client_vpn_route" "vpn_to_private" {
client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.main.id
destination_cidr_block = "0.0.0.0/0"
target_vpc_subnet_id = var.subnet_id
depends_on = [aws_ec2_client_vpn_network_association.vpn_to_private]
}
感想・まとめ
というわけで今回はTerraformでAWS Client VPNを構築してみました。
VPNの採用における要件は色々あると思いますが、割と自由に組み合わせをすることができたり、必要な時にさっと用意することができるので、AWS Client VPNとTerraformの組み合わせは結構良いなと感じてます。
初めてまともに人に読んでいただくための記事を書いたのですが、参考記事そのままにならないようにするのが中々どうして難しかったです。
少しずつでも良い記事を書けるよう、これから技術・文章の研鑽を積んでいきたいと思います。
今回はご覧いただきありがとうございました。次回があればその時はまたよろしくお願いいたします。
-
GitHubのOIDC連携に関しては、複数環境から同時に参照されるリソースを管理するまとまりを別途作ることで解決しています。 ↩︎
Discussion