Даже 15 лет назад, когда кто-то говорил о сервере, они имели в виду часть оборудования в центре обработки данных. «Выделенный хостинг» и «Совместное размещение» были гораздо более популярными и известными терминами. Перенесемся в сегодняшний день, и мир стал совсем другим. Аппаратные серверы превратились в виртуальные машины, виртуальные машины превратились в контейнеры Virtuozzo (предшественник Docker), а затем мы пришли к Docker. Подобно описанной выше эволюции, изменился и способ предоставления услуг. Помимо размещения собственной службы, вы можете воспользоваться преимуществами Paas (платформа как услуга), Iaas (инфраструктура как услуга), Saas (программное обеспечение как услуга) и др.

Если вы читали мои предыдущие статьи, такие как Как думать, как инженер, вы знаете, что я хорошо отношусь к настраиваемым / самоуправляемым инструментам, если они являются лучшими кандидатами на работу. Однако часто эту работу лучше выполняет существующий инструмент / репо / продукт. Я так же отношусь к автономным услугам и предложениям Saas. Размещение моих собственных сервисов связано с накладными расходами, такими как исправление, обновление, защита, настройка, масштабирование и т. Д. В отличие от предложения Saas, компания беспокоится о накладных расходах, а я получаю эту услугу. Даже если с предложением Saas связана цена, подумайте о том, сколько времени вы сэкономили, не имея необходимости постоянно поддерживать еще одну услугу. Зачастую предложение Saas намного дешевле, чем самостоятельный хостинг.

Одно из моих любимых предложений Saas, которое я недавно начал использовать, - это семейство трансферов AWS. Для тех, кто не знаком с этой услугой:

Семейство AWS Transfer обеспечивает полностью управляемую поддержку передачи файлов напрямую в Amazon S3 и из него. С поддержкой безопасного протокола передачи файлов (SFTP), протокола передачи файлов через SSL (FTPS) и протокола передачи файлов (FTP)

Напомним, AWS разместит для вас SFTP-сервер, который использует S3 в качестве бэкэнда! Тот факт, что они будут размещать для вас SFTP-сервер сам по себе, ценен, но тот факт, что все файлы попадают в S3, также впечатляет. Большинство сервисов AWS уже естественным образом интегрируются с S3. Когда файлы находятся в S3, ваши лямбды, экземпляры, StepFunction, _____ [ваша любимая служба] могут захватывать файлы и обрабатывать их. Поскольку файлы находятся в S3, аутентификация происходит через стандартный IAM, и ваш код / ​​служба даже не знает и не заботится о том, что сервер SFTP получил файлы там.

И последнее, но не менее важное: поскольку это сервис AWS, настройку можно полностью терраформировать / автоматизировать. Не могли бы вы попросить что-нибудь еще от настройки сервера SFTP?

Если вы готовы сбросить свой старый SFTP-сервер, давайте рассмотрим, как подготовить SFTP-сервер AWS и его зависимости с помощью Terraform. К концу этого у вас не только будет SFTP-сервер, но он будет полностью автоматизирован и сможет поддерживать столько пользователей, сколько захотите.

План игры

План прост. У нас будет единый SFTP-сервер и корзина S3, доступная для всех пользователей. Домашние каталоги наших пользователей будут ${var.s3_bucket_name}/${var.username}. Мы определим разрешения так, чтобы каждый пользователь мог получить доступ только к контенту в своем домашнем каталоге.

Чтобы это произошло, мы создадим два набора кодов:

1. Код для настройки сервера SFTP. Это будет включать в себя все следующие ресурсы терраформирования:

  • aws_transfer_server - ресурс SFTP-сервера
  • iam_role - роль, позволяющая серверу регистрировать трафик
  • iam_policy - Политика, разрешающая серверу регистрировать трафик
  • aws_s3_bucket - корзина s3, в которой будут размещены все домашние каталоги для наших пользователей.
  • aws_route53_record - Создайте дружественное DNS-имя хоста для нашего сервера (например, sftp.domain.com)

2. Модуль для однократного создания экземпляра для каждого пользователя для доступа по SFTP.

  • aws_transfer_user - Создать пользователя в сервисе SFTP
  • aws_transfer_ssh_key - определяет SSH-ключ, который будет использоваться для аутентификации
  • aws_s3_bucket_object - создает пользовательский домашний каталог в сегменте SFTP s3, определенном выше.
  • aws_iam_role - определяет разрешения для пользователя в его домашнем каталоге
  • aws_iam_role_policy - определяет разрешения для пользователя в его домашнем каталоге

Конечным результатом является полностью автоматизированная установка. Он сможет вместить столько пользователей, сколько необходимо, и его будет легко обновлять. Если вы используете Terraform 0.13, вы сможете предпринять дополнительные шаги, чтобы ваш код оставался сухим. Также будет предоставлен подход для Terraform 0.12.

SFTP-сервер

Как указано выше, для создания нашего SFTP-сервера с помощью Terraform и AWS Transfer Family требуется всего 4 ресурса. Существует 5-й необязательный ресурс, который используется для создания записи DNS. Пользовательская запись DNS не требуется, поскольку AWS создаст запись DNS от вашего имени, но проще сообщить вашим клиентам:

  • «Перейти на sftp.domain.com»

vs.

  • «Перейдите на s-1x1x1x1x1x1x1x1x1x1x1x11x.server.transfer.us-east-1.amazonaws.com»

Давайте перейдем к коду, а затем объясним, что мы делаем.

resource "aws_transfer_server" "sftp" {
  identity_provider_type = "SERVICE_MANAGED"

  logging_role = aws_iam_role.sftp-logging.arn

  tags = {
    Terraform  = "true"
  }
}

resource "aws_iam_role" "sftp-logging" {
  name = "sftp-logging-role"

  assume_role_policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
        "Effect": "Allow",
        "Principal": {
            "Service": "transfer.amazonaws.com"
        },
        "Action": "sts:AssumeRole"
        }
    ]
}
EOF

  tags = {
    Name       = "sftp-transfer-logging-role"
    Terraform  = "true"
  }
}

resource "aws_iam_role_policy" "sftp-logging" {
  name = "sftp-logging-policy"
  role = aws_iam_role.sftp-logging.id

  policy = <<POLICY
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:DescribeLogStreams",
                "logs:CreateLogGroup",
                "logs:PutLogEvents"
            ],
            "Resource": "*"
        }
    ]
}
POLICY

}

resource "aws_s3_bucket" "sftp" {
  bucket = "sftp-homedirs"

  server_side_encryption_configuration {
    rule {
      apply_server_side_encryption_by_default {
        sse_algorithm = "AES256"
      }
    }
  }

  tags = {
    Name       = "sftp-homedirs"
    Terraform  = "true"
  }
}

resource "aws_route53_record" "sftpserver" {

  zone_id = var.aws_route53_id
  name    = "sftp.domain.com"
  type    = "CNAME"
  ttl     = "300"

  records = [aws_transfer_server.sftp.endpoint]
}

Если вы знакомы с AWS, большая часть этих ресурсов не должна быть для вас новой. Роль и политика IAM определяют разрешения, необходимые для сервера SFTP. В корзине s3 будут размещаться домашние каталоги всех наших пользователей. Единственный незнакомый ресурс здесь - это «aws_transfer_server».

Как видно из названия, это сам SFTP-сервер. Мы определяем только три варианта нашей настройки:

  • Поставщик удостоверений. Какой механизм аутентификации должен использовать сервер? «SERVICE_MANAGED» означает, что мы настроим доступ внутри самой службы. В качестве альтернативы вы также можете использовать шлюз API для аутентификации.
  • logging_role - присоединяет созданную нами роль IAM, чтобы сервер SFTP мог записывать журналы в CloudWatch.
  • Теги - теги AWS говорят сами за себя.

Полный список опций можно найти в документации Terraform: здесь

Пользовательский модуль SFTP

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

resource "aws_iam_role" "sftp" {
  name = "sftp-${var.username}-user-role"

  assume_role_policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
        "Effect": "Allow",
        "Principal": {
            "Service": "transfer.amazonaws.com"
        },
        "Action": "sts:AssumeRole"
        }
    ]
}
EOF

  tags = {
    Name       = "sftp-${var.username}-user-role"
    Terraform  = "true"
  }
}

resource "aws_iam_role_policy" "sftp" {
  name = "sftp-${var.username}-user-policy"
  role = aws_iam_role.sftp.id

  policy = <<POLICY
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowListingFolder",
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:GetBucketLocation"
            ],
            "Resource": "${var.s3_bucket_arn}",
            "Condition": {
                "StringLike": {
                    "s3:prefix": [
                        "${var.username}/*",
                        "${var.username}"
                    ]
                }
            }
        },
        {
            "Sid": "AllowReadWriteToObject",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObjectVersion",
                "s3:DeleteObject",
                "s3:GetObjectVersion"
            ],
            "Resource": "${var.s3_bucket_arn}/${var.username}*"
        }
    ]
}
POLICY
}

resource "aws_transfer_user" "user" {
  server_id      = var.sftp_server_id
  user_name      = var.username
  role           = aws_iam_role.sftp.arn
  home_directory = "/${var.s3_bucket_name}/${var.username}"

  tags = {
    Terraform  = "true"
  }
}

resource "aws_transfer_ssh_key" "user" {
  server_id = var.sftp_server_id
  user_name = aws_transfer_user.user.user_name
  body      = var.sshkey

}

Обратите внимание на разрешения, которые мы определяем. Поскольку все наши пользователи используют ведро s3, мы блокируем их разрешения для их конкретных домашних каталогов, чтобы убедиться, что они могут получить доступ только к своему собственному контенту. Это достигается такими настройками:

"Condition": {
                "StringLike": {
                    "s3:prefix": [
                        "${var.username}/*",
                        "${var.username}"
                    ]

or

"Resource": "${var.s3_bucket_arn}/${var.username}*"

Для вашей собственной настройки вы могли бы вместо этого создать корзину s3 для каждого пользователя, но это просто кажется ненужным, особенно если вы правильно заблокируете разрешения. Наличие всего в одной корзине S3 также дает множество преимуществ для целей анализа данных.

Помимо разрешений, стоит выделить только ресурсы «aws_transfer_ssh_key» и «aws_transfer_user». Как вы понимаете, эти ресурсы создают нашего пользователя и определяют SSH-ключ, который должен использоваться для аутентификации. Вы снова можете увидеть, как мы продолжаем использовать общую корзину s3.

home_directory = "/${var.s3_bucket_name}/${var.username}"

Необязательно: подготовить папки в домашнем каталоге пользователя. Это можно сделать следующим образом:

resource "aws_s3_bucket_object" "outbound" {
  bucket = var.s3_bucket_name
  acl    = "private"
  key    = "${var.username}/OUTBOUND/"
  source = "/dev/null"
}

Собираем все вместе

Поскольку SFTP-сервер не является модулем, вы можете просто запустить teraform apply, а Terraform сделает все остальное. Код пользователя SFTP - это модуль, поэтому его необходимо создать. В зависимости от вашей версии Terraform вам доступны два варианта:

Terraform ≤ 0,12:

До версии Terraform 0.13 невозможно использовать for_each в модулях. Это означает, что вам нужно будет определить модуль для каждого пользователя. Это выглядит примерно так:

module "exampleuser" {
  source = "../modules/aws-sftp-user"

  username       = "exampleuser"
  sshkey         = "ssh-rsa XXXXXXXXX"
  s3_bucket_arn  = aws_s3_bucket.sftp.arn
  s3_bucket_name = aws_s3_bucket.sftp.id
  sftp_server_id = aws_transfer_server.sftp.id
}
module "exampleuser2" {
  source = "../modules/aws-sftp-user"

  username       = "exampleuser2"
  sshkey         = "ssh-rsa YYYYYYYYYY"
  s3_bucket_arn  = aws_s3_bucket.sftp.arn
  s3_bucket_name = aws_s3_bucket.sftp.id
  sftp_server_id = aws_transfer_server.sftp.id
}
module "exampleuser3" {
  source = "../modules/aws-sftp-user"

  username       = "exampleuser3"
  sshkey         = "ssh-rsa ZZZZZZZZZZ"
  s3_bucket_arn  = aws_s3_bucket.sftp.arn
  s3_bucket_name = aws_s3_bucket.sftp.id
  sftp_server_id = aws_transfer_server.sftp.id
}

Terraform 0,13 (оптимальный):

Благодаря поддержке for_each, добавленной в Terraform 0.13, мы можем упростить приведенное выше следующим образом:

  • Создайте карту ваших пользователей и их ключей SSH.
variable "user_map" {
  type = "map"
  default = {
    exampleuser1 = "ssh-rsa XXXXXXXXXX"
    exampleuser2 = "ssh-rsa YYYYYYYYYY"
    exampleuser3 = "ssh-rsa ZZZZZZZZZZ"
  }
}

Затем создайте экземпляр своего модуля, используя for_each:

module "sftpusers" {
  for_each = var.user_map
  source = "../modules/aws-sftp-user"

  username       = each.key
  sshkey         = each.value
  s3_bucket_arn  = aws_s3_bucket.sftp.arn
  s3_bucket_name = aws_s3_bucket.sftp.id
  sftp_server_id = aws_transfer_server.sftp.id
}

В дальнейшем вы можете легко добавить нового пользователя, просто обновив переменную «user_map» и запустив terraform.

Заключение

За 10–20 минут написания кода мы смогли подготовить полнофункциональный SFTP-сервер. Он полностью автоматизирован, интегрируется с AWS, и его не нужно исправлять, делать избыточным / отказоустойчивым, контролировать дисковое пространство или укреплять. Обо всех накладных расходах уже позаботились, и вы можете пользоваться всеми преимуществами. Поскольку все это находится в AWS, вы даже можете делать то, что невозможно на обычном SFTP-сервере. Например, вы можете добавить триггеры в S3, чтобы лямбда-выражения запускались при загрузке файлов. Мы только что открыли возможности, убрав накладные расходы, это похоже на победу!