Русский
Русский
English
Статистика
Реклама

Автоматизируем поиск секретов в git и ansible

Знаете ли вы что хранится в вашем git репозитории? Нет ли среди сотен коммитов паролей от продуктовых серверов, попавших туда по ошибке?

А что если ansible скрипт при публикации упадет и засветит пароли в логе?

Рассказываю о том как мы попробовали автоматизировать такие проверки и что из этого получилось.

Привет, Хабр!

Меня зовут Олег, я работаю в достаточно крупном для РФ банке, в подразделении "IT для IT".

Недавно к нам обратились друзьяиз внедрения (OPS)с просьбой помочь им автоматизировать процесс приемки изменений в скрипты деплоя (ansible) и конфигурации приложений напромышленных средах.

Исторически у нас есть 2 git хранилища (на самом деле больше, но это не так важно), в одном команды хранят свой код, в другом хранятся скрипты деплоя, конфигурации приложений итд, в общем все что относится к OPS составляющей.

Так вот, исходя из принятого процесса скрипты и конфигурации для промышленных сред хранятсяв masterветке и при вливании в нее нужен апрув от OPS.

Почему именно так? Потому что кроме всего там же в gitхранятся ипароли от сред (хоть и зашифрованные) и отвечают за них OPSы. Если какой то пароль засветится в логе Jenkins или попадет в открытом виде в git - это серьезная утечка и безопасность придет (с паяльником) именно к OPS.

Пароли в git? Серьезно?

Да, к сожалению серьезно. Конечно же для таких вещей нужно использовать системы secret management, такие какHashiCorp Vault,CyberArk Conjurитд.

Но процесс покупки и внедрения тут сложный и долгий, а работать нужно сейчас.

Число команд, проектов и деплоев растет постоянно и ребята уже просто не могли анализировать то количество pull request которое к ним приходило.

Значит, будем автоматизировать!

Что анализируем?

Совместно с коллегами выявили 3 основных вектора утечки пароля, которые их интересовали и встречались наиболее часто:

Пароль в открытом виде

---    dev_ssh_username1: "admin"    dev_ssh_password1: "admin123"

Тут все должно быть ясно - хранить пароли в открытом виде нельзя.

Передача параметров в shell или command таски ansible

- name: Deploy  hosts: all  tasks:    - name: Update      shell: "update.sh --user={{ update_user }} --password={{ update_password }}"

Тут опасность заключается в том, что если playbook будет вызван с -v то мы получим в логах наши секреты в открытом виде.

$ ansible-playbook deploy.yml -i env/DEV/hosts -vTASK [Update] ******************************************************************changed: [192.168.1.2] => {"changed": true, "cmd": "/home/dev/update.sh --user=secret_user --password=secret_password", "delta": "0:00:05.056532", "end": "2020-11-06 09:53:09.397355", "rc": 0, "start": "2020-11-06 09:53:04.340823", "stderr": "", "stderr_lines": [], "stdout": "Update SUCCESS", "stdout_lines": ["Update SUCCESS"]}

Или, если playbook упадет то в логе выведется строка с секретами (даже если не указывать -v)

$ ansible-playbook deploy.yml -i env/DEV/hostsTASK [Update] ******************************************************************fatal: [192.168.1.2]: FAILED! => {"changed": true, "cmd": "/home/dev/update.sh --user=secret_user --password=secret_password", "delta": "0:00:00.018710", "end": "2020-11-06 10:14:30.642419", "msg": "non-zero return code", "rc": 127, "start": "2020-11-06 10:14:30.623709", "stderr": "/bin/sh: /home/dev/update.sh: Нет такого файла или каталога", "stderr_lines": ["/bin/sh: /home/dev/update.sh: Нет такого файла или каталога"], "stdout": "", "stdout_lines": []}

Чтобы этого избежать можноиспользовать environment, например:

- name: Deploy  hosts: all  tasks:    - name: Update      shell: "/home/dev/update.sh $AUTH_DATA"      environment:        AUTH_DATA: "--user={{ update_user }} --password={{ update_password }}"

Тогда в вывод наши credentials не попадут. Еще можно указывать атрибут no_log.

Неосторожное использование withCredentials

У нас используется Jenkins, в котором для работы с credentials используется конструкцияwithCredentials. С помощью нее внутри pipeline можно получить credential, сохранить его в переменные и использовать, например для подключения к какой нибудь сторонней системе. Jenkins при этом будет маскировать в логе значение этих переменных.

Однако, если мы сделаем, например так:

node {  stage('Jenkins Credentials | Decrypt Secret File') {    withCredentials([file(credentialsId: 'credentials-id', variable: 'secretFile')]) {      sh 'cat $secretFile'    }  }}

То несмотря на то, что мы не увидим в логе путь к файлу, мы увидим его содержимое. Так же, можно сохранить переменнуюsecretFile в другую и спокойно вывести ее в лог вне конструкцииwithCredentials.


Перед тем как рассказывать дальше отмечу что все эти проверки направлены на поиск неосторожного, необдуманного использования инструментов. Я конечно же отдаю себе отчет что от злонамеренных действий это не спасет (для этого у банка есть другие структуры).

Ищем пароли

Казалось что поиск паролей в открытом виде это какая то плевая задача. У нас в арсенале есть не толькоSonarQubeно иCheckmarx. Уж они точно должны уметь решать эти задачи.

И вроде бы вот оно - есть такое правило для SonarQubeHard-coded credentials are security-sensitive

Но при внимательном рассмотрении оказывается что правило достаточно простое, оно проверяет попадает ли название переменной под определенный паттерн. Исходник, если интереснотут.

C Checkmarx оказалась так же картина - реагирует он очень выборочно. И правила там тоже очень простые (коллеги из Application Security рассказали). Например, такой код пропускает:

import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController; @RestControllerpublic class DemoController {    @RequestMapping("/show_me_creds")    public @ResponseBody String show_me_creds() {        String thisIsMyLittleSecret = "qwerty12345";        return thisIsMyLittleSecret;    }}

Изучив что есть на рынке пришли к выводу что будем смотреть в сторону opensource утилит. Запрос в google "find secrets in git" выдает нам примерно такие варианты:

Попробовав их все мы остановились на Gitleaks, вот почему:

  • Проводит анализ на основе regexp, которые можно расширять;

  • Умеет учитывать энтропию при анализе;

  • Репорты в json;

  • Легко задавать исключения;

  • В пилоте показал лучший результат.

Как он работает - в основе лежит toml файл с описанием правил, например:

[[rules]]  description = "generic secret regex"  regex = '''secret(.{0,20})([0-9a-zA-Z-._{}$\/\+=]{20,120})'''  tags = ["secret", "example"]

Кроме того в правиле можно указывать требуемую энтропию, например:

[[rules]]  description = "entropy and regex example"  regex = '''secret(.{0,20})([0-9a-zA-Z-._{}$\/\+=]{20,120})'''  [[rules.Entropies]]    Min = "4.5"    Max = "4.7"

В переводе на человеческий это означает: "Если мы встречаем строку кода, которая соответствует регулярному выражению, и эта строка попадает в пределыэнтропииот 4,5 до 4,7, то это пароль"

Я не буду заниматься переводом документации, все прекрасно описанотут.

Пример срабатывания:

  ~  gitleaks --repo=gitleaks --repo=https://github.com/gitleakstest/gronit.git --verbose --prettyINFO[2020-04-28T13:00:34-04:00] cloning... https://github.com/gitleakstest/gronit.gitEnumerating objects: 135, done.Total 135 (delta 0), reused 0 (delta 0), pack-reused 135{        "line": "const AWS_KEY = \"AKIALALEMEL33243OLIAE\"",        "offender": "AKIALALEMEL33243OLIA",        "commit": "eaeffdc65b4c73ccb67e75d96bd8743be2c85973",        "repo": "gronit.git",        "rule": "AWS Manager ID",        "commitMessage": "remove fake key",        "author": "Zachary Rice",        "email": "zricethezav@users.noreply.github.com",        "file": "main.go",        "date": "2018-02-04T19:43:28-06:00",        "tags": "key, AWS"}......WARN[2020-04-28T13:00:35-04:00] 6 leaks detected. 33 commits audited in 77 milliseconds 738 microseconds

Из вызова выше видно, что gitleaks умеет работать непосредственно с репозиторием - вы можете указать ему ссылку на репо и получить отчет не клонируя репозиторий на локаль.

Если произошло ложное срабатывание - достаточно занести строку в whitelist правила, например:

[[rules]]  description = "entropy and regex example"  regex = '''secret(.{0,20})['|"]([0-9a-zA-Z-._{}$\/\+=]{20,120})['|"]'''  [[rules.Entropies]]    Min = "4.5"    Max = "4.7"  [[rules.whitelist]]    regex = '''secret.some_value_that_match_regexp_but_not_really_a_secret'''    description = "ignore that string"

В результате мы пришли к такой схеме - при создании pull request через webhook вызывается Jenkins, который скачивает репозиторий, запускает для него gitleaks и если тот находит утечки - ставит статус NEED WORK.

Конечно мы немного затюнили правила под себя, так же мы поправили вывод, чтобы найденные gitleaks пароли не светились в логах (чтобы борясь с компрометацией мы не компрометировали пароли).

Проверяем ansible

Двигаемся дальше, итак, мы хотим находить вот такие playbook, потому что не хотим случайно засветить в логах Jenkins наши креды.

- name: Deploy  hosts: all  tasks:    - name: Update      shell: "update.sh --user={{ update_user }} --password={{ update_password }}"

С ansible все просто - его легко распарсить, можно использовать OPA (рекомендуюстатьюот Александра Токаревапо теме), можно самим быстро написать проверки в python.

Хорошо что мы этого не сделали, а вспомнили проAnsible Lint.

Это официальный линтер для Ansible, который имеет "из коробки" кучу полезных проверок, но нас интересует не это.

Нам были нужны вот эти 2 фичи:

  • Возможность писать свои правила;

  • Автоматическое сканирование и поиск playbook. Не нужно передавать конкретные файлы на проверку, можно запустить линтер в папке и он сам найдет их.

Писать правила очень просто - правило представляет собой python код, в котором обязательно должны быть объявлены переменные id, short description, description и tags (их использует Ansible Lint для ведения списка правил) и методы match или matchtask, в которых мы пишем саму логику проверки. Более подробно предлагаю почитать воригинальной документации.

Для нашей проверки мы используем matchtask, на вход которого поступает каждый найденный task, который мы можем анализировать.

К сожалению я не могу здесь привести код именно нашего таска, но он похож на этот пример (полностьютут):

class CommandsInsteadOfModulesRule(AnsibleLintRule):    id = '303'    shortdesc = 'Using command rather than module'    description = (        'Executing a command when there is an Ansible module '        'is generally a bad idea'    )    severity = 'HIGH'    tags = ['command-shell', 'resources', 'ANSIBLE0006']     _commands = ['command', 'shell']    _modules = {        'apt-get': 'apt-get',#сокращаю список, чтобы не постить простыню        'yum': 'yum',    }     def matchtask(self, file, task):        if task['action']['__ansible_module__'] not in self._commands:            return         first_cmd_arg = get_first_cmd_arg(task)        if not first_cmd_arg:            return         executable = os.path.basename(first_cmd_arg)        if executable in self._modules and \                boolean(task['action'].get('warn', True)):            message = '{0} used in place of {1} module'            return message.format(executable, self._modules[executable])

Логика примерно такая - если task является command или shell и первый аргумент является командой из списка_modules - рекомендуем заменить команду на модуль.

Нашу проверку мы организовали похожим образом - составили список интересующих нас аргументов (password/pass/login итд) и анализируем каждый аргумент тасков command и shell на попадание в список.

Кроме того мы учитываем атрибут no_log, при его наличии аргументы не анализируем. Если в будущем разработчик уберет no_log - новый код опять попадет на проверку и наше правило это увидит.

Итак, после проверки Gitleaks запускается Ansible Lint, который в режиме автоматического сканирования прогоняет наше правило. Другие правила мы отключили, т.к. не можем навязывать командам использование линтера (хотя и рекомендует всем).

Проверяем withCredentials

Напоминаю о проблеме, например вот такой код в Jenkinsfile выведет в лог содержимое секретного файла, что скорее всего будет утечкой (хотя и замаскирует путь к нему).

node {  stage('Jenkins Credentials | Decrypt Secret File') {    withCredentials([file(credentialsId: 'credentials-id', variable: 'secretFile')]) {      sh 'cat $secretFile'    }  }}

А вот тут нас ждал провал.

Jenkinsfile это по сути groovy, который можно разобрать наAbstract Syntax Treeи анализировать. Мы использовалиAstBuilderчтобы получить дерево, научились находитьwithCredentials, анализировать его параметры и находить их использование внутри withCredentials. Например, код выше мы научились анализировать и реагировать на него.

Однако в жизни все сложнее,например я могу записатьsecretFile в глобальную переменную и использовать где-то в другом stage, могу записатьsecretFile в какой то временный файл итп итд.

Когда код скрипта приблизился к 1000 строк стало понятно что его уже сейчас пора рефакторить, плюс логики получается так много что впору выделять под развитие отдельную команду, поэтому пока что эту проверку и ее развитиемы отложили.

Кстати, если у вас используется только декларативный pipeline то потенциально можно использоватьPipeline model definition plugin, в котором можно конвертнуть pipeline в удобный json и анализировать уже его. Нам это к сожалению не подошло - у нас вовсю используется скриптовый.

Что в результате?

Мы провели внутренний пилот, в который попало 1000 pull request. Порядка 3% были ложные срабатывания gitleaks, в некоторых случаях были найдены реальные секреты, опасные скрипты ansible.

В целом схема показала себя работоспособной, коллеги из OPS довольны и уже включают эти проверки на остальные репозитории.

В конце так же хочу поделится интересными проектами, которые использовать в этой работе не удалось:

Источник: habr.com
К списку статей
Опубликовано: 01.02.2021 00:22:31
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Информационная безопасность

Open source

Devops

Git

Jenkins

Ansible

Python

Secrets-management

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru