Ansible Vault в Jenkins-пайплайне: зашифрованные секреты в git без паранойи
Ansible Vault 2.0 позволяет хранить секреты прямо в репозитории. Интегрируем vault-password с Jenkins через переменную среды - разработчики не видят prod-credentials, сборка работает.
Ansible Vault в составе Ansible 2.0 позволяет хранить зашифрованные секреты прямо в репозитории git
В одном из проектов на сопровождении у нас сложилась классическая ситуация: Ansible-роли в git, Jenkinsfile в git, а файл с паролями БД и API-ключами - на сервере в каком-то /etc/ansible/secrets.yml, который никто не версионировал и который однажды потерялся при переезде на новый Jenkins-агент. Мы потратили сорок минут на восстановление. После этого сели и нормально разобрались с Ansible Vault.
Что такое Ansible Vault
Vault - это механизм шифрования файлов прямо внутри Ansible-репозитория. Появился он не в 2.0 - базовая функциональность была ещё в 1.x - в 2.0 он подтянулся вместе с остальными улучшениями выпуска. Нас интересует именно сценарий «зашифрованный файл с секретами живёт в git».
Смысл простой: берёшь файл group_vars/production/vault.yml с паролями и ключами, прогоняешь его через ansible-vault encrypt, и в репозитории оказывается что-то вроде:
$ANSIBLE_VAULT;1.1;AES256
38663465373262303665656566323961343634376262626165336634613665666130356234633462
...
Этот зашифрованный blob можно коммитить без опасений. Расшифровать его без vault-password невозможно - AES-256. Разработчики видят в git историю изменений файла, но не содержимое.
Откуда Ansible берёт пароль
Вот тут начинается практика. Запустить ansible-playbook с vault-файлом можно тремя способами:
--ask-vault-pass- интерактивный промпт, не подходит для CI вообще.--vault-password-file /path/to/file- читает пароль из файла. Вариант рабочий, но файл надо где-то хранить на агенте.- Переменная среды
ANSIBLE_VAULT_PASSWORD_FILE- указывает на тот же файл, но через env.
Мы пошли таким путём: vault-password хранится в Jenkins как Secret Text (Credentials), Jenkins при запуске сборки записывает его во временный файл, ANSIBLE_VAULT_PASSWORD_FILE указывает на этот файл, после сборки файл удаляется. Никакого хранения на диске агента между сборками.
Как это выглядит в Jenkinsfile
node {
withCredentials([string(credentialsId: 'ansible-vault-password', variable: 'VAULT_PASSWORD')]) {
stage('Deploy') {
sh '''
echo "$VAULT_PASSWORD" > /tmp/vault_pass_$BUILD_NUMBER
chmod 600 /tmp/vault_pass_$BUILD_NUMBER
export ANSIBLE_VAULT_PASSWORD_FILE=/tmp/vault_pass_$BUILD_NUMBER
ansible-playbook -i inventory/production deploy.yml
rm -f /tmp/vault_pass_$BUILD_NUMBER
'''
}
}
}
Jenkins маскирует содержимое VAULT_PASSWORD в логах сборки - в выводе появляется ****. Файл создаётся с правами 600. После деплоя удаляется.
Один момент, на который потратили время: если ansible-playbook упадёт с ошибкой до строчки с rm -f, файл останется на диске. Решили через trap:
sh '''
VAULT_FILE=/tmp/vault_pass_$BUILD_NUMBER
echo "$VAULT_PASSWORD" > "$VAULT_FILE"
chmod 600 "$VAULT_FILE"
trap "rm -f $VAULT_FILE" EXIT
ANSIBLE_VAULT_PASSWORD_FILE="$VAULT_FILE" ansible-playbook -i inventory/production deploy.yml
'''
trap ... EXIT сработает при любом выходе из shell, включая ошибку.
Структура репозитория
Секреты живут отдельным файлом рядом с обычными переменными:
group_vars/
production/
vars.yml # обычные переменные, открытый текст
vault.yml # зашифрованный vault-файл
В vars.yml можно даже сослаться на vault-переменные по имени для удобства:
# vars.yml
db_password: "{{ vault_db_password }}"
api_key: "{{ vault_api_key }}"
А в vault.yml уже реальные значения. Редактировать vault-файл можно командой ansible-vault edit vault.yml - откроет в $EDITOR, сохранит обратно зашифрованным.
Что получилось на практике
Разработчики работают с репозиторием как обычно. Vault-файл они видят - это зашифрованный blob, без пароля от него ноль пользы. Production-credentials знают только те, кому передан vault-password через личный канал. Jenkins знает его через Credentials Store.
Побочный эффект: git-история теперь показывает, что секреты менялись (и когда), но не что именно. Это лучше, чем было - хоть какой-то аудит.
Пара вещей, которые немного раздражают. Ротация vault-password - это ansible-vault rekey vault.yml, а потом нужно обновить секрет в Jenkins и у всех, кто имеет доступ. Не страшно, но вручную. И если в команде несколько человек редактируют vault-файл, нужно договариваться о единственном vault-password - несколько паролей на один файл vault не поддерживает.
В целом - работает, надёжно, и то, что мы потеряли сорок минут на восстановление старой схемы, больше не повторится.