В продолжение поста Helm: пошаговое создание чарта и деплоймента из Jenkins – теперь нам надо добавить шифрование данных, что бы не хранить пароли и различные приватные ключи в открытом виде в Github-репозиториях – даже пусть и приватных.
Судя по гуглу, чуть ли не единственный вариант – это использование helm-secrets (а подскажите в комментариях – кто чем ещё пользуется?).
Из плюшек – возможность работать с ключами из AWS KMS, и автоматом подставлять данные в шаблоны.
Суть простая: берёт GPG/PGP/KMS ключ, им шифруется файл. Всё 🙂
Под капотом использует Mozilla SOPS (SOPS – Secrets OPerationS).
Что выполним – установим плагин, создадим AWS KMS ключ, создадим файл с паролем, обновим уже задеплоенный хельм-релиз.
В конце – добавим работу с плагином Jenkins-джобу из предыдущего поста.
Содержание
helm-secrets
Переходим в репозиторий, в каталог с файлами чарта:
[simterm]
$ cd k8s/bttrm-apps-backend/ $ ll total 24 -rw-r--r-- 1 setevoy setevoy 1109 May 14 08:21 Chart.yaml drwxr-xr-x 2 setevoy setevoy 4096 May 14 08:21 charts drwxr-xr-x 2 setevoy setevoy 4096 May 14 09:27 secrets drwxr-xr-x 2 setevoy setevoy 4096 May 15 15:37 templates -rw-r--r-- 1 setevoy setevoy 1225 May 15 10:01 values.yaml
[/simterm]
Установка
helm-secrets & sops на Arch Linux
Вообще установка должна выполняться через helm plugin
– но на Arch Linux не сработало.
Сначала ругнулось на права:
[simterm]
$ helm plugin install https://github.com/futuresimple/helm-secrets which: no dpkg in (/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/home/setevoy/go/bin) mv: cannot create regular file '/usr/local/bin/sops': Permission denied Error: plugin install hook for "secrets" exited with error
[/simterm]
Потом ещё раз на права – да и устанвавливать от sudo
в /root
– бред:
[simterm]
$ sudo helm plugin install https://github.com/futuresimple/helm-secrets which: no dpkg in (/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/home/setevoy/go/bin) /root/.local/share/helm/plugins/helm-secrets/install-binary.sh: line 62: /tmp/sops: Permission denied Error: plugin install hook for "secrets" exited with error
[/simterm]
Потому – ставим сам sops
из AUR:
[simterm]
$ yay -S sops
[/simterm]
И читаем документацию по helm plugin – куда он вообще устанвливает файлы плагинов?
$XDG_DATA_HOME/helm/plugins
– ага.
$XDG_DATA_HOME
должен вести на $HOME/.local/share
– проверяем:
[simterm]
$ ll $HOME/.local/share/helm/plugins/ total 4 lrwxrwxrwx 1 setevoy setevoy 76 May 15 08:30 helm-secrets -> /home/setevoy/.cache/helm/plugins/https-github.com-futuresimple-helm-secrets
[/simterm]
Смотрим содержимое:
[simterm]
$ ls -l /home/setevoy/.cache/helm/plugins/https-github.com-futuresimple-helm-secrets/ total 68 -rw-r--r-- 1 setevoy setevoy 11337 May 15 08:30 LICENSE -rw-r--r-- 1 setevoy setevoy 20057 May 15 08:30 README.md drwxr-xr-x 4 setevoy setevoy 4096 May 15 08:30 example -rwxr-xr-x 1 setevoy setevoy 2346 May 15 08:30 install-binary.sh -rw-r--r-- 1 setevoy setevoy 338 May 15 08:30 plugin.yaml -rwxr-xr-x 1 setevoy setevoy 12388 May 15 08:30 secrets.sh -rwxr-xr-x 1 setevoy setevoy 4621 May 15 08:30 test.sh
[/simterm]
В общем-то вся работа плагина построена на bash-скриптах. Прелестно.
Сами скрипты описаны в документации Moving parts of project.
Ладно.
Что плагин вообще выполняет, какие действия?
Читаем файл plugin.yaml
:
[simterm]
$ cat /home/setevoy/.cache/helm/plugins/https-github.com-futuresimple-helm-secrets/plugin.yaml name: "secrets" version: "2.0.2" usage: "Secrets encryption in Helm for Git storing" description: |- This plugin provides secrets values encryption for Helm charts secure storing command: "$HELM_PLUGIN_DIR/secrets.sh" useTunnel: true hooks: install: "$HELM_PLUGIN_DIR/install-binary.sh" update: "$HELM_PLUGIN_DIR/install-binary.sh"
[/simterm]
command: "$HELM_PLUGIN_DIR/secrets.sh"
– ага…
Посмотрим скрипт secrets.sh
:
[simterm]
$ cat /home/setevoy/.cache/helm/plugins/https-github.com-futuresimple-helm-secrets/secrets.sh | wc -l 534
[/simterm]
21-й век – мы полагаемся на километровый баш-скрипт для работы с важными данными.
Окей… Прелестно.
Настройка шифрования
За настройку ключей используемых для encrypt/decrypt действий отвечает файл .sops.yaml
в каталоге чарта.
Примеры настроек – Using .sops.yaml conf to select KMS/PGP for new files.
Переходим к созданию ключа, которым будем шифровать наши данные.
AWS KMS
Документация – https://aws.amazon.com/ru/kms/getting-started.
Создаём KMS ключ, тип – дефолтный, симетричный (см. Using symmetric and asymmetric keys):
Задаём его администратора:
Далее надо добавить пользователей, у которых будет доступ к ключу.
Так как мы будем использовать файл из Jenkins, который работает на EC2 и доступ в AWS получает через свой EC2 Instance Profile – то надо добавить роль, которая используется им для аутентификации и авторизации в нашем AWS-аккаунте.
Находим профиль:
Находим, и подключаем её:
Подтверждаем сгенерированную политику:
Кстати:
... "Principal": { "AWS": "arn:aws:iam::534***385:root" }, "Action": "kms:*", "Resource": "*" ...
ARN arn:aws:iam::534***385:root указывает на всех пользователей AWS-аккаунта, насколько я помню из поста AWS Elastic Kubernetes Service: RBAC-авторизация через AWS IAM и RBAC группы.
И таки да – у коллеги доступ к ключу есть, хотя явно user-прав ему не назначал. Окей – имеем это ввиду.
Ключ готов:
.sops.yaml
config
В корне каталога с файлами чарта создаём файл .sops.yaml
– это файл настроек для SOPS, который он будет использовать для работы с нашими секретами.
В примерах есть варианты использования различных ключей для различных файлов секретов – можно использовать различные ключи для различных файлов.
Пока используем один дефолтный для всех – создаём правило:
--- creation_rules: - kms: 'arn:aws:kms:eu-west-1:534***385:key/620b89fe-***-25b435611e8b'
Файл секретов
В каталоге чарта bttrm-apps-backend создаём файл для паролей – secrets.yaml
.
Тут есть подводный камень, на который я напоролся – по-умолчанию helm-secrets
будет искать файл по маске secrets.*.yaml
– см. Usage and examples:
By convention, files containing secrets are named secrets.yaml, or anything beginning with “secrets.” and ending with “.yaml”. E.g. secrets.test.yaml and secrets.prod.yaml.
Т.е. назвать файл passwords.yaml
– не вариант, по крайней мере без подолнительных телодвижений.
Итак, сейчас в файле values.yaml
имеются пароли открытым текстом:
... image: registry: "docker.io" username: "bttrm" password: "pass" repository: "bttrm" name: "bttrm-apps" tag: "120" ...
Вырезаем из values.yaml
строку image.password
– переносим в файл secrets.yaml
:
image: password: "pass"
Или, что бы сделать тут пример более простым и наглядным – добавляем в secrets.yaml
новое значение, например:
test: secret: testecret
И создаём манифест Kubernetes Secret, в моём случае они создаётся из шаблона bttrm-apps-backend/templates/bttrm-apps-secrets.yaml
, где bttrm-apps-backend – каталог чарта:
--- apiVersion: v1 kind: Secret metadata: name: test-secret type: Opaque stringData: example-secret: {{ .Values.test.secret }}
В stringData
обращаемся к .Values
, как обычно.
helm secrets
– AccessDeniedException
Шифруем файл секретов:
[simterm]
$ helm secrets enc secrets.yaml Encrypting secrets.yaml Could not generate data key: [failed to encrypt new data key with master key "arn:aws:kms:eu-west-1:534***385:key/620b89fe-8365-45a6-aad6-25b435611e8b": Failed to call KMS encryption service: AccessDeniedException: status code: 400, request id: 699332aa-492b-47b4-b9ab-50f83dbcc2a4] Error: plugin "secrets" exited with error
[/simterm]
А, окей.
Проблема, собственно, ясна сразу – SOPS пытается обратиться к AWS, используя default профиль из ~/.aws/credentials
, в котором у меня записан мой личный аккаунт AWS, а не рабочий.
Можно добавить aws_profile
в .sops.yaml
:
--- creation_rules: - kms: 'arn:aws:kms:eu-west-1:534***385:key/620b89fe-***-25b435611e8b' aws_profile: 'arseniy'
Но тогда этот же профиль будет использоваться при деплое чарта из Jenkins.
Поэтому – задаём профиль переменной окружения:
[simterm]
$ AWS_PROFILE=arseniy
[/simterm]
И ещё раз пробуем зашифровать данные в чарте bttrm-apps-backend:
[simterm]
$ helm secrets enc bttrm-apps-backend/secrets.yaml Encrypting bttrm-apps-backend/secrets.yaml Already encrypted: bttrm-apps-backend/secrets.yaml
[/simterm]
Проверяем содержимое secrets.yaml
теперь:
image: password: ENC[AES256_GCM,data:1t7 .. PyY,iv:9mzTyB ... 9KG7+Hg=,tag:6KJ ... gvA==,type:str] test: secret: ENC[AES256_GCM,data:l4c ... vCy,iv:9riw ... 0fvQ=,tag:G3p ... oSw==,type:str] sops: kms: - arn: arn:aws:kms:eu-west-1:534***385:key/620b89fe- ... -25b435611e8b created_at: '2020-05-15T06:33:44Z' enc: AQICAHj9F0HBsgu ... kb6GOxJkiOMZSSxOtA== ...
В строках image.password
и test.secret
теперь содержатся зашифрованные значения, а затем – описание того, как SOPS его шифровал.
Просмотреть содержимое можно с помощью view
:
[simterm]
$ helm secrets view bttrm-apps-backend/secrets.yaml image: password: pass test: secret: testecret
[/simterm]
Отредактировать файл – с помощью edit
:
[simterm]
$ helm secrets edit bttrm-apps-backend/secrets.yaml
[/simterm]
Test deploy
Что бы задеплоить – просто вызываем helm
c install
или upgrade
, или как в нашем случае в Jenkins – upgrade --install
, но добавляем secrets
– helm
вызовет скрипт secrets.sh
, который выполнит всю работу:
[simterm]
$ helm secrets upgrade --install --namespace bttrm-apps-dev-1-ns --create-namespace --atomic bttrm-apps-backend bttrm-apps-backend/ -f bttrm-apps-backend/values.yaml -f bttrm-apps-backend/secrets.yaml Release "bttrm-apps-backend" has been upgraded. Happy Helming! ...
[/simterm]
Тут может быть интересно посмотреть процессы, которые запускаются во время вызова helm secrets upgrade
:
[simterm]
$ ps aux | grep helm setevoy 74906 0.0 0.3 1270888 49984 pts/1 Sl+ 17:05 0:00 helm secrets upgrade --install --namespace bttrm-apps-dev-1-ns --create-namespace --atomic bttrm-apps-backend bttrm-apps-backend/ -f bttrm-apps-backend/values.yaml -f bttrm-apps-backend/secrets.yaml setevoy 74914 0.0 0.0 4436 3672 pts/1 S+ 17:05 0:00 bash /home/setevoy/.local/share/helm/plugins/helm-secrets/secrets.sh upgrade --install --create-namespace --atomic bttrm-apps-backend bttrm-apps-backend/ -f bttrm-apps-backend/values.yaml -f bttrm-apps-backend/secrets.yaml setevoy 74963 0.8 0.5 1271768 96876 pts/1 Sl+ 17:06 0:00 helm upgrade bttrm-apps-backend bttrm-apps-backend/ --install --create-namespace --atomic -f bttrm-apps-backend/values.yaml -f bttrm-apps-backend/secrets.yaml.dec
[/simterm]
Т.е:
- мы вызываем
helm secrets upgrade
- затем вызывается
bash /home/setevoy/.local/share/helm/plugins/helm-secrets/secrets.sh upgrade
- который вызывает
helm upgrade
Проверяем наш секрет в Kubernetes:
[simterm]
$ kk -n bttrm-apps-dev-1-ns get secret test-secret -o yaml apiVersion: v1 data: example-secret: dGVzdGVjcmV0 ...
[/simterm]
И значение dGVzdGVjcmV0:
[simterm]
$ echo dGVzdGVjcmV0 | base64 --decode testecret
[/simterm]
Переходим к Jenkins.
Jenkins
Теперь надо всё это добавить в Jenkinsfile, который писали в предыдущем посте.
Если сейчас удалить релиз:
[simterm]
$ helm -n bttrm-apps-dev-1-ns delete bttrm-apps-backend release "bttrm-apps-backend" uninstalled
[/simterm]
А потом попробовать задеплоить его заново из Jenkins – то получим ошибку, т.к. helm
не сможет получить данные, которые мы вынесли из values.yaml
в secrets.yaml
:
[simterm]
+ helm upgrade --install --namespace bttrm-apps-dev-1-ns --create-namespace --atomic bttrm-apps-backend bttrm-apps-backend-20.tgz Release "bttrm-apps-backend" does not exist. Installing it now. Error: unable to build kubernetes objects from release manifest: error validating "": error validating data: [unknown object type "nil" in Secret.stringData.backend-apple-cert-passphrase, unknown object type "nil" in Secret.stringData.backend-apple-sigin-key-id, unknown object type "nil" in Secret.stringData.backend-db-password, unknown object type "nil" in Secret.stringData.backend-private-key, unknown object type "nil" in Secret.stringData.backend-secret]
[/simterm]
Что надо сделать – добавить в Докер-образ, который используется для билда, плагин helm-secrets
, а потом обновить стейдж stage(“Helm install”) в скрипте пайплайна – добавить вызов secrets
и -f secrets.yaml
.
Jenkins Docker build-image
Сейчас Dockefile для сборки образа, который используется для билда и деплоя выглядит так:
FROM bitnami/minideb:stretch RUN apt update && install_packages ca-certificates wget RUN install_packages curl python-pip python-setuptools jq git RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl RUN chmod +x ./kubectl RUN mv ./kubectl /usr/local/bin/kubectl WORKDIR /tmp RUN curl --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp RUN mv /tmp/eksctl /usr/local/bin RUN pip install ansible boto3 awscli WORKDIR /tmp RUN curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 RUN /bin/bash get_helm.sh USER root
Добавляем установку плагина – там исходный образ bitnami/minideb:stretch
– SOPS должен установится нормально, а не как на Arch Linux:
... RUN curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 RUN /bin/bash get_helm.sh RUN helm plugin install https://github.com/futuresimple/helm-secrets USER root
Собираем:
[simterm]
$ docker build -t bttrm/kubectl-aws:4.1 . ... Step 14/15 : RUN helm plugin install https://github.com/futuresimple/helm-secrets ---> Running in 9c8b1e891e86 /usr/bin/dpkg /root/.local/share/helm/plugins/helm-secrets/install-binary.sh: line 57: sudo: command not found Error: plugin install hook for "secrets" exited with error The command '/bin/sh -c helm plugin install https://github.com/futuresimple/helm-secrets' returned a non-zero code: 1
[/simterm]
Ага, счаз.
Окей – поставим SOPS из PIP:
... RUN mv /tmp/eksctl /usr/local/bin RUN pip install ansible boto3 awscli sops WORKDIR /tmp ...
Правда, во время сборки Helm сказал, что:
[simterm]
... sops is already installed: INFO: You're using Sops 1 written in Python. Sops 2 was rewritten in Go. Consider installing it with: $ go get -u go.mozilla.org/sops/cmd/sops sops 1.18 ...
[/simterm]
Посмотрим – как пойдёт во время билда в Jenkins.
Jenkinsfile
Обновляем скрипт пайплайна – добавляем secrets
и -f bttrm-apps-backend/secrets.yaml
:
... stage("Helm install") { ... sh "helm secrets upgrade --install --namespace ${AWS_EKS_NAMESPACE} --create-namespace --atomic ${APP_CHART_NAME} ${APP_CHART_NAME}-${RELEASE_VERSION}.tgz -f bttrm-apps-backend/secrets.yaml" } } } ...
Запускаем:
Готово.