В этом туториале я опишу простую и безопасную настройку мультиаккаунтной инфраструктуры, основанной на 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-коде.
Полезные ссылки
Комментарии (4)
baldr
27.10.2021 19:34(ошибся веткой для комментария)
Большой плюс - интеграция с IAM.
Да, если у вы хотите именно через IAM всю организацию завести и поддерживать там юзеров - то да, выглядит логично и, наверное, лучше предложенного решения я ничего бы и не нашел.
Но, например, у моего клиента инфраструктура в AWS, а пользователи (10-15 чел) и почта - в GMail. Поставили OpenVPN (без AS) на почти самый дешевый EC2 сервер и через него ходим. SSO на внутренние сервисы пришлось приделать руками, но в итоге все равно дешевле. Переводить или зеркалировать структуру юзеров в IAM я смысла не вижу.
OpenVPN AS - при более 2 пользователях уже платный, так что разницы нет.
Сам по себе Organization бесплатен (AFAIK)
Да, тут вы тоже правы. Мне казалось что за него тоже берется подписка. Но AWS найдет способы навязать вам сервисы вроде Workdocs если у вас все через IAM.
baldr
Спасибо, статья хорошо объясняет процесс.
Однако... Это тот самый AWS Client VPN, который стоит каких-то конских денег? Мне кажется, этих денег хватит чтобы самому поставить 3 не совсем слабых EC2 инстанса для OpenVPN в кластере и еще доплачивать админу, чтобы следил за ними.
Вдобавок вы будете платить еще и за сервис Organization.
Amet13 Автор
То что это дорого, безусловно. Как и многие другие сервисы AWS.
Можно вообще не париться и поставить pritunl на виртуалке и его менеджить, из моего опыта там оно работает себе и кушать не просит (правда никогда не пробовал, умеет ли оно в ingress authorization, если да, то вообще круто).
Описанный вариант с AWS VPN для небольшой команды, не очень частого использования VPN, но так чтобы один раз настроить и забыть.
vitaly_il1
Большой плюс - интеграция с IAM.
Кстати, подозреваю что кластер OpenVPN потребует платный OpenVPN AS.
Сам по себе Organization бесплатен (AFAIK)