Используйте SOPS с шифрованием Age для защиты секретов внутри репозитория git.
Установите утилиту шифрования
Установите age — простую, современную и безопасную утилиту шифрования.
Загрузите последнюю версию.
$ curl --silent --location --remote-name https://github.com/FiloSottile/age/releases/download/v1.1.1/age-v1.1.1-linux-amd64.tar.gz
Просмотрите содержимое архива.
$ tar --list --gzip --file age-v1.1.1-linux-amd64.tar.gz
age/
age/age
age/age-keygen
age/LICENSE
Извлеките двоичные файлы.
$ sudo tar --extract --gzip --file age-v1.1.1-linux-amd64.tar.gz --strip-components 1 --directory /usr/local/bin age/age age/age-keygen
Проверьте версию приложения.
$ age --version
v1.1.1
Установите менеджер секретов
Загрузите последние версии sops — простую и гибкую утилиту для управления секретами.
$ curl --silent --location --remote-name https://github.com/getsops/sops/releases/download/v3.8.1/sops-v3.8.1.linux.amd64
Установите приложение.
$ sudo install --owner root --group root --mode 555 sops-v3.8.1.linux.amd64 /usr/local/bin/sops
Проверьте версию приложения.
$ sops --version
sops 3.8.1 (latest)
Создание ключей шифрования
Создайте ключи.
$ age-keygen -o age-key-a.txt
Public key: age1nd2t2ndwkdszt7c6gw7npfa0khxfk8ql0vphz5qur69pehu8sycq6ug0c7
$ cat age-key-a.txt
# created: 2024-03-17T16:47:09Z
# public key: age1nd2t2ndwkdszt7c6gw7npfa0khxfk8ql0vphz5qur69pehu8sycq6ug0c7
AGE-SECRET-KEY-15S5H3ARAJE6392T5LD8NLNY5GDEGE3NF4QE7LH3F9WJGR8MSCGUQPCZ2ZN
$ age-keygen -o age-key-b.txt
Public key: age1vyumxy4tpxmh7xa3k9tenf6ezhkq0dm508qy66hukx36z9ydgcps8k30d4
$ cat age-key-b.txt
# created: 2024-03-17T16:47:20Z
# public key: age1vyumxy4tpxmh7xa3k9tenf6ezhkq0dm508qy66hukx36z9ydgcps8k30d4
AGE-SECRET-KEY-1458XV43FWUPVYAM7DEN2J5N9XEZNYCREJQL44ZY4GAJMJQX3YQFSWPASKZ
Защитите эти ключи.
Добавляем доверенные ключи
Для того чтобы зашифровать секрет для нескольких ключей нужно создать файл содержащий все публичные ключи Age/
echo 'age1ls2ftwdzyu0ptdqe8mysde7mzwjdagqvvepr3hk2ysduhlz47enqxtymfh,age17aeun7qfjz470e4k4r3650h6hxdghtnh3z73llczzzzhvhlal44q4h8lsq' > public-age-keys.txt
Создаем переменную с доверенными ключами
export SOPS_AGE_RECIPIENTS=$(<public-age-keys.txt)
И теперь можем зашифровать файл с чувствительными данными.
sops --encrypt --age ${SOPS_AGE_RECIPIENTS} secret.json > secret.json.enc
Оператор секретов
Определите версию Kubernetes.
$ kubectl version
Client Version: v1.29.2
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
Server Version: v1.29.2
Определите значения по умолчанию для sops-secrets-operator. Тег образа зависит от используемой версии программного обеспечения, ознакомьтесь с файлом readme в репозитории GitHub, чтобы узнать больше. Другими наиболее важными частями являются secretsAsFiles (какой секрет использовать) и extraEnv (какой ключ использовать).
$ tee sops-values.yaml <<'EOF'
# Default values for sops-secrets-operator.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
# https://github.com/norwoodj/helm-docs and https://pre-commit.com/
# are used to generate documentation automaticaly
# -- Deployment replica count - should not be modified
replicaCount: 1
# UPDATE_HERE
image:
# -- Operator image name
repository: isindir/sops-secrets-operator
# -- Operator image tag
tag: 0.12.4
# -- Operator image pull policy
pullPolicy: Always
# UPDATE_HERE
initImage:
# -- Init container image name
repository: ubuntu
# -- Init container image tag
tag: noble-20240225
# -- Init container image pull policy
pullPolicy: Always
# -- Secrets to pull image from private docker repository
imagePullSecrets: []
# -- Overrides auto-generated short resource name
nameOverride: ""
# -- Overrides auto-generated long resource name
fullnameOverride: ""
# -- Annotations to be added to operator pod (can be used with kiam or kube2iam)
podAnnotations: {}
serviceAccount:
# -- Annotations to be added to the service account
annotations: {}
# -- Requeue failed reconciliation in minutes (min 1). (default 5)
requeueAfter: 5
# -- Paths to a kubeconfig. Only required if out-of-cluster.
kubeconfig:
enabled: false
path:
# -- Logging configuration section suggested values
# Development Mode (encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn).
# Production Mode (encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error) (default)
logging:
# -- Zap Development Mode enabled
development: false
# -- Zap log encoding (one of 'json' or 'console')
encoder: json
# -- Zap Level to configure the verbosity of logging. Can be one of 'debug', 'info', 'error', or any integer value > 0 which corresponds to custom debug levels of increasing verbosity
level: info
# -- Zap Level at and above which stacktraces are captured (one of 'info', 'error').
stacktraceLevel: error
# -- Zap time encoding (one of 'epoch', 'millis', 'nano', 'iso8601', 'rfc3339' or 'rfc3339nano'). Defaults to 'epoch'.
timeEncoding: iso8601
healthProbes:
# -- The address the probe endpoint binds to. (default ":8081")
port: 8081
# -- Liveness probe configuration
liveness:
initialDelaySeconds: 15
periodSeconds: 20
# -- Readiness probe configuration
readiness:
initialDelaySeconds: 5
periodSeconds: 10
# -- GPG configuration section
gpg:
# -- If `true` GCP secret will be created from provided value and mounted as environment variable
enabled: false
# -- Name of the secret to create - will override default secret name if specified
secret1: gpg1
# -- Name of the secret to create - will override default secret name if specified
secret2: gpg2
# -- GCP KMS configuration section
gcp:
# -- Node labels for operator pod assignment
enabled: false
# -- Name of the secret to create - will override default secret name if specified
svcAccSecretCustomName: ''
# -- If `gcp.enabled` is `true`, this value must be specified as GCP service account secret json payload
svcAccSecret: ''
# -- Name of a pre-existing secret containing GCP service account secret json payload
existingSecretName: ''
# -- Azure KeyVault configuration section
azure:
# Specify credentials here or use existingSecretName below to use a pre-configred secret
# -- if true Azure KeyVault will be used
enabled: false
# -- TenantID of Azure Service principal to use
tenantId: ''
# -- ClientID (Application ID) of Azure Service Principal to use
clientId: ''
# -- Client Secret of Azure Service Principal
clientSecret: ''
# Pre-existing secret must contain the keys tenantId, clientId and clientSecret with the appropriate values
# -- Name of a pre-existing secret containing Azure Service Principal Credentials (ClientID, ClientSecret, TenantID)
existingSecretName: ''
# -- A list of additional environment variables
extraEnv:
- name: SOPS_AGE_KEY_FILE
value: /etc/sops-age-keys/age-key.txt
#- name: AWS_SDK_LOAD_CONFIG
# value: "1"
# -- configure custom secrets to be used as environment variables at runtime, see values.yaml
secretsAsEnvVars: []
#- name: SECRET_GREETING
# secretName: my-secret-greeting
# secretKey: greeting
# -- configure custom secrets to be mounted at runtime, see values.yaml
secretsAsFiles:
- mountPath: /etc/sops-age-keys
name: age-keys
secretName: sops-age-key-file
# Repeat for as many keys as you have
# All files within secret will be mounted in "/etc/foo" - same as 1st example in k8s documentation
# all secrets will be mounted as readonly
#- name: foo
# mountPath: "/etc/foo"
# secretName: mysecret
# -- Operator container resources
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 500m
# memory: 128Mi
# requests:
# cpu: 10m
# memory: 64Mi
# -- Node selector to use for pod configuration
nodeSelector: {}
securityContext:
# -- Enable securityContext
enabled: false
# -- UID to run as
runAsUser: 13001
# -- GID to run as
runAsGroup: 13001
# -- Enable kubelet validation for using root user to run container
runAsNonRoot: true
# -- fs group
fsGroup: 13001
# -- seccompProfile.type
seccompProfileType: RuntimeDefault
# -- if seccompProfile.type is set to Localhost, set localhostProfile to value of seccompProfileName (user must specify value)
seccompProfileName: ""
# -- container/initContainer
container:
# -- enables securityContext capabilities feature in containers
enabled: false
# -- capabilities
capabilities:
drop:
- all
add:
- NET_BIND_SERVICE
# -- Tolerations to be applied to operator pod
tolerations: []
# -- Node affinity for pod assignment
affinity: {}
rbac:
# -- Create and use RBAC resources
enabled: true
metrics:
# -- Enable prometheus metrics
enabled: false
EOF
Добавляем репозиторий sops-secrets-operator
$ helm3 repo add sops https://isindir.github.io/sops-secrets-operator/
"sops" has been added to your repositories
Установка оператора
$ helm3 upgrade --install sops sops/sops-secrets-operator --create-namespace --namespace sops --values sops-values.yaml
Release "sops" does not exist. Installing it now.
NAME: sops
LAST DEPLOYED: Sun Mar 17 20:40:47 2024
NAMESPACE: sops
STATUS: deployed
REVISION: 1
TEST SUITE: None
Он пока не будет работать, так как мы должны сначала создать секрет, который включает в себя ранее сгенерированные ключи.
$ kubectl get --namespace sops deployments
NAME READY UP-TO-DATE AVAILABLE AGE
sops-sops-secrets-operator 0/1 1 0 10m
Создайте секрет, используя один или несколько ключей.
$ kubectl create --namespace sops secret generic sops-age-key-file --from-file=age-key.txt=<(cat age-key-a.txt age-key-b.txt)
secret/sops-age-key-file created
Посмотрим созданный секрет.
$ kubectl get --namespace sops secret sops-age-key-file -o yaml
apiVersion: v1
data:
age-key.txt: IyBjcmVhdGVkOiAyMDI0LTAzLTE3VDE2OjQ3OjA5WgojIHB1YmxpYyBrZXk6IGFnZTFuZDJ0Mm5kd2tkc3p0N2M2Z3c3bnBmYTBraHhmazhxbDB2cGh6NXF1cjY5cGVodThzeWNxNnVnMGM3CkFHRS1TRUNSRVQtS0VZLTE1UzVIM0FSQUpFNjM5MlQ1TEQ4TkxOWTVHREVHRTNORjRRRTdMSDNGOVdKR1I4TVNDR1VRUENaMlpOCiMgY3JlYXRlZDogMjAyNC0wMy0xN1QxNjo0NzoyMFoKIyBwdWJsaWMga2V5OiBhZ2Uxdnl1bXh5NHRweG1oN3hhM2s5dGVuZjZlemhrcTBkbTUwOHF5NjZodWt4MzZ6OXlkZ2NwczhrMzBkNApBR0UtU0VDUkVULUtFWS0xNDU4WFY0M0ZXVVBWWUFNN0RFTjJKNU45WEVaTllDUkVKUUw0NFpZNEdBSk1KUVgzWVFGU1dQQVNLWgo=
kind: Secret
metadata:
creationTimestamp: "2024-03-17T21:00:05Z"
name: sops-age-key-file
namespace: sops
resourceVersion: "133989"
uid: a76654cf-d6ae-42bd-a785-d3035b48d691
type: Opaque
Расшифруйте его содержимое.
$ kubectl get --namespace sops secret sops-age-key-file -o go-template='{{ range $key, $value := .data }}{{ $key }}{{ ": " }}{{ $value | base64decode }}{{ "\n" }}{{ end }}'
age-key.txt: # created: 2024-03-17T16:47:09Z
# public key: age1nd2t2ndwkdszt7c6gw7npfa0khxfk8ql0vphz5qur69pehu8sycq6ug0c7
AGE-SECRET-KEY-15S5H3ARAJE6392T5LD8NLNY5GDEGE3NF4QE7LH3F9WJGR8MSCGUQPCZ2ZN
# created: 2024-03-17T16:47:20Z
# public key: age1vyumxy4tpxmh7xa3k9tenf6ezhkq0dm508qy66hukx36z9ydgcps8k30d4
AGE-SECRET-KEY-1458XV43FWUPVYAM7DEN2J5N9XEZNYCREJQL44ZY4GAJMJQX3YQFSWPASKZ
Подождите, пока sops-оператор не будет готово к использованию.
$ kubectl wait --namespace sops deployment --for=condition=available --selector=app.kubernetes.io/name=sops-secrets-operator --timeout=120s
deployment.apps/sops-sops-secrets-operator condition met
Теперь sops-оператор работает.
Создавайте секреты с помощью шаблонов
$ tee sopssecret-general-1.yml <<EOF
apiVersion: isindir.github.com/v1alpha3
kind: SopsSecret
metadata:
name: sopssecret-general-1
namespace: default
spec:
suspend: false
secretTemplates:
- name: secret-index-1
stringData:
index: "Hello world (secret 1)!!!"
EOF
$ sops --age age1nd2t2ndwkdszt7c6gw7npfa0khxfk8ql0vphz5qur69pehu8sycq6ug0c7 --encrypted-suffix='Templates' --encrypt sopssecret-general-1.yml > sopssecret-general-1.enc.yml
$ cat sopssecret-general-1.enc.yml
apiVersion: isindir.github.com/v1alpha3
kind: SopsSecret
metadata:
name: sopssecret-general-1
namespace: default
spec:
suspend: false
secretTemplates:
- name: ENC[AES256_GCM,data:A9NHhUbkei9vuEUUlzw=,iv:CoKDgKUwD1+xTyaZqed8zn+3fNZ38V0Qn/ZdOL6JTn8=,tag:+W1z0fAt0pGJtrI0jgpcFw==,type:str]
stringData:
index: ENC[AES256_GCM,data:L01jnkjyLcLLKnNqTrHQvkOfzzu+spJHyQ==,iv:vM1nxLm4v0w/cTW2zaWyI2utsN6YemGk/8USh2b127I=,tag:2KJltWvYSHtMHBWpBpD7gg==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1nd2t2ndwkdszt7c6gw7npfa0khxfk8ql0vphz5qur69pehu8sycq6ug0c7
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLUGhxY1Q2eUhzSjN1dGdS
OFpjRElEYzdGRGt5VnVXbElRTnJSSnYxSVFnCk1UanM5UFRGekNhOWFOL2I2ZTk3
MFhlMWlkbkJ3V2ZrRktBSE1xU1NlUlUKLS0tIEs5UE1ZODBHVXZPbFhTbGpqblRo
T2tBb3VsUVpsdTAvSUlRbjZSbThsQTQK9jhi1GrnnQwYYAK6HN8/hCRm1ln81PGl
RDFOt21tUpo1uKIt8twY3V5e1KhEWraGeAINtNW0K9010BsdFm7BUA==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2024-03-17T21:28:56Z"
mac: ENC[AES256_GCM,data:2C5aP0oSZAUSu4lpa7gvX9YKb+/22ZlaYjXmzVStRS9Uv/uEWxA/NJlP8Mu7fBWdkNEtbAd78/Y4THs2qIo9p2nuUDeQ7RQlMHh5icpScaCNx/amIpX6P3VQO7fNcl3aYvdC7Su5N4xdqqnRqe8cThTHgrKBX8xhO27bm1n0lXw=,iv:QPdcHQj4GW1vKQ2i1hYgtDykvqlr4Scll1+Xg4h9ib0=,tag:kJFNMur1FrqggpkxxT1xWQ==,type:str]
pgp: []
encrypted_suffix: Templates
version: 3.8.1
Создайте и зашифруйте второй секретный шаблон.
$ tee sopssecret-general-2.yml <<EOF
apiVersion: isindir.github.com/v1alpha3
kind: SopsSecret
metadata:
name: sopssecret-general-2
namespace: default
spec:
suspend: false
secretTemplates:
- name: secret-index-2
stringData:
index: "Hello world (secret 2)!!!"
EOF
$ sops --age age1vyumxy4tpxmh7xa3k9tenf6ezhkq0dm508qy66hukx36z9ydgcps8k30d4 --encrypted-suffix='Templates' --encrypt sopssecret-general-2.yml > sopssecret-general-2.enc.yml
$ cat sopssecret-general-2.enc.yml
apiVersion: isindir.github.com/v1alpha3
kind: SopsSecret
metadata:
name: sopssecret-general-2
namespace: default
spec:
suspend: false
secretTemplates:
- name: ENC[AES256_GCM,data:0yRcsB8rjm2pXIVYXsE=,iv:wqg1tO1uCVQYgnpSo3ijFw6E8lvX/xHCQVooWoGn6No=,tag:UrjnJZXdcbeDMTa9AaAZxg==,type:str]
stringData:
index: ENC[AES256_GCM,data:Ns7KDI+agA2+c5TGoLp8VV/OlmTY8C+C/A==,iv:H4oqTU1fGVUf6HI6364NvegwPdHD8xoqvQcAbrLZ1pw=,tag:EhelBnsjODBEmQVXs35WPg==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1vyumxy4tpxmh7xa3k9tenf6ezhkq0dm508qy66hukx36z9ydgcps8k30d4
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnYmRnVWVPdnRWdzNsUFk0
Wk04NWF1ZU1nZWI2STEwSDdNQ0tRQStkaGlNCnZvZmkvWkxGSERvckFYcFVUMHFp
ZUtFWkNzUXVhSFFZTkRONVVvdlU3ck0KLS0tIFlHeXlyY0lDZUZEbFc0WDJZNG9F
WERsd1V1dUdtM0wrcUVCQ3Qwb1BFaFUK0++rRSLfz4J5wJDFbL0BDlI3P/kwlQ7A
/AAp/ocfCVKjCg9MD/4ariRtGk5ALaIoFypkOXIByoj3lQ/Nc8TP3g==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2024-03-17T21:32:00Z"
mac: ENC[AES256_GCM,data:FEtKZaZMYboslvl/9jterThtY9srcp3XnNGbBo2EiceR//UK/KH9fRI2Zz2Ek7+eKLwHUYsPuKy565jsKl7t+GUVCaGlsROVzkx1h5duPnEYinXvCvzYacvQa1ztW/YGpxYUYwpPUVns0bYt48wvWu0BGpeDqunx0g0y0xUlQQk=,iv:CQDZQTTNiSrtGEjXnm9SDmTpHoaesoBRSUOtgTyCshc=,tag:BZcUUA/fFq/Uc1E5SjA2sA==,type:str]
pgp: []
encrypted_suffix: Templates
version: 3.8.1
Применим манифесты в кубер.
$ kubectl apply -f sopssecret-general-1.enc.yml,sopssecret-general-2.enc.yml
sopssecret.isindir.github.com/sopssecret-general-1 created
sopssecret.isindir.github.com/sopssecret-general-2 created
Проверьте созданные ресурсы.
$ kubectl get sopssecrets
NAME STATUS
sopssecret-general-1 Healthy
sopssecret-general-2 Healthy
Проверяйте секреты, созданные на основе шаблонов.
$ kubectl get secrets
NAME TYPE DATA AGE
secret-index-1 Opaque 1 26s
secret-index-2 Opaque 1 26s
$ kubectl get secret secret-index-1 secret-index-2 -o go-template='{{ range .items -}}{{ range $key, $value := .data }}{{ $key }}{{ ": " }}{{ $value | base64decode }}{{ "\n" }}{{ end }}{{ end }}'
index: Hello world (secret 1)!!!
index: Hello world (secret 2)!!!
Выполните простой тест
Создайте примеры deployments
$ kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: octopus-httpd
name: httpd-octopus
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: octopus-httpd
template:
metadata:
labels:
app: octopus-httpd
spec:
containers:
- image: httpd:latest
name: octopus-httpd
ports:
- name: http
protocol: TCP
containerPort: 80
volumeMounts:
- name: html-volume
mountPath: "/usr/local/apache2/htdocs/index.html"
subPath: "index.html"
volumes:
- name: html-volume
secret:
secretName: secret-index-1
items:
- key: index
path: index.html
---
apiVersion: v1
kind: Service
metadata:
name: octopus-service
spec:
type: ClusterIP
ports:
- name: http
port: 80
protocol: TCP
selector:
app: octopus-httpd
EOF
deployment.apps/httpd-octopus created
service/octopus-service created
$ kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: squid-httpd
name: httpd-squid
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: squid-httpd
template:
metadata:
labels:
app: squid-httpd
spec:
containers:
- image: httpd:latest
name: squid-httpd
ports:
- name: http
protocol: TCP
containerPort: 80
volumeMounts:
- name: html-volume
mountPath: "/usr/local/apache2/htdocs/index.html"
subPath: "index.html"
volumes:
- name: html-volume
secret:
secretName: secret-index-2
items:
- key: index
path: index.html
---
apiVersion: v1
kind: Service
metadata:
name: squid-service
spec:
type: ClusterIP
ports:
- name: http
port: 80
protocol: TCP
selector:
app: squid-httpd
EOF
deployment.apps/httpd-squid created
service/squid-service created
Проверьте созданные ресурсы.
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
httpd-octopus 1/1 1 1 59s
httpd-squid 1/1 1 1 38s
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.152.183.1 443/TCP 97m
octopus-service ClusterIP 10.152.183.152 80/TCP 6m11s
squid-service ClusterIP 10.152.183.138 80/TCP 5m31s
Выполните тест.
$ curl 10.152.183.152
Hello world (secret 1)!!!
$ curl 10.152.183.138
Hello world (secret 2)!!!
Дополнительные примечания
Вы сможете обновить helm-chart
$ helm3 upgrade --reuse-values -f sops-values.yaml -n default sops sops/sops-secrets-operator
Release "sops" has been upgraded. Happy Helming!
NAME: sops
LAST DEPLOYED: Sun Mar 17 12:18:41 2024
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None
В редких случаях вы увидите аналогичную ошибку при попытке перезаписать существующий секрет
$ kubectl get sopssecrets
NAME STATUS
sopssecret-general-1 Healthy
sopssecret-general-2 Child secret is not owned by controller error
$ kubectl get secrets
NAME TYPE DATA AGE
secret-index Opaque 1 3m34s
Чтобы поменять ключи местами, просто добавьте новый и повторно зашифруйте SopsSecrets.
Источник-1: https://sleeplessbeastie.eu/2024/03/20/how-to-utilize-sops-with-age-encryption/
Источник-2: https://devops.datenkollektiv.de/using-sops-with-age-and-git-like-a-pro.html
Was this helpful?
0 / 0