
AWS Least Privilege для распределенного высокоскоростного развертывания
Repokid ======= []() [](https://travis-ci.com/Netflix/repokid) [](https://badge.fury.io/py/repokid) [](https://coveralls.io/github/Netflix/repokid?branch=master) [](https://discord.gg/9kwMWa6) <img align="center" alt="Repokid Logo" src="https://raw.githubusercontent.com/Netflix/repokid/master/docs/images/Repokid.png?raw=true" width="25%" display="block"> Repokid использует Access Advisor, предоставляемый [Aardvark](https://github.com/Netflix-Skunkworks/aardvark), для удаления разрешений, предоставляющих доступ к неиспользуемым сервисам, из inline-политик IAM-ролей в учётной записи AWS. ## Начало работы ### Установка ```bash mkvirtualenv repokid git clone [email protected]:Netflix/repokid.git cd repokid pip install -e . repokid config config.json ``` #### DynamoDB Вам понадобится таблица [DynamoDB](https://aws.amazon.com/dynamodb/) с именем `repokid_roles` (укажите учётную запись и endpoint в разделе `dynamo_db` файла конфигурации). Таблица должна иметь следующие свойства: - `RoleId` (строка) в качестве первичного ключа раздела, без первичного ключа сортировки. - Глобальный вторичный индекс с именем `Account`, где первичный ключ раздела — `Account`, а проецируемые атрибуты — `RoleId` и `Account`. - Глобальный вторичный индекс с именем `RoleName`, где первичный ключ раздела — `RoleName`, а проецируемые атрибуты — `RoleId` и `RoleName`. Для разработки вы можете запустить DynamoDB [локально](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.html). Для локального запуска: ```bash docker-compose up ``` Endpoint для DynamoDB будет `http://localhost:8000`. Панель администрирования DynamoDB доступна по адресу `http://localhost:8001`. Если вы используете версию для разработки, таблица и индексы будут созданы автоматически. #### Разрешения IAM Repokid требует IAM-роль в каждой учётной записи, которая будет опрашиваться. Кроме того, Repokid должен запускаться с ролью или пользователем, который может выполнять `sts:AssumeRole` для ролей разных учётных записей. RepokidInstanceProfile: - Создаётся только один. - Должна иметь возможность вызывать `sts:AssumeRole` для всех RepokidRole. - Разрешения DynamoDB для таблицы `repokid_roles` и всех индексов (указываются в подразделе `assume_role` раздела `dynamo_db` конфигурации) и возможность выполнять `dynamodb:ListTables`. RepokidRole: - Должна существовать в каждой учётной записи, управляемой repokid. - Должна иметь политику доверия, разрешающую `RepokidInstanceProfile`. - Имя должно быть указано в `connection_iam` файла конфигурации. - Имеет следующие разрешения: ```json { "Version": "2012-10-17", "Statement": [ { "Action": [ "iam:DeleteInstanceProfile", "iam:DeleteRole", "iam:DeleteRolePolicy", "iam:GetAccountAuthorizationDetails", "iam:GetInstanceProfile", "iam:GetRole", "iam:GetRolePolicy", "iam:ListInstanceProfiles", "iam:ListInstanceProfilesForRole", "iam:ListRolePolicies", "iam:PutRolePolicy", "iam:UpdateRoleDescription" ], "Effect": "Allow", "Resource": "*" } ] } ``` Таким образом, если вы отслеживаете `n` учётных записей, вам всегда понадобится `n+1` ролей (`n` RepokidRole и `1` RepokidInstanceProfile). #### Редактирование config.json Запуск `repokid config config.json` создаёт файл, который необходимо отредактировать. Найдите и обновите следующие поля: - `dynamodb`: При использовании локального DynamoDB укажите endpoint `http://localhost:8010`. При использовании DynamoDB, размещённого в AWS, укажите `region`, `assume_role` и `account_number`. - `aardvark_api_location`: Адрес вашего REST API Aardvark. Например, `https://aardvark.yourcompany.net/api/1/advisors`. - `connection_iam`: Установите `assume_role` в `RepokidRole` (или любое другое имя, которое вы выбрали). ## Дополнительная конфигурация Repokid использует фильтры для определения того, какие роли являются кандидатами на репо. Фильтры могут быть настроены под вашу среду, как описано ниже. ### Фильтр блокировки (Blocklist Filter) Роли могут быть исключены путём добавления в фильтр блокировки. Одна из распространённых причин исключения роли — если соответствующая рабочая нагрузка изредка выполняет действия, которые не были замечены, но, как известно, являются обязательными. Есть два способа исключить роль: - Исключить имя роли для всех учётных записей: добавить его в список в конфигурации `filter_config.BlocklistFilter.all`. - Исключить имя роли для конкретной учётной записи: добавить его в список в конфигурации `filter_config.BlocklistFilter.<ACCOUNT_NUMBER>`. Чёрные списки также можно поддерживать в S3-файле блокировки. Они должны быть в следующем формате: ```json { "arns": ["arn1", "arn2"], "names": {"role_name_1": ["all", "account_number_1"], "role_name_2": ["account_number_2", "account_number_3"]} } ``` ### Фильтр исключительности (Exclusive Filter) Если вы предпочитаете репозировать только определённые роли, можете использовать фильтр исключительности. Например, вы хотите учитывать только роли, используемые в production или определёнными командами. Чтобы выбрать роли для репо, перечислите их имена в файлах конфигурации. Также поддерживаются шаблоны в стиле glob. Выбор ролей может быть задан для каждой учётной записи отдельно или глобально. Чтобы активировать этот фильтр, добавьте `"repokid.filters.exclusive:ExclusiveFilter"` в раздел `active_filters` файла конфигурации. Для настройки вы можете начать с автоматически сгенерированного файла конфигурации, в котором есть пример конфигурации в разделе `"filter_config"`: ``` "ExclusiveFilter": { "all": [ "<GLOB_PATTERN>" ], "<ACCOUNT_NUMBER>": [ "<GLOB_PATTERN>" ] } ``` ### Фильтр возраста (Age Filter) По умолчанию фильтр возраста исключает роли, возраст которых меньше 90 дней. Чтобы изменить это, отредактируйте настройку конфигурации: `filter_config.AgeFilter.minimum_age`. ### Активные фильтры Могут быть созданы новые фильтры для поддержки внутренней логики. В Netflix у нас есть несколько, специфичных для наших случаев использования. Чтобы сделать их активными, убедитесь, что они находятся в пути Python, и добавьте их в конфигурацию в список в разделе `active_filters`. ## Расширение Repokid ### Хуки (Hooks) Repokid расширяется с помощью хуков, которые вызываются до, во время и после различных операций, как указано ниже. | Имя хука | Контекст | |-----------|---------| | `AFTER_REPO` | role, errors | | `AFTER_REPO_ROLES` | roles, errors | | `BEFORE_REPO_ROLES` | account_number, roles | | `AFTER_SCHEDULE_REPO` | roles | | `DURING_REPOABLE_CALCULATION` | role_id, arn, account_number, role_name, potentially_repoable_permissions, minimum_age | | `DURING_REPOABLE_CALCULATION_BATCH` | role_batch, potentially_repoable_permissions, minimum_age | Хуки должны соответствовать следующему интерфейсу: ```python from repokid.hooks import implements_hook from repokid.types import RepokidHookInput, RepokidHookOutput @implements_hook("TARGET_HOOK_NAME", 1) def custom_hook(input_dict: RepokidHookInput) -> RepokidHookOutput: """Функции хуков вызываются со словарём, содержащим ключи, перечисленные выше, в зависимости от целевого хука. Любые изменения, внесённые во входные данные и возвращённые в выходных, будут переданы последующим функциям хуков. """ ... ``` Примеры реализаций хуков можно найти в [`repokid.hooks.loggers`](https://github.com/netflix/repokid/blob/master/repokid/hooks/loggers/__init__.py). ### Фильтры Можно написать собственные фильтры, чтобы исключить роли из репо. Фильтры должны соответствовать следующему интерфейсу: ```python from repokid.filters import Filter from repokid.types import RepokidFilterConfig from repokid.role import RoleList class CustomFilterName(Filter): def __init__(self, config: RepokidFilterConfig = None) -> None: """Фильтры инициализируются словарём, содержащим содержимое `filter_config.FilterName` из файла конфигурации. В этом примере инициализация будет с `filter_config.CustomFilterName`. Конфигурация доступна через `self.config`. Если вам не нужна какая-либо пользовательская логика инициализации, вы можете опустить эту функцию в своём классе фильтра. """ super().__init__(config=config) # пользовательская логика инициализации здесь ... def apply(self, input_list: RoleList) -> RoleList: """Определить роли, которые нужно исключить, и вернуть их как RoleList""" ... ``` Простая реализация фильтра находится в [`repokid.filters.age`](https://github.com/netflix/repokid/blob/master/repokid/filters/age/__init__.py). Более сложный пример — в [`repokid.blocklist.age`](https://github.com/netflix/repokid/blob/master/repokid/filters/blocklist/__init__.py). ## Как использовать После настройки Repokid используйте его следующим образом: ### Стандартный рабочий процесс - Обновить кэш ролей: `repokid update_role_cache <ACCOUNT_NUMBER>` - Отобразить кэш ролей: `repokid display_role_cache <ACCOUNT_NUMBER>` - Показать информацию о конкретной роли: `repokid display_role <ACCOUNT_NUMBER> <ROLE_NAME>` - Сделать репо для конкретной роли: `repokid repo_role <ACCOUNT_NUMBER> <ROLE_NAME>` - Сделать репо для всех ролей в учётной записи: `repokid repo_all_roles <ACCOUNT_NUMBER> -c` ### Планирование Вместо немедленного выполнения репо вы можете запланировать его (команда `schedule_repo`). Время между планированием и возможностью выполнения настраивается, но по умолчанию роли могут быть репозированы через 7 дней после планирования. Затем вы можете выполнить команду `repo_scheduled_roles`, чтобы репозировать только те роли, которые уже были запланированы. ### Нацеливание на определённое разрешение Допустим, вы считаете определённое разрешение особенно опасным в вашей среде. Здесь я использую `s3:PutObjectACL` в качестве примера. Вы можете использовать Repokid, чтобы найти все роли, имеющие это разрешение (даже скрытые в wildcard), и затем удалить только это одно разрешение. Поиск и удаление: - Перед началом убедитесь, что кэш ролей обновлён. - Найти роли с заданным разрешением: `repokid find_roles_with_permissions <permission>... [--output=ROLE_FILE]` - Удалить разрешение из ролей: `repokid remove_permissions_from_roles --role-file=ROLE_FILE <permission>... [-c]` Пример: ``` $ repokid find_roles_with_permissions "s3:putobjectacl" "sts:assumerole" --output=myroles.json ... $ repokid remove_permissions_from_roles --role-file=myroles.json "s3:putobjectacl" "sts:assumerole" -c ``` ### Откат Repokid сохраняет копию каждой версии inline-политик, о которых ему известно. Они добавляются, когда во время `update_role_cache` обнаруживается другая версия политики, а также при каждом действии репо. Чтобы восстановить предыдущую версию, выполните: Просмотреть все версии ролей: `repokid rollback_role <ACCOUNT_NUMBER> <ROLE_NAME>` Восстановить конкретную версию: `repokid rollback_role <ACCOUNT_NUMBER> <ROLE_NAME> --selection=<NUMBER> -c` ### Статистика Repokid ведёт подсчёт общего количества разрешений для каждой роли. Статистика добавляется при каждом выполнении `update_role_cache` или `repo_role`. Чтобы вывести всю статистику в CSV-файл, выполните: `repokid repo_stats <OUTPUT_FILENAME>`. Можно указать номер учётной записи, чтобы вывести статистику только для неё. ### Библиотека > Новое в `v0.14.2` Repokid можно вызывать как библиотеку с помощью модуля `repokid.lib`: ```python from repokid.lib import display_role, repo_role, update_role_cache account_number = "123456789012" display_role(account_number, "superCoolRoleName") update_role_cache(account_number) repo_role(account_number, "superCoolRoleName", commit=True) ``` ## Dispatcher ## Repokid Dispatcher предназначен для прослушивания сообщений в очереди и выполнения действий. На данный момент доступны следующие действия: - Получить список сервисов, доступных для репо из роли - Установить или удалить opt-out - Просмотреть и выполнить откат для роли Repokid будет отвечать через настраиваемый SNS-топик с информацией об успехах или ошибках. Компонент Dispatcher помогает организовать жизненный цикл репо в вашей организации. Вы можете предоставить очередь напрямую разработчикам, но, скорее всего, её следует защитить, поскольку откат может быть опасным действием, если выполнять его без осторожности. ## Разработка ### Релизинг Версионирование осуществляется с помощью [setupmeta](https://github.com/zsimic/setupmeta). Чтобы создать новый релиз: ```bash python setup.py version --bump patch --push # Проверьте вывод и убедитесь, что он соответствует ожиданиям # Если всё в порядке, сделайте коммит и отправьте новый тег: python setup.py version --bump patch --push --commit ```