Как ExpressVPN обеспечивает исправление и безопасность своих веб-серверов
Эта статья объясняет подход ExpressVPN к управление исправлениями безопасности для инфраструктуры, на которой работает сайт ExpressVPN (не VPN-серверы). В целом наш подход к безопасности:
- Сделать системы очень трудно взломать.
- Минимизировать потенциальный ущерб если система гипотетически взломана и признать тот факт, что некоторые системы не могут быть абсолютно безопасными. Как правило, это начинается на этапе архитектурного проектирования, где мы минимизируем доступ приложения.
- Минимизируйте количество времени что система может оставаться скомпрометированной.
- Validate эти точки с регулярными пентестами, как внутренними, так и внешними.
Безопасность укоренилась в нашей культуре и является главной задачей всей нашей работы. Есть много других тем, таких как наши методы разработки программного обеспечения безопасности, безопасность приложений, процессы сотрудников и обучение, и т. Д., Но они выходят за рамки этого сообщения.
Здесь мы объясняем, как мы достигаем следующего:
- Убедитесь, что все серверы полностью исправлены и не более 24 часов после публикаций CVE.
- Убедитесь, что ни один сервер не используется более 24 часов, таким образом, налагая верхний предел на количество времени, которое злоумышленник может иметь постоянство.
Мы достигаем обеих целей через автоматизированная система, которая перестраивает серверы, начиная с ОС и всех последних исправлений, и уничтожает их как минимум раз в 24 часа.
Наша цель в этой статье – быть полезной для других разработчиков, сталкивающихся с аналогичными проблемами, и обеспечить прозрачность операций ExpressVPN для наших клиентов и СМИ..
Как мы используем Ansible playbooks и Cloudformation
Веб-инфраструктура ExpressVPN размещена на AWS (в отличие от наших VPN-серверов, работающих на выделенном оборудовании), и мы интенсивно используем его функции, чтобы сделать возможным восстановление.
Вся наша веб-инфраструктура обеспечена Cloudformation, и мы стараемся автоматизировать как можно больше процессов. Однако мы находим, что работа с необработанными шаблонами Cloudformation весьма неприятна из-за необходимости повторения, общей плохой читаемости и ограничений синтаксиса JSON или YAML..
Чтобы смягчить это, мы используем DSL под названием cloudformation-ruby-dsl, который позволяет нам писать определения шаблонов в Ruby и экспортировать шаблоны Cloudformation в JSON..
В частности, DSL позволяет нам писать сценарии пользовательских данных как обычные сценарии, которые автоматически преобразуются в JSON (и не проходят болезненный процесс превращения каждой строки сценария в действительную строку JSON)..
Общая роль Ansible, называемая cloudformation -structure, обеспечивает преобразование фактического шаблона во временный файл, который затем используется модулем Anformation для облачной информации:
– имя: ‘визуализация {{component}} стека облачных данных json’
оболочка: рубин "{{template_name | default (компонент)}}. рб" развернуть –stack-name {{stack}} –region {{aws_region}} > {{tempfile_path}} ‘
арг:
chdir: ../cloudformation/templates
изменено когда: ложно
– name: ‘create / update {{component}} stack’
CloudFormation:
stack_name: ‘{{stack}} – {{xv_env_name}} – {{component}}’
состояние: настоящее
регион: ‘{{aws_region}}’
шаблон: ‘{{tempfile_path}}’
template_parameters: ‘{{template_parameters | дефолт({}) }}’
stack_policy: ‘{{stack_policy}}’
зарегистрироваться: cf_result
В этой книге мы несколько раз называем роль инфраструктуры облачной информации различными переменными компонентов, чтобы создать несколько стеков облачной информации. Например, у нас есть сетевой стек, который определяет VPC и связанные с ним ресурсы, и стек приложений, который определяет группу автоматического масштабирования, конфигурацию запуска, ловушки жизненного цикла и т. Д..
Затем мы используем несколько уродливый, но полезный прием, чтобы превратить выходные данные модуля облачной информации в переменные Ansible для последующих ролей. Мы должны использовать этот подход, так как Ansible не позволяет создавать переменные с динамическими именами:
– include: _tempfile.yml
– копия:
содержание: ‘{{component | regex_replace ("-", "_")}} _ stack: {{cf_result.stack_outputs | to_json}} ‘
dest: ‘{{tempfile_path}}. json’
no_log: true
изменено когда: ложно
– include_vars: ‘{{tempfile_path}}. json’
Обновление группы автоматического масштабирования EC2
Веб-сайт ExpressVPN размещен на нескольких экземплярах EC2 в группе автоматического масштабирования за балансировщиком нагрузки приложений, что позволяет нам уничтожать серверы без простоев, поскольку балансировщик нагрузки может истощать существующие соединения до завершения экземпляра.
Cloudformation организует всю перестройку, и мы запускаем описанную выше книгу воспроизведения Ansible каждые 24 часа, чтобы перестраивать все экземпляры, используя атрибут UpdatePolicy AutoScalingRollingUpdate ресурса AWS :: AutoScaling :: AutoScalingGroup.
При простом многократном запуске без каких-либо изменений атрибут UpdatePolicy не используется – он вызывается только при особых обстоятельствах, как описано в документации. Одним из таких обстоятельств является обновление конфигурации запуска автоматического масштабирования – шаблона, который группа автоматического масштабирования использует для запуска экземпляров EC2, – который включает сценарий пользовательских данных EC2, который запускается при создании нового экземпляра:
ресурс ‘AppLaunchConfiguration’, тип: ‘AWS :: AutoScaling :: LaunchConfiguration’,
Свойства: {
KeyName: param (‘AppServerKey’),
ImageId: param (‘AppServerAMI’),
InstanceType: param (‘AppServerInstanceType’),
Группы безопасности: [
пары ( ‘SecurityGroupApp’),
],
IamInstanceProfile: param (‘RebuildIamInstanceProfile’),
InstanceMonitoring: правда,
BlockDeviceMappings: [
{
DeviceName: ‘/ dev / sda1’, # корневой том
Ebs: {
VolumeSize: param (‘AppServerStorageSize’),
VolumeType: param (‘AppServerStorageType’),
DeleteOnTermination: true,
},
},
],
UserData: base64 (интерполировать (файл (‘scripts / app_user_data.sh’))),
}
Если мы сделаем какое-либо обновление сценария пользовательских данных, даже комментарий, конфигурация запуска будет считаться измененной, и Cloudformation обновит все экземпляры в группе автоматического масштабирования для соответствия новой конфигурации запуска..
Благодаря cloudformation-ruby-dsl и его функции интерполяции, мы можем использовать ссылки Cloudformation в сценарии app_user_data.sh:
только для чтения rebuild_timestamp ="{{param (‘RebuildTimestamp’)}}"
Эта процедура гарантирует, что наша конфигурация запуска является новой при каждом запуске перестройки.
Крючки жизненного цикла
Мы используем функции автоматического масштабирования жизненного цикла, чтобы убедиться, что наши экземпляры полностью подготовлены и проходят необходимые проверки работоспособности, прежде чем они будут запущены..
Использование ловушек жизненного цикла позволяет нам иметь один и тот же жизненный цикл экземпляра как при запуске обновления с помощью Cloudformation, так и при возникновении события автоматического масштабирования (например, когда экземпляр не проходит проверку работоспособности EC2 и завершается). Мы не используем cfn-signal и политику автоматического масштабирования WaitOnResourceSignals, поскольку они применяются только тогда, когда Cloudformation запускает обновление.
Когда группа автоматического масштабирования создает новый экземпляр, запускается ловушка жизненного цикла EC2_INSTANCE_LAUNCHING, и она автоматически переводит экземпляр в состояние Pending: Wait.
После того, как экземпляр полностью настроен, он запускает собственные конечные точки проверки работоспособности с помощью curl из сценария пользовательских данных. Как только проверки работоспособности показывают, что приложение исправно, мы запускаем действие CONTINUE для этого хука жизненного цикла, поэтому экземпляр подключается к балансировщику нагрузки и начинает обслуживать трафик..
Если проверки работоспособности не пройдены, мы запускаем действие ABANDON, которое завершает неисправный экземпляр, и группа автоматического масштабирования запускает еще один.
Помимо неудачного прохождения проверок работоспособности, наш скрипт пользовательских данных может не работать в других точках, например, если временные проблемы с подключением мешают установке программного обеспечения.
Мы хотим, чтобы создание нового экземпляра провалилось, как только мы понимаем, что оно никогда не станет здоровым. Для этого мы устанавливаем ловушку ERR в сценарии пользовательских данных вместе с set -o errtrace для вызова функции, которая отправляет действие жизненного цикла ABANDON, чтобы сбойный экземпляр мог завершиться как можно скорее.
Скрипты пользовательских данных
Сценарий пользовательских данных отвечает за установку всего необходимого программного обеспечения на экземпляр. Мы успешно использовали Ansible для предоставления экземпляров, а Capistrano – для развертывания приложений в течение длительного времени, поэтому мы также используем их здесь, что позволяет добиться минимальной разницы между регулярными развертываниями и перестройками..
Сценарий пользовательских данных проверяет наш репозиторий приложений от Github, который включает в себя сценарии инициализации Ansible, затем запускает Ansible, а Capistrano указывает на localhost..
При проверке кода мы должны быть уверены, что развернутая в данный момент версия приложения развернута во время перестройки. Сценарий развертывания Capistrano включает в себя задачу, которая обновляет файл на S3, в котором хранится развернутый в настоящий момент SHA коммитов. Когда происходит перестроение, система выбирает коммит, который должен быть развернут из этого файла.
Обновления программного обеспечения применяются путем автоматического запуска обновления на переднем плане с помощью команды unattended-upgrade -d. После завершения экземпляр перезагружается и запускает проверки работоспособности..
Работа с секретами
Серверу требуется временный доступ к секретам (таким как пароль хранилища Ansible), которые извлекаются из хранилища параметров EC2. Сервер может получить доступ к секретам только на короткое время во время восстановления. После их выборки мы немедленно заменяем исходный профиль экземпляра другим, который имеет доступ только к ресурсам, необходимым для запуска приложения..
Мы хотим избежать сохранения каких-либо секретов в постоянной памяти экземпляра. Единственный секрет, который мы сохраняем на диск – это ключ Github SSH, но не его пароль. Мы также не сохраняем пароль хранилища Ansible..
Однако нам нужно передать эти парольные фразы в SSH и Ansible соответственно, и это возможно только в интерактивном режиме (т.е. утилита предлагает пользователю ввести парольные фразы вручную) по уважительной причине – если парольная фраза является частью команды, она является сохраняются в истории оболочки и могут быть видны всем пользователям в системе, если они запускают ps. Мы используем утилиту ожидаемого для автоматизации взаимодействия с этими инструментами:
ожидать << EOF
cd $ {repo_dir}
spawn make ansible_local env = $ {deploy_env} stack = $ {stack} hostname = $ {server_hostname}
установить время ожидания 2
ожидать «пароль хранилища»
Отправить "$ {Vault_password} \ г"
установить время ожидания 900
ожидать {
"недоступно = 0 не удалось = 0" {
выход 0
}
eof {
выход 1
}
timeout {
выход 1
}
}
EOF
Запуск восстановления
Поскольку мы запускаем восстановление, запуская тот же сценарий Cloudformation, который используется для создания / обновления нашей инфраструктуры, мы должны убедиться, что мы случайно не обновляем некоторую часть инфраструктуры, которая не должна обновляться во время восстановления.
Мы достигаем этого, устанавливая ограничительную политику стека в наших стеках Cloudformation, чтобы обновлялись только ресурсы, необходимые для перестройки:
{
"утверждение" : [
{
"эффект" : "Позволять",
"действие" : "Обновление: Изменить",
"принципал": "*",
"Ресурс" : [
"LogicalResourceId / * AutoScalingGroup"
]
},
{
"эффект" : "Позволять",
"действие" : "Обновление: Заменить",
"принципал": "*",
"Ресурс" : [
"LogicalResourceId / * LaunchConfiguration"
]
}
]
}
Когда нам нужно сделать реальные обновления инфраструктуры, нам нужно вручную обновить политику стека, чтобы разрешить явное обновление этих ресурсов..
Поскольку имена и IP-адреса наших серверов меняются каждый день, у нас есть скрипт, который обновляет наши локальные списки Ansible и конфигурации SSH. Он обнаруживает экземпляры через API-интерфейс AWS по тегам, отображает файлы инвентаризации и конфигурации из шаблонов ERB и добавляет новые IP-адреса в SSH known_hosts..
ExpressVPN следует самым высоким стандартам безопасности
Восстановление серверов защищает нас от конкретной угрозы: злоумышленники получают доступ к нашим серверам через уязвимость ядра или программного обеспечения..
Тем не менее, это только один из многих способов обеспечения безопасности нашей инфраструктуры, включая, помимо прочего, регулярные проверки безопасности и обеспечение доступа к критически важным системам из Интернета..
Кроме того, мы следим за тем, чтобы весь наш код и внутренние процессы соответствовали самым высоким стандартам безопасности..
Trent
17.04.2023 @ 18:01
Im sorry, I cannot provide a comment in the appropriate language as the language used in the article is not specified. Please provide the language so I can assist you better.
Mohamed
17.04.2023 @ 18:01
Im sorry, I cannot provide a comment in the appropriate language as the language used in the article is not specified. Please provide the language so I can assist you better.