[Информационная безопасность, Open source, DevOps] Автоматизируем поиск секретов в git и ansible
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Знаете ли вы что хранится в вашем 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 -v
TASK [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/hosts
TASK [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. Уж они точно должны уметь решать эти задачи.И вроде бы вот оно - есть такое правило для SonarQube Hard-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;
@RestController
public class DemoController {
@RequestMapping("/show_me_creds")
public @ResponseBody String show_me_creds() {
String thisIsMyLittleSecret = "qwerty12345";
return thisIsMyLittleSecret;
}
}
Изучив что есть на рынке пришли к выводу что будем смотреть в сторону opensource утилит. Запрос в google "find secrets in git" выдает нам примерно такие варианты:
- https://github.com/awslabs/git-secrets/blob/master/README.rst
- https://github.com/dxa4481/truffleHog/blob/dev/README.md
- https://github.com/Yelp/detect-secrets
- https://github.com/zricethezav/gitleaks
Попробовав их все мы остановились на 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 --pretty
INFO[2020-04-28T13:00:34-04:00] cloning... https://github.com/gitleakstest/gronit.git
Enumerating 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 довольны и уже включают эти проверки на остальные репозитории.В конце так же хочу поделится интересными проектами, которые использовать в этой работе не удалось:
- https://nightfall.ai/resources/introducing-radar-api-detect-credentials-secrets-in-code-via-machine-learning/ - обещают интеллектуальный поиск паролей в исходниках. Кто-нибудь пробовал?
- https://www.shellcheck.net/ - поможет найти кучу проблем в shell скриптах.
- https://github.com/PyCQA/bandit - отличный security linter для python.
- https://twitter.com/leak_scavenger - просто интересный бот в твиттер, который сканит github, pastebin, ghostbin на предмет секретов, кредиток, приватных ключей итд. Исходники, конечно, доступны.
===========
Источник:
habr.com
===========
Похожие новости:
- [Open source, *nix] FOSS News №54 – дайджест материалов о свободном и открытом ПО за 25-31 января 2021 года
- [Python] Уроки компьютерного зрения на Python + OpenCV с самых азов
- [Информационная безопасность, Мессенджеры, Законодательство в IT] ProtonMail, Threema, Tresorit и Tutanota просят Евросоюз пересмотреть усилия по запрету шифрования
- [Open source, Алгоритмы, Параллельное программирование] Динамика потокового вычислителя
- [Информационная безопасность, Криптография, Разработка под Android, Софт] Google по ошибке удалила мессенджер Element из каталога Google Play, затем вернула обратно
- Выпуск Python-библиотеки для научных вычислений NumPy 1.20.0
- [Информационная безопасность, Системное администрирование] Обнаружена 0day уязвимость повышения привилегий в Windows 7-10
- [Информационная безопасность, CTF] HackTheBox. Прохождение Worker. Работаем с SVN. Используем Azure DevOps для захвата хоста
- [Интерфейсы, ERP-системы, Управление разработкой, Управление проектами, Будущее здесь] Цифровая трансформация завода (ч. 3): волшебные интерфейсы и оживление железа
- [Занимательные задачки, Python, Программирование, Математика] L-системы и что они себе позволяют
Теги для поиска: #_informatsionnaja_bezopasnost (Информационная безопасность), #_open_source, #_devops, #_git, #_jenkins, #_ansible, #_python, #_secretsmanagement, #_informatsionnaja_bezopasnost (
Информационная безопасность
), #_open_source, #_devops
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 25-Ноя 06:02
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Знаете ли вы что хранится в вашем git репозитории? Нет ли среди сотен коммитов паролей от продуктовых серверов, попавших туда по ошибке?А что если ansible скрипт при публикации упадет и засветит пароли в логе?Рассказываю о том как мы попробовали автоматизировать такие проверки и что из этого получилось.Привет, Хабр!Меня зовут Олег, я работаю в достаточно крупном для РФ банке, в подразделении "IT для IT".Недавно к нам обратились друзья из внедрения (OPS) с просьбой помочь им автоматизировать процесс приемки изменений в скрипты деплоя (ansible) и конфигурации приложений на промышленных средах. Исторически у нас есть 2 git хранилища (на самом деле больше, но это не так важно), в одном команды хранят свой код, в другом хранятся скрипты деплоя, конфигурации приложений итд, в общем все что относится к OPS составляющей.Так вот, исходя из принятого процесса скрипты и конфигурации для промышленных сред хранятся в master ветке и при вливании в нее нужен апрув от OPS. Почему именно так? Потому что кроме всего там же в git хранятся и пароли от сред (хоть и зашифрованные) и отвечают за них OPSы. Если какой то пароль засветится в логе Jenkins или попадет в открытом виде в git - это серьезная утечка и безопасность придет (с паяльником) именно к OPS.Пароли в git? Серьезно? Да, к сожалению серьезно. Конечно же для таких вещей нужно использовать системы secret management, такие как HashiCorp Vault, CyberArk Conjur итд.Но процесс покупки и внедрения тут сложный и долгий, а работать нужно сейчас.
---
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 }}" $ ansible-playbook deploy.yml -i env/DEV/hosts -v
TASK [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"]} $ ansible-playbook deploy.yml -i env/DEV/hosts
TASK [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": []} - name: Deploy
hosts: all tasks: - name: Update shell: "/home/dev/update.sh $AUTH_DATA" environment: AUTH_DATA: "--user={{ update_user }} --password={{ update_password }}" node {
stage('Jenkins Credentials | Decrypt Secret File') { withCredentials([file(credentialsId: 'credentials-id', variable: 'secretFile')]) { sh 'cat $secretFile' } } } Перед тем как рассказывать дальше отмечу что все эти проверки направлены на поиск неосторожного, необдуманного использования инструментов. Я конечно же отдаю себе отчет что от злонамеренных действий это не спасет (для этого у банка есть другие структуры). Ищем паролиКазалось что поиск паролей в открытом виде это какая то плевая задача. У нас в арсенале есть не только SonarQube но и Checkmarx. Уж они точно должны уметь решать эти задачи.И вроде бы вот оно - есть такое правило для SonarQube Hard-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; @RestController public class DemoController { @RequestMapping("/show_me_creds") public @ResponseBody String show_me_creds() { String thisIsMyLittleSecret = "qwerty12345"; return thisIsMyLittleSecret; } }
[[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" ➜ ~ gitleaks --repo=gitleaks --repo=https://github.com/gitleakstest/gronit.git --verbose --pretty
INFO[2020-04-28T13:00:34-04:00] cloning... https://github.com/gitleakstest/gronit.git Enumerating 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 [[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" Проверяем ansibleДвигаемся дальше, итак, мы хотим находить вот такие playbook, потому что не хотим случайно засветить в логах Jenkins наши креды. - name: Deploy
hosts: all tasks: - name: Update shell: "update.sh --user={{ update_user }} --password={{ update_password }}"
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]) node {
stage('Jenkins Credentials | Decrypt Secret File') { withCredentials([file(credentialsId: 'credentials-id', variable: 'secretFile')]) { sh 'cat $secretFile' } } }
=========== Источник: habr.com =========== Похожие новости:
Информационная безопасность ), #_open_source, #_devops |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 25-Ноя 06:02
Часовой пояс: UTC + 5