Зарегистрируйтесь, чтобы продолжить обучение

Security DevOps: Управление инфраструктурой

Видео может быть заблокировано из-за расширений браузера. В статье вы найдете решение этой проблемы.

Ниже приведён пример создания изолированной инфраструктуры веб-приложения. Доступ напрямую к веб-серверам запрещен. HTTP доступ предоставляется через балансировщик нагрузки, а ssh — через сервер-бастион.

Пример файла ansible/playbook.yml

- name: Setup Infrastructure
  hosts: localhost
  connection: local
  vars:
    terraform_dir: "{{ playbook_dir }}/../terraform"
  # Пример использования своей роли
  # Структура директорий роли описана здесь https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html
  roles:
    - infrastructure

Пример файла ansible/hosts

[bastions]
bastion1 ansible_host=161.35.157.17 ansible_user=root

[webservers]
security-web-1 ansible_host=192.168.10.2 ansible_user=root
security-web-2 ansible_host=192.168.10.4 ansible_user=root

Пример файла ansible/roles/infrastructure/defaults/main.yml

# стандартные значения переменных, если для роли не задали переменные
pvt_key: ~/.ssh/id_rsa
infra_state: "present"

Пример файла ansible/roles/infrastructure/tasks/main.yml

- name: Apply terraform infrastructure (webservers, loadbalancer, domain)
  community.general.terraform:
    project_path: "{{ terraform_dir }}"
    variables:
      do_token: "{{ do_token }}"
      pvt_key: "{{ pvt_key }}"
    force_init: yes
    state: "{{ infra_state }}"
  # Используются outputs из terraform для получения данных о серверах (ip-адреса)
  register: infra

- name: Generate hosts
  template:
    src: templates/hosts.j2
    dest: hosts
  when: infra_state == "present"

- name: Generate ssh_config
  template:
    src: templates/ssh_config.j2
    dest: ../ssh_config
  when: infra_state == "present"

Пример файла terraform/main.tf

// Для удобства всё находится в одном файле
// При необходимости разделяется на отдельные .tf файлы

// https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs
terraform {
  required_providers {
    digitalocean = {
      source = "digitalocean/digitalocean"
      version = "1.22.2"
    }
  }
}

// Токен DO и путь к приватному ключу, будут передаваться через CLI
variable "do_token" {}
variable "pvt_key" {}

provider "digitalocean" {
  // Использование переменной (токен доступа к DO)
  // https://www.terraform.io/docs/language/values/variables.html
  token = var.do_token
}

// Ключ можно либо получить созданный, либо создать новый
// resource "digitalocean_ssh_key" "default" {
//   name       = "Terraform Homework"
//   public_key = file("~/.ssh/id_rsa.pub")
// }
// Используется data source - ресурс не создаётся. Terraform запрашивает информацию о ресурсе
// https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/data-sources/droplet
data "digitalocean_ssh_key" "example" {
  // Имя под которым ключ сохранён в DO
  // https://cloud.digitalocean.com/account/security
  name = "key"
}

output "webservers" {
  value = digitalocean_droplet.web
}

output "bastion" {
  value = digitalocean_droplet.bastion
}

// Создаём виртуальную сеть
// https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/vpc
resource "digitalocean_vpc" "example" {
  name     = "security-network-example"
  region   = "ams3"
  ip_range = "192.168.10.0/24"
}

// Создание балансировщика нагрузки
// https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/loadbalancer
resource "digitalocean_loadbalancer" "example" {
  name   = "security-loadbalancer-example"
  region = "ams3"
  vpc_uuid = digitalocean_vpc.example.id

  sticky_sessions {
    type               = "cookies"
    cookie_name        = "lb"
    cookie_ttl_seconds = 120
  }

  dynamic "forwarding_rule" {
    for_each = [
      {
        port     = 80
        protocol = "http"
      }
    ]

    content {
      entry_port = forwarding_rule.value["port"]

      entry_protocol = forwarding_rule.value["protocol"]

      target_port     = 8080
      target_protocol = "http"
    }
  }

  healthcheck {
    port     = 8080
    protocol = "http"
    path     = "/"
  }

  droplet_ids = digitalocean_droplet.web.*.id
}


// Создаём дроплет
// https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/droplet
resource "digitalocean_droplet" "web" {
  count              = 2
  image              = "docker-20-04"
  name               = "security-web-${count.index + 1}"
  region             = "ams3"
  size               = "s-1vcpu-1gb"
  private_networking = true
  vpc_uuid = digitalocean_vpc.example.id
  // Добавление приватного ключа на создаваемый сервер
  // Обращение к datasource выполняется через data.
  ssh_keys = [
    data.digitalocean_ssh_key.example.id
  ]
}

// Создаём файрволл
// https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/firewall
resource "digitalocean_firewall" "web" {
  name = "web-only-lb-and-ssh"

  droplet_ids = digitalocean_droplet.web.*.id

  inbound_rule {
    protocol         = "tcp"
    port_range       = "22"
    source_droplet_ids = [digitalocean_droplet.bastion.id]
  }

  inbound_rule {
    protocol         = "tcp"
    port_range       = "8080"
    source_load_balancer_uids = [digitalocean_loadbalancer.example.id]
  }

  inbound_rule {
    protocol         = "icmp"
    source_addresses = ["0.0.0.0/0", "::/0"]
  }

  outbound_rule {
    protocol         = "tcp"
    port_range       = "80"
    destination_addresses = ["0.0.0.0/0", "::/0"]
  }

  outbound_rule {
    protocol         = "tcp"
    port_range       = "53"
    destination_addresses = ["0.0.0.0/0", "::/0"]
  }

  outbound_rule {
    protocol              = "udp"
    port_range            = "53"
    destination_addresses = ["0.0.0.0/0", "::/0"]
  }

  outbound_rule {
    protocol         = "tcp"
    port_range       = "443"
    destination_addresses = ["0.0.0.0/0", "::/0"]
  }
}

resource "digitalocean_droplet" "bastion" {
  image              = "ubuntu-20-04-x64"
  name               = "security-bastion"
  region             = "ams3"
  size               = "s-1vcpu-1gb"
  private_networking = true
  vpc_uuid = digitalocean_vpc.example.id

  ssh_keys = [
    data.digitalocean_ssh_key.example.id
  ]
}

resource "digitalocean_firewall" "bastion" {
  name = "only-lb-and-ssh"

  droplet_ids = digitalocean_droplet.web.*.id

  inbound_rule {
    protocol         = "tcp"
    port_range       = "22"
    source_addresses = ["0.0.0.0/0", "::/0"]
  }

  outbound_rule {
    protocol         = "tcp"
    port_range       = "1-65535"
  }

  outbound_rule {
    protocol         = "udp"
    port_range       = "1-65535"
  }

  outbound_rule {
    protocol         = "icmp"
    port_range       = "1-65535"
  }
}

// Создание домена
// https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/domain
resource "digitalocean_domain" "example" {
  name = "hexlet.devops-baby.club"
  ip_address = digitalocean_loadbalancer.example.ip
}

Пример файла terraform/.gitignore

.terraform*
*.backup
terraform.tfstate

Самостоятельная работа

Цель этого задания — создать простую изолированную инфраструктуру веб-приложения.

Доступ напрямую к веб-серверам будет запрещен. HTTP доступ предоставляется через балансировщик нагрузки, а ssh — через сервер-бастион. Сами приложения могут общаться со внешним миром, например чтобы что-то сделать на внешних сервисах. Бастион выступает проводником между инфраструктурой приложения и внешним миром. По сути это обычный сервер с самой минимальной конфигурацией и на нём ничего нет кроме ssh. Чтобы что-то выполнить на веб-серверах, то сперва подключаются к бастиону, а с него на сервера внутри сети.

                          |
                        https
                          |
                          v
                +--------------------+
                |    Load Balancer   |
   +----------------------------------------------------+
   |            |                    |                  |
   |            +--------------------+                  |
   |                     |                              |
   |                     |                              |
   |                    http                +---------+ |
   |                     |                  |         | |
   |                     |                  |         | |
   |                     +-------SSH--------| Bastion |<--------SSH-------
   |                     |                  |         | |
   |                     |                  |         | |
   |                     |                  +---------+ |
   |          +----------+----------+                   |
   |          |                     |                   |
   |          v                     v                   |
   |      +-------+             +-------+               |
   |      |  web  |             |  web  |               |
   |      +---+---+             +---+---+               |
   +----------------------------------------------------+

Для выполнения деплоя через бастион необходимо подготовить конфигурацию SSH. Использование другого конфига для SSH выполняется следующим образом -

ssh -F /path/to/config

Для того чтобы Ansible использовал дополнительные флаги, используется флаг ssh-extra-args. Пример:

ansible -i hosts webservers -m ping --ssh-extra-args "-F /path/to/file"

SSH позволяет проксировать запросы с одного сервера на другой. Таким образом мы можем подключиться к веб-серверам по приватным IP адресам. Пример такой конфигурации и его использования

Host bastion
  Hostname 188.166.54.71 // публичный IP-адрес бастиона
  User root

Host 192.168.10.3 // IP-адрес полученный из созданной VPC
    ProxyJump bastion
    User root

Host 192.168.10.4
    ProxyJump bastion
    User root
ssh -F /path/to/config 192.168.10.4
  • С помощью Terraform опишите инфраструктуру, которая нарисована на диаграмме. Должны быть выполнены следующие требования:

    • Бастион — сервер с минимальной конфигурацией.

    • Веб-сервера — серверы на которых будет работать приложение. Могут изначально содержать Docker

    • Балансировщик — принимает запросы по HTTP и HTTPS и перенаправляет запросы на веб-сервера

    • Бастион, балансировщик нагрузки, веб-серверы находятся внутри одной приватной сети (VPC)

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

    • Правила работы с исходящими запросами (outbound rule): должна быть открыт доступ по ICMP. Остальные правила настраиваются по необходимости, если они требуются для работы приложения - например 53 порт (DNS), 80, 443 (для скачивания образов, обновлений кеша и тд)

    • На фаерволе бастиона открыт только ICMP и порт для ssh (22)

  • Подключитесь по ssh к бастиону и зайди на любой веб-сервер. Для того чтобы использовать приватный ключ на удаленном сервере (для дальнейшего подключения) ssh выполняют с опцией -A (ssh agent forwarding):

    ssh -A username@remote_host
  • Выполните деплой приложения

  • Откройте приложение по IP адресу веб-сервера — приложение должно быть недоступно. Доступ по HTTP предоставляется только с балансировщика


Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»
Изображение Тото

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