AWS: ротация ключей IAM пользователей, EC2 IAM Roles и Jenkins

Автор: | 05/30/2019
 

Сегодня просматривал 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, если не найдены другие способы авторизации:

  1. Passing credentials as parameters in the boto.client() method
  2. Passing credentials as parameters when creating a Session object
  3. Environment variables
  4. Shared credential file (~/.aws/credentials)
  5. AWS config file (~/.aws/config)
  6. Assume Role provider
  7. Boto2 config file (/etc/boto.cfg and ~/.boto)
  8. Instance metadata service on an Amazon EC2 instance that has an IAM role configured.

Собственно, что делаем:

  1. создаём IAM роль с нужными политиками
  2. подключаем роль к EC2
  3. пользуемся AWS CLI (для примера) с этого EC2 без небходимости создавать ACCESS/SECRET ключи

Поехали.

Создание IAM-роли

Создаём роль, выбираем тип EC2:

Подключаем политику, тут для примера AmazonRoute53ReadOnlyAccess:

Сохраняем новую роль:

Запуск EC2

Запускаем EC2, и указываем ему IAM-роль:

Проверка

Логинимся на инстанс:

ssh admin@34.244.125.163 -i setevoy-testing-eu-west-1.pem

Проверяем IAM в мета-данных инстанса:

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"
...

Устанавливаем AWS CLI:

root@ip-172-31-42-77:/home/admin# apt update && apt -y install awscli

И получаем список зон без регистрации и СМС какой-либо настройки CLI:

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
...

Работает.

Jenkins

Теперь пойдём дальше, и проверим — будет ли работать эта схема из Jenkins, т.к. во-первых — сам Jenkins запущен в Docker-контейнере, во-вторых — он запускает джобы, такие как Ansible-задачи, в Docker-контейнерах.

Обновляем запущенный инстанс:

Подключаем созданную ранее роль:

Создадим свой Docker образ с AWS CLI — пишем Dockerfile:

FROM python:3.7-stretch
RUN apt-get update -y
RUN pip install awscli

Собираем образ:

root@jenkins-dev:/opt/jenkins# docker build -t setevoy/awscli .

Проверяем:

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

Переходим в 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