Как ExpressVPN поддържа своите уеб сървъри закърпени и защитени

ExpressVPN сървърът се издига от пепелта.

Тази статия обяснява подхода на ExpressVPN към управление на патча за сигурност за инфраструктурата, изпълняваща уебсайта ExpressVPN (не VPN сървърите). По принцип подходът ни към сигурността е:

  1. Направете системи много трудно за хакване.
  2. Минимизирайте потенциалните щети ако една система хипотетично се хакне и признае факта, че някои системи не могат да бъдат направени напълно сигурни. Обикновено това започва от фазата на архитектурно проектиране, където намаляваме достъпа на приложението.
  3. Минимизирайте количеството време че една система може да остане компрометирана.
  4. Проверка тези точки с редовни петести, вътрешни и външни.

Сигурността е вкоренена в нашата култура и е основната грижа, която ръководи цялата ни работа. Има много други теми като нашите практики за разработка на софтуер за сигурност, сигурност на приложенията, процеси и обучение на служители и т.н., но те са извън обхвата на тази публикация.

Тук обясняваме как постигаме следното:

  1. Уверете се, че всички сървъри са пълни и никога повече от 24 часа зад публикации на CVEs.
  2. Уверете се, че никой сървър никога не се използва повече от 24 часа, по този начин се поставя горна граница за времето, което нападателят може да има постоянство.

Ние постигаме и двете цели чрез автоматизирана система, която възстановява сървърите, започвайки от операционната система и всички най-нови корекции, и ги унищожава поне веднъж на 24 часа.

Нашето намерение за тази статия е да бъдем полезни за други разработчици, изправени пред подобни предизвикателства и да дадем прозрачност на операциите на ExpressVPN за нашите клиенти и медии.

Как използваме Ansplay playbooks и Cloudformation

Уеб инфраструктурата на ExpressVPN се хоства на AWS (за разлика от нашите VPN сървъри, които работят на специализиран хардуер) и ние използваме сериозно неговите функции, за да направим възможна възстановяването.

Цялата ни уеб инфраструктура е снабдена с Cloudformation и ние се опитваме да автоматизираме възможно най-много процеси. Ние обаче намираме, че работата със сурови шаблони на Cloudformation е доста неприятна поради необходимостта от повторение, цялостна лоша четимост и ограниченията на JSON или YAML синтаксиса.

За да смекчим това, използваме DSL, наречен cloudformation-ruby-DSL, който ни позволява да пишем дефиниции на шаблони в Ruby и да експортираме шаблони на Cloudformation в JSON.

По-специално, DSL ни позволява да пишем скриптове за потребителски данни като обикновени скриптове, които се преобразуват автоматично в JSON (и не преминават през болезнения процес на превръщане на всеки ред от скрипта във валиден JSON низ).

Ролевата роля на Ansible, наречена cloudformation-инфраструктура, се грижи за превръщането на действителния шаблон във временен файл, който след това се използва от модула за отговор на cloudformation:

- name: 'render {{компонент}} облак на информация за стека json'
черупка: 'рубин "{{шаблон_име | по подразбиране (компонент)}}. rb" разширяване - име на стек {{стек}} - регион {{aws_region}} > {{tempfile_path}} '
аргументи:
chdir: ../cloudformation/templates
change_when: false

- име: 'създаване / актуализация {{компонент}} стек'
cloudformation:
stack_name: '{{стек}} - {{xv_env_name}} - {{компонент}}'
състояние: настоящ
регион: '{{aws_region}}'
шаблон: '{{tempfile_path}}'
template_parameters: '{{template_parameters | по подразбиране({}) }}'
stack_policy: '{{stack_policy}}'
регистър: cf_result

В плейбека няколко пъти наричаме облачната инфраструктура-инфраструктура с различни променливи на компонента, за да създадем няколко стека на Cloudformation. Например имаме мрежов стек, който дефинира VPC и свързаните с него ресурси, и стек на приложението, който определя групата за автоматично мащабиране, конфигурация на стартиране, куки за жизнения цикъл и т.н..

След това използваме малко грозен, но полезен трик, за да превърнем изхода на модула за облачност в променливи Ansible за следващи роли. Трябва да използваме този подход, тъй като Ansible не позволява създаването на променливи с динамични имена:

- включват: _tempfile.yml
- копие:
съдържание: '{{компонент | regex_replace ("-", "_")}} _ стек: {{cf_result.stack_outputs | to_json}} '
dest: '{{tempfile_path}}. json'
no_log: вярно
change_when: false

- include_vars: '{{tempfile_path}}. json'

Актуализиране на EC2 групата за автоматично мащабиране

Уебсайтът ExpressVPN се хоства на множество EC2 инстанции в група за автоматично мащабиране зад балансиращо устройство за приложение, което ни позволява да унищожаваме сървъри без прекъсване, тъй като балансиращият натоварване може да източи съществуващите връзки преди да се прекрати екземпляр.

Облачната оркестрация организира цялата реконструкция и ние задействаме описаната по-горе пиесица Ansible на всеки 24 часа, за да възстановява всички случаи, използвайки атрибута AutoScalingRollingUpdate UpdatePolicy на AWS :: AutoScaling :: AutoScalingGroup ресурс.

Когато просто се задейства многократно без никакви промени, атрибутът UpdatePolicy не се използва - той се извиква само при специални обстоятелства, както е описано в документацията. Едно от тези обстоятелства е актуализация на конфигурацията за стартиране на автоматично мащабиране - шаблон, който групата за автоматично мащабиране използва за стартиране на EC2 екземпляри - която включва скрипта на потребителските данни на EC2, който работи при създаването на нов екземпляр:

ресурс 'AppLaunchConfiguration', Тип: 'AWS :: AutoScaling :: LaunchConfiguration',
Имоти: {
KeyName: param („AppServerKey“),
ImageId: param („AppServerAMI“),
InstanceType: param („AppServerInstanceType“),
SecurityGroups: [
параметър ( "SecurityGroupApp"),
],
IamInstanceProfile: param ('RebuildIamInstanceProfile'),
InstanceMonitoring: true,
BlockDeviceMappings: [
{
DeviceName: '/ dev / sda1', # root root
Ebs: {
VolumeSize: парам („AppServerStorageSize“),
VolumeType: param („AppServerStorageType“),
DeleteOnTermina: вярно,
}
}
],
UserData: base64 (интерполат (файл ('скриптове / app_user_data.sh'))),
}

Ако направим някаква актуализация на скрипта на потребителските данни, дори коментар, конфигурацията за стартиране ще се счита за променена и Cloudformation ще актуализира всички случаи в групата за автоматично скалиране, за да се съобрази с новата конфигурация на стартиране.

Благодарение на cloudformation-ruby-dsl и неговата функция за интерполация на полезността, можем да използваме препратки към Cloudformation в скрипта на app_user_data.sh:

readonly rebuild_timestamp ="{{param ('RebuildTimestamp')}}"

Тази процедура гарантира, че нашата стартираща конфигурация е нова всеки път, когато се задейства презастройката.

Куки за жизнения цикъл

Използваме куки за жизнен цикъл с автоматично мащабиране, за да се уверим, че нашите случаи са напълно предвидени и преминат необходимите здравни проверки, преди да станат на живо.

Използването на куки за жизнения цикъл ни позволява да имаме един и същ жизнен цикъл на екземпляра, както когато задействаме актуализацията с Cloudformation, така и когато се случи събитие с автоматично мащабиране (например, когато екземпляр не успее проверка на здравето на EC2 и бъде прекратен). Не използваме cfn-сигнал и правилата за актуализиране на автоматичното мащабиране WaitOnResourceSignals, защото те се прилагат само когато Cloudformation задейства актуализация.

Когато групата за автоматично мащабиране създава нов екземпляр, EC2_INSTANCE_LAUNCHING кука за жизнения цикъл се задейства и тя автоматично поставя екземпляра в състояние Pending: Wait.

След като екземплярът е напълно конфигуриран, той започва да удря собствените си крайни точки за проверка на здравето с къдряне от скрипта на потребителските данни. След като здравните проверки отчитат, че приложението е здравословно, ние издаваме ПРОДЪЛЖИТЕЛНО действие за тази кука на жизнения цикъл, така че екземплярът се привързва към балансиращия натоварване и започва да обслужва трафика.

Ако здравните проверки се провалят, ние издаваме действие ABANDON, което прекратява повредената инстанция и групата за автоматично мащабиране стартира друга.

Освен че не успее да премине здравните проверки, скриптът ни с потребителски данни може да се провали в други точки - например, ако временните проблеми с свързаността предотвратяват инсталирането на софтуер.

Искаме създаването на нова инстанция да се провали веднага щом разберем, че тя никога няма да стане здрава. За да постигнем това, ние задаваме ERR капан в скрипта на потребителските данни, заедно с set -o errtrace, за да извикаме функция, която изпраща действие на жизнения цикъл на ABANDON, така че дефектна инстанция може да се прекрати възможно най-бързо.

Скриптове за потребителски данни

Скриптът на потребителските данни отговаря за инсталирането на целия необходим софтуер на инстанцията. Успешно използвахме инстанциите на Ansible to предоставянето и Capistrano за разгръщане на приложения за дълго време, така че ние също ги използваме тук, позволявайки минималната разлика между обикновените устройства и възстановявания.

Скриптът на потребителските данни проверява нашето хранилище на приложения от Github, което включва скриптове за предоставяне на Ansible, след това изпълнява Ansible и Capistrano посочва localhost.

Когато проверяваме кода, трябва да сме сигурни, че понастоящем разгърнатата версия на приложението е разгърната по време на възстановяването. Скриптът за внедряване на Capistrano включва задача, която актуализира файл в S3, който съхранява в момента разгърнатата комисия SHA. Когато възстановяването се случи, системата извежда ангажимента, който трябва да бъде разгърнат от този файл.

Актуализациите на софтуера се прилагат като стартират надграждане без надзор на преден план с командата без надзор надстройка -d. След като приключи, екземплярът се рестартира и стартира здравните проверки.

Справяне с тайни

Сървърът се нуждае от временен достъп до тайни (като паролата на Ansult treult), които се получават от EC2 Parameter Store. Сървърът може да получи достъп до тайни само за кратко време по време на възстановяването. След като бъдат извлечени, ние незабавно заместваме профила на първоначалния екземпляр с друг, който има достъп само до ресурси, необходими за стартиране на приложението.

Искаме да избегнем запазването на някакви тайни в постоянната памет на инстанцията. Единствената тайна, която спестяваме на диска, е ключът на Github SSH, но не и неговата парола. Ние също така не запазваме паролата на хранилището Ansible.

Трябва обаче да предадем тези пароли съответно на SSH и Ansible и това е възможно само в интерактивен режим (т.е. полезната програма подканва потребителя да въведе ръчно фразите) по добра причина - ако паролата е част от командата, тя е записани в историята на черупките и могат да бъдат видими за всички потребители в системата, ако те изпълняват ps. Използваме помощната програма очакваме, за да автоматизираме взаимодействието с тези инструменти:

очаквам << EOF
cd $ {repo_dir}
spawn make ansible_local env = $ {implemen_env} stack = $ {stack} име на хост = $ {server_hostname}
задайте изчакване 2
очаквайте „Парола за трезори“
изпрати "$ {Vault_password} \ г"
задайте изчакване 900
очаквам {
"недостъпно = 0 неуспешно = 0" {
изход 0
}
eof {
изход 1
}
изчакване {
изход 1
}
}
EOF

Задействане на възстановяването

Тъй като ние задействаме възстановяването, като стартираме същия скрипт на Cloudformation, който се използва за създаване / актуализиране на нашата инфраструктура, трябва да сме сигурни, че случайно не актуализираме част от инфраструктурата, която не трябва да се актуализира по време на възстановяването.

Ние постигаме това чрез задаване на рестриктивна политика за стека на нашите стекове на Cloudformation, така че да се актуализират само необходимите ресурси за възстановяването:

{
"изявление" : [
{
"ефект" : "Позволява",
"действие" : "Update: Промени",
"основен": "*",
"средство" : [
"LogicalResourceId / * AutoScalingGroup"
] }
{
"ефект" : "Позволява",
"действие" : "Актуализация: Заменете",
"основен": "*",
"средство" : [
"LogicalResourceId / * LaunchConfiguration"
] }
] }

Когато трябва да направим актуални актуализации на инфраструктурата, трябва ръчно да актуализираме правилата за стека, за да разрешим изрично актуализациите на тези ресурси.

Тъй като имената на хостовете и IP адресите на нашия сървър се променят всеки ден, имаме скрипт, който актуализира нашите локални инвентаризации на Ansible и SSH конфигурации. Той открива случаите чрез AWS API по тагове, прави инвентара и конфигурира файлове от шаблони на ERB и добавя новите IP адреси към SSH известни_хости.

ExpressVPN следва най-високите стандарти за сигурност

Възстановяването на сървъри ни защитава от специфична заплаха: атакуващите да получат достъп до нашите сървъри чрез уязвимост на ядрото / софтуера.

Това обаче е само един от многото начини да запазим сигурността на нашата инфраструктура, включително, но не само, да се подлагаме на редовни одити за сигурност и да правим критични системи недостъпни от интернет.

Освен това ние се уверяваме, че всички наши кодови и вътрешни процеси следват най-високите стандарти за сигурност.

Как ExpressVPN поддържа своите уеб сървъри закърпени и защитени
admin Author
Sorry! The Author has not filled his profile.