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

Введение

Я разбил статью на несколько основных частей:

  • Во-первых, я покажу как создать инфраструктуру в AWS с нуля, с безопасной структурой аккаунтов, сетей, пирингов

  • Вторая часть этой статьи посвящена AWS SSO: пользователям, группам, MFA, и т.д.

  • Третья часть описывает процесс развертывания сервиса AWS VPN с помощью terraform и его настройки для ранее созданных сетей

Начнем!

Структура AWS-аккаунтов

Мультиаккаунтная структура для AWS имеет ряд преимуществ. Я не буду на них останавливаться, только покажу пример:

Структура аккаунтов
Структура аккаунтов

Аккаунт root является главным в организации (на него привязывается биллинг), все остальные аккаунты добавляются под руководство этого аккаунта.

Каждый аккаунт привязан к разному почтовому адресу, но обычно можно использовать почтовые алиасы для удобства управления с одного почтового ящика.

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

https://console.aws.amazon.com/organizations/v2/home/accounts
https://console.aws.amazon.com/organizations/v2/home/accounts

Этого достаточно на текущий момент, можем идти дальше.

Сетевая структура

В моем примере аккаунты 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.

Не забываем включать MFA для пользователя
Не забываем включать MFA для пользователя

Итак, мы подготовили группы и пользователя, самое время создать новое 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-devvpn-stagevpn-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)


  1. baldr
    27.10.2021 11:03
    +2

    Спасибо, статья хорошо объясняет процесс.

    Однако... Это тот самый AWS Client VPN, который стоит каких-то конских денег? Мне кажется, этих денег хватит чтобы самому поставить 3 не совсем слабых EC2 инстанса для OpenVPN в кластере и еще доплачивать админу, чтобы следил за ними.

    Вдобавок вы будете платить еще и за сервис Organization.


    1. Amet13 Автор
      27.10.2021 11:17

      То что это дорого, безусловно. Как и многие другие сервисы AWS.

      Можно вообще не париться и поставить pritunl на виртуалке и его менеджить, из моего опыта там оно работает себе и кушать не просит (правда никогда не пробовал, умеет ли оно в ingress authorization, если да, то вообще круто).

      Описанный вариант с AWS VPN для небольшой команды, не очень частого использования VPN, но так чтобы один раз настроить и забыть.


    1. vitaly_il1
      27.10.2021 19:12

      AWS Client VPN, который стоит каких-то конских денег? 

      Большой плюс - интеграция с IAM.
      Кстати, подозреваю что кластер  OpenVPN потребует платный OpenVPN AS.

      вы будете платить еще и за сервис Organization

      Сам по себе Organization бесплатен (AFAIK)


  1. 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.