В этом туториале я опишу простую и безопасную настройку мультиаккаунтной инфраструктуры, основанной на AWS, включая SSO и решение для VPN от Amazon.

Введение
Я разбил статью на несколько основных частей:
Во-первых, я покажу как создать инфраструктуру в AWS с нуля, с безопасной структурой аккаунтов, сетей, пирингов
Вторая часть этой статьи посвящена AWS SSO: пользователям, группам, MFA, и т.д.
Третья часть описывает процесс развертывания сервиса AWS VPN с помощью terraform и его настройки для ранее созданных сетей
Начнем!
Структура AWS-аккаунтов
Мультиаккаунтная структура для AWS имеет ряд преимуществ. Я не буду на них останавливаться, только покажу пример:

Аккаунт root
является главным в организации (на него привязывается биллинг), все остальные аккаунты добавляются под руководство этого аккаунта.
Каждый аккаунт привязан к разному почтовому адресу, но обычно можно использовать почтовые алиасы для удобства управления с одного почтового ящика.
Давайте создадим аккаунт с именем root
и добавим новые аккаунты в организацию. Логинимся в аккаунты, включаем MFA для пользователей и удостоверяемся, что наша структура аккаунтов верна и мы находимся в одной организации.

Этого достаточно на текущий момент, можем идти дальше.
Сетевая структура
В моем примере аккаунты dev
, stage
и prod
изолированы друг от друга.
Аккаунт common
используется для общих сервисов, таких как система CI/CD, хранилища данных (S3-бакеты), и т.д. Поэтому, в моем случае, для того чтобы разрешить сетевое соединение между common
и dev
/stage
/prod
аккаунтам, нам нужно создать VPC-пиринг (соединение между виртуальными сетями).
Давайте сделаем это с помощью terraform для аккаунтов common
и dev
.
Для начала создадим VPC:
data "aws_availability_zones" "available" {}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "3.7.0"
name = "${var.env}-vpc"
cidr = var.vpc_cidr
azs = data.aws_availability_zones.available.names
private_subnets = var.vpc_private_subnets
public_subnets = var.vpc_public_subnets
enable_nat_gateway = true
single_nat_gateway = true
enable_dns_hostnames = true
}
А также файл с входными параметрами terraform.tfvars
:
region = "eu-central-1"
env = "common"
# https://www.davidc.net/sites/default/subnets/subnets.html
# каждая подсеть ~8190 хостов
# 10.0.192.0/19 зарезервирована под VPN-клиентов
vpc_cidr = "10.0.0.0/16"
vpc_private_subnets = ["10.0.0.0/19", "10.0.32.0/19", "10.0.64.0/19"]
vpc_public_subnets = ["10.0.96.0/19", "10.0.128.0/19", "10.0.160.0/19"]
Если вы хотите применить то же самое для аккаунта dev
, просто скопируйте и вставьте файл и измените значения сетей и подсетей, к примеру на 10.1.0.0/16
и т.д. Помните, что подсети должны быть разными, чтобы избежать их пересечения после пиринга.
После этого мы имеем новые виртуальные сети:
common-vpc
в аккаунтеcommon
с CIDR:10.0.0.0/16
, тремя публичными и тремя частными подсетямиdev-vpc
в аккаунтеdev
с CIDR:10.1.0.0/16
, тремя публичными и тремя частными подсетями
Давайте соединим их с помощью пиринга:
module "common_dev_peering" {
source = "grem11n/vpc-peering/aws"
version = "4.0.1"
providers = {
aws.this = aws
aws.peer = aws.dev
}
this_vpc_id = module.vpc.vpc_id
peer_vpc_id = var.vpc_dev_accepter_id
auto_accept_peering = true
}
Не забываем указать vpc_dev_accepter_id
в файле terraform.tfvars
:
...
vpc_dev_accepter_id = "vpc-12345678"
Ту же самую процедуру можно проделать, к примеру, для аккаунтов stage
и prod
.

Для тестирования сетевого соединения, достаточно в обоих сетях создать виртуалку и попробовать проверить каким-нибудь инструментом типа ping
или traceroute
.
Настраиваем SSO
Заходим в AWS в аккаунт root
, находим сервис AWS SSO и создаем три группы:
vpn-dev
vpn-stage
vpn-prod
Идея состоит в том, чтобы разделять доступ к различным сетям для разных групп (RBAC) с VPN.

Также создадим пользователя, пусть будет amet-umerov
, после добавления его во все ранее созданные группы, мы хотим чтобы пользователь получил доступ во все сети с помощью VPN.

Итак, мы подготовили группы и пользователя, самое время создать новое SSO-приложение под названием VPN:
Add a custom SAML 2.0 application
Загружаем
AWS SSO SAML metadata file
, он нам пригодится позжеSet session duration:
12 hours
Application ACS URL:
http://127.0.0.1:35001
Application SAML audience:
urn:amazon:webservices:clientvpn
Также создадим еще одно SSO-приложение с именем VPN Self-Service с такими же настройками, кроме:
Application ACS URL:
https://self-service.clientvpn.amazonaws.com/api/auth/sso/saml
Добавим маппинг атрибутов для этих приложений:
Subject
—user:subject
—emailAddress
NameID
—${user:email}
—unspecified
FirstName
—${user:name}
—unspecified
LastName
—${user:familyName}
—unspecified
memberOf
—${user:groups}
—unspecified
И привяжем к ним все три группы (vpn-dev
, vpn-stage
, vpn-prod
).

Вот как оно работает:

Настраиваем VPN
Нашим последним шагом является создание клиентской точки доступа для VPN в аккаунте common
. Но перед этим необходимо подготовить некоторые сертификаты и ключи.
Генерируем сертификаты для сервера и клиента:
$ git clone https://github.com/OpenVPN/easy-rsa.git
$ cd easy-rsa/easyrsa3
$ ./easyrsa init-pki
$ ./easyrsa build-ca nopass
...
Common Name (eg: your user, host, or server name) [Easy-RSA CA]:vpn.domain.org
$ ./easyrsa build-server-full vpn-aws-server nopass
$ ./easyrsa build-client-full vpn-aws-client nopass
Копируем сгенерированные сертификаты в безопасное место и импортируем сертификат для сервера в AWS ACM:
$ mkdir ~/.vpn-assets/
$ cp pki/ca.crt ~/.vpn-assets/
$ cp pki/private/ca.key ~/.vpn-assets/
$ cp pki/issued/vpn-aws-*.crt ~/.vpn-assets/
$ cp pki/private/vpn-aws-*.key ~/.vpn-assets/
$ aws --profile common \
--region eu-central-1 \
acm import-certificate \
--certificate fileb://$HOME/.vpn-assets/vpn-aws-server.crt \
--private-key fileb://$HOME/.vpn-assets/vpn-aws-server.key \
--certificate-chain fileb://$HOME/.vpn-assets/ca.crt
# На всякий случай скопируем это все в S3-бакет
$ aws --profile=common s3 cp --recursive ~/.vpn-assets/ s3://my-bucket/vpn/
Создаем новое VPN-соединение с помощью terraform:
# SAML провайдеры из метадата-файлов, загруженных нами ранее
resource "aws_iam_saml_provider" "vpn" {
name = "vpn"
saml_metadata_document = file("${path.module}/files/VPN_ins-mymetadata-file.xml")
}
resource "aws_iam_saml_provider" "vpn_self_service" {
name = "vpn-self-service"
saml_metadata_document = file("${path.module}/files/VPN Self-Service_ins-mymetadata-file.xml")
}
# Получаем импортированный нами сертификат и subnet_id
data "aws_acm_certificate" "vpn_aws_server_cert" {
domain = "vpn-aws-server"
statuses = ["ISSUED"]
}
data "aws_subnet" "vpn_subnet_id" {
filter {
name = "tag:Name"
values = ["${var.env}-vpc-private"]
}
availability_zone_id = "euc1-az1"
}
# Подготовка CloudWatch для логирования VPN
resource "aws_cloudwatch_log_group" "client_vpn" {
name = "vpn_endpoint_cloudwatch_log_group"
}
resource "aws_cloudwatch_log_stream" "client_vpn" {
name = "vpn_endpoint_cloudwatch_log_stream"
log_group_name = aws_cloudwatch_log_group.client_vpn.name
}
# Точка доступа VPN
resource "aws_ec2_client_vpn_endpoint" "vpn" {
description = "VPN client for AWS"
server_certificate_arn = data.aws_acm_certificate.vpn_aws_server_cert.arn
client_cidr_block = var.vpn_client_cidr_block
dns_servers = var.vpn_dns_servers
split_tunnel = "true"
self_service_portal = "enabled"
transport_protocol = "udp"
authentication_options {
type = "federated-authentication"
saml_provider_arn = aws_iam_saml_provider.vpn.arn
self_service_saml_provider_arn = aws_iam_saml_provider.vpn_self_service.arn
}
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
}
}
# Фаерволы для VPN
resource "aws_security_group" "vpn_main" {
name = "vpn_main"
description = "Allow VPN all traffic"
vpc_id = module.vpc.vpc_id
egress {
description = "Allow all traffic for VPN"
cidr_blocks = ["0.0.0.0/0"]
from_port = "0"
protocol = "-1"
self = "false"
to_port = "0"
}
ingress {
description = "Allow all traffic for VPN"
cidr_blocks = ["0.0.0.0/0"]
from_port = "0"
protocol = "-1"
self = "false"
to_port = "0"
}
}
# Ассоциируем VPN-точку с подсетью
resource "aws_ec2_client_vpn_network_association" "vpn" {
client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.vpn.id
subnet_id = data.aws_subnet.vpn_subnet_id.id
security_groups = [aws_security_group.vpn_main.id]
}
# Идентификаторы (access_group_id) можно найти тут (в аккаунте root):
# https://eu-central-1.console.aws.amazon.com/singlesignon/home?region=eu-central-1#/groups
resource "aws_ec2_client_vpn_authorization_rule" "vpn_dev" {
client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.vpn.id
target_network_cidr = "10.10.0.0/16"
access_group_id = "1234-5678-..."
description = "vpn-dev"
}
# Разрешаем доступ в сеть common для группы vpn-dev
resource "aws_ec2_client_vpn_authorization_rule" "vpn_common" {
client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.vpn.id
target_network_cidr = "10.0.0.0/16"
access_group_id = "1234-5678-..."
description = "vpn-common"
}
# Маршруты
resource "aws_ec2_client_vpn_route" "vpn_to_dev" {
client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.vpn.id
destination_cidr_block = "10.10.0.0/16"
target_vpc_subnet_id = aws_ec2_client_vpn_network_association.vpn.subnet_id
}
И модифицируем файл terraform.tfvars
:
...
vpn_client_cidr_block = "10.0.192.0/19"
vpn_dns_servers = ["1.1.1.1", "8.8.8.8"]
После применения данной конфигурации мы получаем клиентскую точку доступа к VPN с контролем доступа на уровне ролей (групп).
Тестируем.
Заходим на портал самообслуживания, выглядит ссылка примерно так:
https://self-service.clientvpn.amazonaws.com/endpoints/cvpn-endpoint-1234567890
Загружаем VPN-клиент для своей ОС, а также конфигурацию (файл в формате
.ovpn
)

Запускаем VPN-клиент, создаем новый профиль и импортируем конфигурацию VPN
Удостоверяемся, что мы подключены к VPN
Проверяем соединение с помощью
traceroute
/ping
и все тех же виртуальных машин
После всех проделанных ранее манипуляций у нас есть доступ к сетям аккаунтов common
и dev
с помощью VPN .
Данная конфигурация может быть масштабирована для других аккаунтов с иными правилами доступа и маршрутами в нашем terraform-коде.