ExternalSecrets Operator part 1
Данное расширение позволяет синхронизировать секреты из других провайдеров с секретами в k8s. Эта статья - первая часть, посвящённая данному решению.
Установка в k8s.
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets \
external-secrets/external-secrets \
-n external-secrets \
--create-namespace
------------------------------------------------------------------------
helm list -n external-secrets
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
external-secrets external-secrets 1 2023-08-21 15:28:29.095992 +0300 MSK deployed external-secrets-0.9.2 v0.9.2
ExternalSecret Operator создаёт несколько сущностей.
kubectl api-resources
clusterexternalsecrets ces external-secrets.io/v1beta1 false ClusterExternalSecret
clustersecretstores css external-secrets.io/v1beta1 false ClusterSecretStore
externalsecrets es external-secrets.io/v1beta1 true ExternalSecret
secretstores ss external-secrets.io/v1beta1 true SecretStore
Создаваемые ресурсы.
kubectl get po -n external-secret
NAME READY STATUS RESTARTS AGE
external-secrets-5b6fbc88f9-286m6 1/1 Running 0 13m
external-secrets-cert-controller-7b7d4c9bcd-7gnq4 1/1 Running 0 13m
external-secrets-webhook-7446c9896-br9ql 1/1 Running 0 13m
kubectl get deploy -n external-secret
NAME READY UP-TO-DATE AVAILABLE AGE
external-secrets 1/1 1 1 31m
external-secrets-cert-controller 1/1 1 1 31m
external-secrets-webhook 1/1 1 1 31m
kubectl get svc -n external-secret
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
external-secrets-webhook ClusterIP 10.75.239.144 <none> 443/TCP 17m
kubectl get crd
NAME CREATED AT
clusterexternalsecrets.external-secrets.io 2023-08-16T09:50:37Z
clustersecretstores.external-secrets.io 2023-08-16T09:50:37Z
externalsecrets.external-secrets.io 2023-08-16T09:50:37Z
secretstores.external-secrets.io 2023-08-16T09:50:37Z
kubectl get validatingwebhookconfigurations
NAME WEBHOOKS AGE
externalsecret-validate 1 19m
secretstore-validate 2 19m
kubectl get secret -n external-secret
NAME TYPE DATA AGE
external-secrets-webhook Opaque 4 22m
sa-creds Opaque 1 22m
sh.helm.release.v1.external-secrets.v1 helm.sh/release.v1 1 22m
kubectl get cm -n external-secret
NAME DATA AGE
kube-root-ca.crt 1 22m
kubectl get sa -n external-secret
NAME SECRETS AGE
default 0 23m
external-secrets 0 23m
external-secrets-cert-controller 0 23m
external-secrets-webhook 0 23m
kubectl get role -n external-secret
NAME CREATED AT
external-secrets-leaderelection 2023-08-16T09:50:37Z
kubectl describe role external-secrets-leaderelection -n external-secret
Name: external-secrets-leaderelection
Labels: app.kubernetes.io/instance=external-secrets
app.kubernetes.io/managed-by=Helm
app.kubernetes.io/name=external-secrets
app.kubernetes.io/version=v0.5.5
helm.sh/chart=external-secrets-0.5.5
Annotations: meta.helm.sh/release-name: external-secrets
meta.helm.sh/release-namespace: external-secret
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
configmaps [] [] [create]
leases.coordination.k8s.io [] [] [get create update patch]
configmaps [] [external-secrets-controller]
Troubleshooting
При возникновении проблем с синхронизацией секретов необходимо будет посмотреть логи пода external-secrets. Там будет указана ошибка.
#Пример
kubectl logs external-secrets-644849d5c9-j9984 -n external-secrets
Провайдеры.
Fake Provider.
Используется только в рамках тестирования работоспособности сервиса.
kubectl apply -f - <<< '
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: fake
spec:
provider:
fake:
data:
- key: "/foo/bar"
value: "HELLO1"
version: "v1"
- key: "/foo/bar"
value: "HELLO2"
version: "v2"
- key: "/foo/baz"
valueMap:
foo: example
other: thing'
kubectl get css
NAME AGE STATUS CAPABILITIES READY
fake 11s Valid ReadWrite True
kubectl apply -f - <<< '
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: fake-secret
spec:
refreshInterval: 1h
secretStoreRef:
name: fake
kind: ClusterSecretStore
target:
name: secret-to-be-created
data:
- secretKey: foo_bar
remoteRef:
key: /foo/bar
version: v1
dataFrom:
- extract:
key: /foo/baz'
kubectl get es
NAME STORE REFRESH INTERVAL STATUS READY
fake-secret fake 1h SecretSynced True
kubectl get secret secret-to-be-created --output=json | jq --raw-output ".data"
{
"foo": "ZXhhbXBsZQ==",
"foo_bar": "SEVMTE8x",
"other": "dGhpbmc="
}
--------------------------------------------------------------------------
apiVersion: v1
kind: Secret
metadata:
name: secret-to-be-created
namespace: default
data:
foo_bar: SEVMTE8x # HELLO1 (via data)
foo: ZXhhbXBsZQ== # example (via dataFrom)
other: dGhpbmc= # thing (via dataFrom)
ExternalSecret Operator + YC Certificate Manager
ExternalSecret позволяет использовать внешнюю, по отношению к кластеру Kubernetes, секретницу. В данном примере используется интеграция с YC CM.
Предполагаю, что SA есть и IAM ключ для него уже сгенерирован. Если нет, то можно воспользоваться инструкцией: https://teletype.in/@cameda/w93lPMPQWS4
На всякий случай. Генерацию IAM ключа для SA можно выполнить так:
yc iam key create --service-account-id $SA --output key.json
Создаётся файл key.json, который можно использовать в дальнейшем.
Создаём самоподписанный сертификат и добавляем его в CM.
openssl req -x509 -newkey rsa:4096 -nodes \
-keyout key.pem \
-out cert.pem \
-days 365 \
-subj '/CN=example.com'
yc cm certificate create \
--name camedakey \
--chain cert.pem \
--key key.pem
yc cm certificate get camedakey
id: fpqtr8jd2jd07a8u60vp
folder_id:
created_at: "2023-08-21T13:31:28.078Z"
name: camedakey
type: IMPORTED
domains:
- example.com
status: ISSUED
issuer: CN=example.com
subject: CN=example.com
serial: 7d358e9654ba7771df479b778093800eebc9600e
updated_at: "2023-08-21T13:31:28.078Z"
issued_at: "2023-08-21T13:31:28.078Z"
not_after: "2024-08-20T13:29:48Z"
not_before: "2023-08-21T13:29:48Z"
yc cm certificate add-access-binding \
--id fpqtr8jd2jd07a8u60vp \
--service-account-name cameda-service \
--role certificate-manager.certificates.downloader
Посмотрим содержимое сертификата.
yc certificate-manager certificate content \
--id fpqtr8jd2jd07a8u60vp \
--chain cert.pem \
--key key.pem
Создаём секрет на основе файла key.json.
kubectl create secret generic yc-auth --from-file=authorized-key=key.json
# Проверяем, что содержимое соответствует файлу.
kubectl get secret yc-auth --output=json | jq --raw-output ".data[]" | base64 -d
Создаём SecretStore на основе секрета.
kubectl apply -f - <<< '
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: cm-store
spec:
provider:
yandexcertificatemanager:
auth:
authorizedKeySecretRef:
name: yc-auth
key: authorized-key'
kubectl get ss
NAME AGE STATUS CAPABILITIES READY
cm-store 143m Valid ReadOnly True
Создаём ExternalSecret, использующий SecretStore.
kubectl apply -f - <<< '
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: cm-secret
spec:
refreshInterval: 30m
secretStoreRef:
name: cm-store
kind: SecretStore
target:
name: cm-secret # the target k8s secret name
template:
type: kubernetes.io/tls
data:
- secretKey: tls.crt # the target k8s secret key
remoteRef:
key: fpqtr8jd2jd07a8u60vp # the certificate ID
property: chain
- secretKey: tls.key # the target k8s secret key
remoteRef:
key: fpqtr8jd2jd07a8u60vp # the certificate ID
property: privateKey'
В secretKey указываем именно tls.crt и tls.key, а не cert.pem и key.pem.
Иначе будет ошибка.
kubectl get es
NAME STORE REFRESH INTERVAL STATUS READY
cm-secret cm-store 30m SecretSynced True
#Проверим содержимое сертификата.
kubectl get secret cm-secret --output=json | jq --raw-output ".data[]" | base64 -d
Всё норм! Секрет создался и синконулся. Если декодировать его, то получим те же данные, что и в CM.
ExternalSecret Operator + YC LockBox
ExternalSecret позволяет использовать внешнюю, по отношению к кластеру Kubernetes, секретницу. В YC этой секретницей является Lockbox.
Предполагаю, что SA есть и IAM ключ для него уже сгенерирован. Если нет, то можно воспользоваться инструкцией: https://teletype.in/@cameda/w93lPMPQWS4
На всякий случай. Генерацию IAM ключа для SA можно выполнить так:
yc iam key create --service-account-id $SA --output key.json
Создаётся файл key.json, который можно использовать в дальнейшем.
KMS ключ сгенерировал здесь: https://teletype.in/@cameda/vBIMDOZi7vV
Lockbox был создан по этому мануалу: https://teletype.in/@cameda/M1Rm88sCB7t
Создаём секрет на основе файла key.json.
kubectl create secret generic yc-auth --from-file=authorized-key=key.json
# Проверяем, что содержимое соответствует файлу.
kubectl get secret yc-auth --output=json | jq --raw-output ".data[]" | base64 -d
Создаём SecretStore на основе секрета.
kubectl apply -f - <<< '
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: secret-store
spec:
provider:
yandexlockbox:
auth:
authorizedKeySecretRef:
name: yc-auth
key: authorized-key'
kubectl get ss
NAME AGE STATUS
secret-store 4s Valid
SecretStore в статусе Valid. Значит создалось нормально.
Создаём ExternalSecret.
kubectl apply -f - <<< '
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: external-secret
spec:
refreshInterval: 1h
secretStoreRef:
name: secret-store
kind: SecretStore
target:
name: k8s-secret
data:
- secretKey: ssh
remoteRef:
key: e6qad2p5dog8d6e07mol
property: ssh'
secret-store - имя созданного SecretStore для обеспечения доступа к Lockbox;
k8s-secret - имя создаваемого секрета;
e6qa5j84t1gaq1fc1lgr - идентификатор секрета в Lockbox;
ssh - ключ секрета.
kubectl get es
NAME STORE REFRESH INTERVAL STATUS
external-secret secret-store 1h SecretSynced
# Проверяем, что содержимое ключа отдаётся верно.
kubectl get secret k8s-secret --output=json | jq --raw-output ".data.ssh" | base64 -d