Сегодня просматривал IAM-пользователей в AWS, и вспомнил, что с точки зрения безопасности иногда полезно менять ключи доступа:
Но тут встал вопрос: хорошо, задать ключам expire, и периодически их обновлять в AWS IAM — это одно…
Но эти ключи используются в куче скриптов, которые запускаются в Jenkins-джобах.
Например — провижен бекенда выполняется из Ansible и модуля cloudformation
, который использует ключи IAM-пользователя, к которому подключена политика доступа к EC2/RDS/CloudFormation etc.
Дата-аналитики имеют свои джобы, в которых выполняются всякие ETL-задачи, и результат складывается в AWS S3 корзины, и, соответственно, используют для авторизации ключи IAM-пользователя, к которому подключена политика, разрешающая доступ к определённым корзинам.
И что — каждый раз обновлять ключи во всех этих джобах?
Как вариант — можно было бы поднять Hashicorp Vault, и через него выдавать токены доступа всем таким задачам, но это во-первых кусок работы по развёртыванию самого Vault, его бекапированию и т.д., во-вторых — кусок работы по переделке скриптов/задач.
Решение оказалось очень простым и внезапным для меня: использовать IAM roles для EC2.
Внезапно — потому что эта возможность в AWS есть ещё с 2012 года, и я даже пользовался ей, и не раз, например при настройке CloudWatch агентов, но как-то не приходило в голову, что, оказывается, boto3, например, умеет искать EC2 IAM Role, если не найдены другие способы авторизации:
- Passing credentials as parameters in the boto.client() method
- Passing credentials as parameters when creating a Session object
- Environment variables
- Shared credential file (~/.aws/credentials)
- AWS config file (~/.aws/config)
- Assume Role provider
- Boto2 config file (/etc/boto.cfg and ~/.boto)
- Instance metadata service on an Amazon EC2 instance that has an IAM role configured.
Собственно, что делаем:
- создаём IAM роль с нужными политиками
- подключаем роль к EC2
- пользуемся AWS CLI (для примера) с этого EC2 без небходимости создавать ACCESS/SECRET ключи
Поехали.
Содержание
Создание IAM-роли
Создаём роль, выбираем тип EC2:
Подключаем политику, тут для примера AmazonRoute53ReadOnlyAccess:
Сохраняем новую роль:
Запуск EC2
Запускаем EC2, и указываем ему IAM-роль:
Проверка
Логинимся на инстанс:
[simterm]
$ ssh [email protected] -i setevoy-testing-eu-west-1.pem
[/simterm]
Проверяем IAM в мета-данных инстанса:
[simterm]
root@ip-172-31-42-77:/home/admin# curl http://169.254.169.254/latest/meta-data/iam/info { "Code" : "Success", "LastUpdated" : "2019-05-30T10:54:26Z", "InstanceProfileArn" : "arn:aws:iam::534***385:instance-profile/ec2-example-role" ...
[/simterm]
Устанавливаем AWS CLI:
[simterm]
root@ip-172-31-42-77:/home/admin# apt update && apt -y install awscli
[/simterm]
И получаем список зон без регистрации и СМС какой-либо настройки CLI:
[simterm]
root@ip-172-31-42-77:/home/admin# aws route53 list-hosted-zones --output text HOSTEDZONES 33C2D264-***-***-3052BEA607A9 /hostedzone/Z30***LB6 example.com. 104 CONFIG DME sites False ...
[/simterm]
Работает.
Jenkins
Теперь пойдём дальше, и проверим — будет ли работать эта схема из Jenkins, т.к. во-первых — сам Jenkins запущен в Docker-контейнере, во-вторых — он запускает джобы, такие как Ansible-задачи, в Docker-контейнерах.
Обновляем запущенный инстанс:
Подключаем созданную ранее роль:
Создадим свой Docker образ с AWS CLI — пишем Dockerfile
:
FROM python:3.7-stretch RUN apt-get update -y RUN pip install awscli
Собираем образ:
[simterm]
root@jenkins-dev:/opt/jenkins# docker build -t setevoy/awscli .
[/simterm]
Проверяем:
[simterm]
root@jenkins-dev:/opt/jenkins# docker run -ti setevoy/awscli aws --version aws-cli/1.16.168 Python/3.7.3 Linux/4.9.0-8-amd64 botocore/1.12.158
[/simterm]
Переходим в Jenkins, создаём тестовую джобу.
Тут используем Jenkins Pipeline скрипт:
node { docker.image('setevoy/awscli:latest').inside('-v /var/run/docker.sock:/var/run/docker.sock') { stage('List zones') { sh "aws route53 list-hosted-zones --output text" } } }
И запускаем джобу:
А для проверки того, что подключенная роль работает как планировалось, а именно — ограничение доступа в ней — выполним запрос, который не разрешён политикой.
В политике разрешён доступ AmazonRoute53ReadOnlyAccess — попробуем получить список корзин вместо зон Route53:
node { docker.image('setevoy/awscli:latest').inside('-v /var/run/docker.sock:/var/run/docker.sock') { stage('List zones') { sh "aws s3api list-buckets" } } }
Запускаем:
+ aws s3api list-buckets
An error occurred (AccessDenied) when calling the ListBuckets operation: Access Denied
AccessDenied — отличненько.
Единственный нюанс в использовании ролей EC2 в качестве системы авторизации Jenkins-задач, это то, что по сути EC2 с Jenkins-ом получает полный доступ к инфрастуктуре AWS.
Т.е. если кто-то получит доступ к SSH на этой машине — он получит и доступ ко всем ресурсам аккаунта.
С одной стороны — это можно обойти, используя спот-инстансы AWS в качестве Jenkins-воркеров, к которым подключались бы ограниченные политики, а с другой стороны — если кто-то получил доступ к вашему Jenkins, то у вас и так уже полные штаны проблем.
Поэтому не забываем его обновлять, и ограничивать доступ на уровне AWS SecurityGroups и авторизации.
После первого взлома командой Matrix был опубликован отчёт, в котором указано, что взлом был совершён через уязвимость в необновлённой системе непрерывной интеграции Jenkins. После получения доступа к серверу с Jenkins атакующие перехватили ключи SSH и получили возможность доступа к другим серверам инфраструктуры.
https://www.opennet.ru/opennews/art.shtml?num=50501