Секрети в репозиторіях. Як SOPS і FluxCD рятують DevOps від помилок

На самому початку співпраці з клієнтом наша компанія проводить процес discovery. Отримавши доступ до оточення потенційного клієнта, протягом декількох днів наш CTO та ще один інженер (іноді це я) досліджують усе, що можливо, та шукають місця, які можна покращити. Де можна налаштувати автоматизацію, прибрати зайве і загалом зробити життя розробників клієнта краще.

Під час вищезгаданого процесу discovery можна зустріти один тривіальний момент: секрети та ключі, закомічені у репозиторій. І, судячи з новин про великі зливи кодових баз, це явище доволі розповсюджене навіть серед великих компаній, як-то Marcedez-Benz чи Github. А відповідно до Gitguardian, у 2023 році загалом у публічні репозиторії було викладено 12М секретів.

Чому ми бачимо секрети та ключі у репозиторіях? На мою думку, головна причина в тому, що це зручно. Усі ми розуміємо, що це небезпечно і може призвести до дуже серйозних наслідків як для компанії, так і для розробника. Що усі секрети мають зберігатися у сховищі секретів, наприклад, Hashicorp Vault, і підтягуватись у Kubernetes-поди через оператор.

Але будемо чесними, якщо компанії не потрібно пройти сертифікацію SOC2 для якогось контракту, ця таска буде лежати в беклозі та ніхто її не чіпатиме. Тому що ми завжди бажаємо простоти та зручності.

Тому сьогодні я хочу розповісти, як почати шифрувати секрети у репозиторій за допомогою SOPS, не жертвуючи зручністю. А щоб це була стаття про DevOps, налаштуємо розшифрування та деплоймент наших секретів у Kubernetes-кластер за допомогою FluxCD.

Дисклеймер #1. Підхід, який я описую далі, в жодному разі не претендує на звання найбезпечнішого. Зберігання секретів поза репозиторієм завжди буде найкращим варіантом. Цей підхід не працюватиме для всіх компаній та команд.

Дисклеймер #2. Принципи роботи FluxCD та його базові налаштування не описуються в цій статті.

SOPS

SOPS — це утиліта для шифрування/розшифрування файлів. SOPS здатен працювати з широким набором інструментів та сервісів:

  • PGP;
  • age;
  • GCP KMS;
  • Azure Key Vault;
  • AWS KMS;
  • Hashicorp Vault.

Базова робота з SOPS виглядає таким чином:

1. Створюємо ключ (у цьому прикладі я використовуватиму age-ключі):

18:31:20 ❯ age-keygen > age.key
age-keygen: warning: writing secret key to a world-readable file
Public key: age1qfe60mzumfm65a34jj8fe340fe7crh2ukuxpx64t35kjl947qsuslsdrf5

2. Шифруємо файл:

18:32:15 ❯ cat dou.txt
hello dou.ua
18:32:37 ❯ sops -e --age age1qfe60mzumfm65a34jj8fe340fe7crh2ukuxpx64t35kjl947qsuslsdrf5 -i dou.txt
18:32:40 ❯ cat dou.txt
{
    	"data": "ENC[AES256_GCM,data:pTh//dMJ8jEVDFWcKg==,iv:Z5k41VNpgAS5+dh21fAxLaflC148nbLfr0yOLYRWFes=,tag:/tt5ReaCYTLhzxnlpFGRjA==,type:str]",
    	"sops": {
            	"kms": null,
            	"gcp_kms": null,
            	"azure_kv": null,
            	"hc_vault": null,
            	"age": [
                    	{
                            	"recipient": "age1qfe60mzumfm65a34jj8fe340fe7crh2ukuxpx64t35kjl947qsuslsdrf5",
                            	"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSByRnhTZkRqci9GUzFPUU4r\nbG9vdDdkQ0JOZmhKUFd2alBiOVhaenhWMmdnCi9LZWNzNEVaVVV1MnU1dGErcXBI\nK0IwYURaY01MbUE3VVhLeHI1YWFtVlUKLS0tIFN0SzFGU0RXdTBwcy8vTFJmaWc1\nbWxRRE9yUWM3cnlDOGhJd3MxbWsyQ3cKTDidXcN7myCfaPfQALp79UHJqxcnnfMm\nOZp7FTYwUSoteoVOAcsivDTGwmzC9fAfYow8MFnEqMZHprpupGE+Zg==\n-----END AGE ENCRYPTED FILE-----\n"
                    	}
            	],
            	"lastmodified": "2024-08-05T16:08:50Z",
            	"mac": "ENC[AES256_GCM,data:h2Pjlo25AAdhq38QvAIxcNVI4u7aGk2WQc6I9I/I2R6lr1bL2ZGZWonmJwfcI4dhRPSW3nV7EXsw+C9X/gYWoajc1Oz4ImgG8ogiCMoy1mRFRG+sccXiqd39HLPxb57DM8JhMja0ERu0/Q8zns7vQPCLPqU/oCUDKkdTYgEkr3I=,iv:pg7EKRfucyfnIPEGra+o2nbsL7YkAfzroh6Td2CvpLA=,tag:9x86fvQ11RVk9g2c1zySpA==,type:str]",
            	"pgp": null,
            	"unencrypted_suffix": "_unencrypted",
            	"version": "3.7.3"
    	}
}

3. Розшифровуємо файл:

18:36:29 ❯ sops -d -i dou.txt
Failed to get the data key required to decrypt the SOPS file.

Group 0: FAILED
  age163ggudjavhda8n3h65y3k95ag0jrwr6uyvtz2d4uju8w4zemf54s8m9qr8: FAILED
	- | no age identity found in "/Users/rtim/Library/Application
  	| Support/sops/age/keys.txt" that could decrypt the data

Recovery failed because no master key was able to decrypt the file. In
order for SOPS to recover the file, at least one key has to be successful,
but none were.

SOPS не має приватного ключа, що відповідає публічному ключу, яким був зашифрований файл. Як можна побачити, нам потрібно додати ключ у /Users/user/Library/ApplicationSupport/sops/age/keys.txt:

cat age.key >> '/Users/user/Library/Application Support/sops/age/keys.txt'

На Linux SOPS використовує наступний шлях для зберігання age-ключів: $XDG_CONFIG_HOME/sops/age/keys.txt або $HOME/.config/sops/age/keys.txt.

4. Розшифровуємо файл, додавши приватний ключ до сховища:

19:11:32 ❯ sops -d -i dou.txt
19:11:34 ❯ cat dou.txt
hello dou.ua

Налаштовуємо правила у .sops.yaml

Базовий підхід чудово працює для одного чи двох файлів. Якщо є необхідність опрацьовувати велику кількість файлів, або послуговуватись кількома різними ключами, можемо використати creationRules у .sops.yaml, який можна покласти у корінь репозиторія. Команда sops рекурсивно шукає у репозиторії файл .sops.yaml. Якщо такий знайдено — ім’я файлу, який зараз обробляється, порівнюється з regex-виразами у цьому файлі. Ключ буде обрано з першого виразу, під який підпадає ім’я файлу.

Отже, `.sops.yaml` виглядатиме таким чином (приватні ключі я вже додав до сховища):

creation_rules:
  - path_regex: deploy/development/secrets/.*
	age: age1qfe60mzumfm65a34jj8fe340fe7crh2ukuxpx64t35kjl947qsuslsdrf5
  - path_regex: deploy/production/secrets/.*
	age: age14cjcdma8udezj6krmntyhdy9x0t388qjg6wuh0klslcd3mmdfgms0mymsl

Також маємо 2 секрети в окремих папках:

19:33:58 ❯ cat deploy/development/secrets/dou.txt
This is a DEVELOPMENT secret from dou

19:34:01 ❯ cat deploy/production/secrets/dou.txt
and this is a PRODUCTION secret from dou

Тепер, шифруючи та розшифровуючи ці два файли, SOPS буде використовувати різні ключі, відповідно до creation_rules (зверніть увагу на recipient):

19:36:43 ❯ sops -e -i ./deploy/development/secrets/*
19:36:49 ❯ cat ./deploy/development/secrets/dou.txt
{
    	"data": "ENC[AES256_GCM,data:jnSI3LqSKcoeRZX9FZ/4ZiHYBqpQSPodYRgvB/6UAIqxWfQSOnmT,iv:adWYc2ylJ1n1dIkJz4k9Jrj3Wo2ns6XxM9lTFXwC4M0=,tag:sSDIigWVTjjt5psgKW+nzQ==,type:str]",
    	"sops": {
            	"kms": null,
            	"gcp_kms": null,
            	"azure_kv": null,
            	"hc_vault": null,
            	"age": [
                    	{
                            	"recipient": "age1qfe60mzumfm65a34jj8fe340fe7crh2ukuxpx64t35kjl947qsuslsdrf5",
                            	"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBaL1ljUi9YSjRqUHp0aWRU\nWDVCZ3JQc3kyUTN4OG1vVTJYTGcyanlqTFZzCkpITTJPdWlnWE1wYzVrbDVPK0Rm\na0VZU2orRzdtbVd5YjRtWjhaTml0QWsKLS0tIHUxYzYwMlRsb0d5bHA0MzVaQzBh\nU3BMd1V5STI2cXpHdzVFMGFQVGdpZFUKMUkpQkh+QIAuU6F9G5WnZJd8aPikkykw\nGg77inL63oNDz6V9Xws0oRlpj7YbXRqy9oUk+iToUXxx1+iEqQCLGw==\n-----END AGE ENCRYPTED FILE-----\n"
                    	}
            	],
            	"lastmodified": "2024-08-05T16:36:43Z",
            	"mac": "ENC[AES256_GCM,data:A+cxx0sYKf2h9rJJqZhGYp0fn/5AQss3Obo++mjtvdzRB0sHfpQ0yaLjvBQTjpfe8yG7eZE5GzrQUwPAPRbywsV+wVTKPsURAqpTozZH8epLh6KOMTrCq/gOiz9/woUzKoTPCkBkAN/H8v0Al6EebHdZ+1+rsCXQTVNvBJRfDuI=,iv:QRENhojGh0ihVowsX5B4VQXK3CR0JDYX9vwYwKEEpc8=,tag:lywtECYJwP1Hzr817Hmu6Q==,type:str]",
            	"pgp": null,
            	"unencrypted_suffix": "_unencrypted",
            	"version": "3.7.3"
    	}
}

Production-файл виглядає так:

19:37:30 ❯ sops -e -i ./deploy/production/secrets/*
19:37:31 ❯ cat ./deploy/production/secrets/dou.txt | grep recipient
"recipient": "age14cjcdma8udezj6krmntyhdy9x0t388qjg6wuh0klslcd3mmdfgms0mymsl",

Як бачимо, ці 2 файли були зашифровані різними публічними ключами, відповідно, для розшифрування будуть використовуватись різні приватні ключі.

Шифрування тільки певної частини файлу

В усіх попередніх прикладах SOPS шифрував увесь файл. При роботі з Kubernetes такий підхід може виявитись незручним. Наприклад, хотілося б бачити назву секрету без необхідності розшифровувати файл. Або ж зашифрувати тільки певну частину Helm values. Для цього потрібно у sops.yaml-файлі до відповідного правила додати атрибут encrypted_regex:

creation_rules:
  - path_regex: deploy/development/secrets/.*.yaml
	age: age1qfe60mzumfm65a34jj8fe340fe7crh2ukuxpx64t35kjl947qsuslsdrf5
	encrypted_regex: ^(data|stringData)$
  - path_regex: deploy/production/secrets/.*.yaml
	age: age14cjcdma8udezj6krmntyhdy9x0t388qjg6wuh0klslcd3mmdfgms0mymsl

Тепер, коли я шифруватиму ConfigMap чи Secret-маніфести у відповідних директоріях, тільки частина з секретною інформацією буде зашифрована:

19:47:59 ❯ sops -e -i ./deploy/development/secrets/configmap.yaml
19:48:23 ❯ cat ./deploy/development/secrets/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
	name: dou
	namespace: default
data:
	dou: ENC[AES256_GCM,data:j+Y=,iv:irtkrZks8R4gJBIyQtIq/kUAr7bAB+r9IyDBhfBH1d4=,tag:U6Z7r8khWaHklDvvrFliSw==,type:str]
	glory: ENC[AES256_GCM,data:BI2Ar6AuPg==,iv:IS0UMCYwoVLyg0k0cMdSG6phagTQptx0zwzR4Ht5LK0=,tag:0GyzgTBp+joeVlR/X4dj0g==,type:str]
sops:
	kms: []
	gcp_kms: []
	azure_kv: []
	hc_vault: []
	age:
    	- recipient: age1qfe60mzumfm65a34jj8fe340fe7crh2ukuxpx64t35kjl947qsuslsdrf5
      	enc: |
        	-----BEGIN AGE ENCRYPTED FILE-----
        	YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCWDRGcDNtS3dYWVMrenNp
        	ckpCUGJTenV3SFZmN0xTdUNhSmU5Y25mWEhJCjFwZUxnRUhmbFRibkNVd3pGZ0xR
        	R2hlNEtHdlZydlJNMEtSN0d6OGExd28KLS0tIGZmYm5zcXFldnNta083YnVudFUr
        	VC9zWXlhMmUwWEgxenVuZUVGSXZJNGcK+5KshVHhA/4N8DlasHbUIQpPR4VNzNvs
        	erU9oO7AVJkRXl0keG6cNnKTPz7ekXJwOE6HsTnAzdwUg6wzOkhhbg==
        	-----END AGE ENCRYPTED FILE-----
	lastmodified: "2024-08-05T16:47:59Z"
	mac: ENC[AES256_GCM,data:9wWFJnF0SvcxNzkkQM+AyElRd7G6irPc54a34+jc4l0RW4h65PNImZ2KYm62MydM3zX8lPCzjfF9/2wpLciDkXRZ7CcVb/wqVSxYEMvQzu1+rElysAYStFLIJ5vk0fTZsYWn3YGMWZKzo7mAyqj4t1sQzPjcUw/AUIOZGVZO2xQ=,iv:CTLtGR/09gALoJsTMXARVOoabKMt6K3uHoSVAcqHpZI=,tag:68YdEdY7Jur9DWURunsXIQ==,type:str]
	pgp: []
	encrypted_regex: ^(data|stringData)$
	version: 3.7.3
---
sops:
	kms: []
	gcp_kms: []
	azure_kv: []
	hc_vault: []
	age:
    	- recipient: age1qfe60mzumfm65a34jj8fe340fe7crh2ukuxpx64t35kjl947qsuslsdrf5
      	enc: |
        	-----BEGIN AGE ENCRYPTED FILE-----
        	YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCWDRGcDNtS3dYWVMrenNp
        	ckpCUGJTenV3SFZmN0xTdUNhSmU5Y25mWEhJCjFwZUxnRUhmbFRibkNVd3pGZ0xR
        	R2hlNEtHdlZydlJNMEtSN0d6OGExd28KLS0tIGZmYm5zcXFldnNta083YnVudFUr
        	VC9zWXlhMmUwWEgxenVuZUVGSXZJNGcK+5KshVHhA/4N8DlasHbUIQpPR4VNzNvs
        	erU9oO7AVJkRXl0keG6cNnKTPz7ekXJwOE6HsTnAzdwUg6wzOkhhbg==
        	-----END AGE ENCRYPTED FILE-----
	lastmodified: "2024-08-05T16:47:59Z"
	mac: ENC[AES256_GCM,data:9wWFJnF0SvcxNzkkQM+AyElRd7G6irPc54a34+jc4l0RW4h65PNImZ2KYm62MydM3zX8lPCzjfF9/2wpLciDkXRZ7CcVb/wqVSxYEMvQzu1+rElysAYStFLIJ5vk0fTZsYWn3YGMWZKzo7mAyqj4t1sQzPjcUw/AUIOZGVZO2xQ=,iv:CTLtGR/09gALoJsTMXARVOoabKMt6K3uHoSVAcqHpZI=,tag:68YdEdY7Jur9DWURunsXIQ==,type:str]
	pgp: []
	encrypted_regex: ^(data|stringData)$
	version: 3.7.3

Завдяки цьому я все ще здатен визначити ім’я ConfigMap, її неймспейс та ключі, які в ній зберігаються. Це особливо зручно для пошуку тексту у репозиторії.

SOPS + AWS KMS

age може бути зручним коли тільки одна людина працює над проектом, тому що тільки один ключ може бути використаний для шифрування. І якщо ми почнемо передавати цей ключ усім розробникам, рано чи пізно побачимо наш же ключ у повідомленні від Github про закомічений секрет у репозиторій.

Також з часом може з’явитися необхідність розділити доступ до секретів, щоб, наприклад, тільки певні люди могли розшифрувати production-секрети.

З використанням AWS KMS це можливо налаштувати за допомогою IAM-політик. Щоб почати використовувати SOPS з попередньо створеним AWS KMS ключем, достатньо лише змінити відповідний creation_rule у .sops.yaml:

creation_rules:
  - path_regex: deploy/development/secrets/.*.yaml
	age: age1qfe60mzumfm65a34jj8fe340fe7crh2ukuxpx64t35kjl947qsuslsdrf5
	encrypted_regex: ^(data|stringData)$
  - path_regex: deploy/production/secrets/.*.yaml
	kms: arn:aws:kms:eu-central-1:123456789:key/b9a704a8-41b7-4efe-8804-756a8d9289f0
	encrypted_regex: ^(data|stringData)$

Тепер для шифрування секретів для production-оточення SOPS використовуватиме вказаний KMS-ключ:

20:18:23 ❯ sops -e -i ./deploy/production/secrets/configmap.yaml
20:22:16 ❯ cat ./deploy/production/secrets/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
	name: dou-production
	namespace: prod
data:
	secret_key: ENC[AES256_GCM,data:AAj0,iv:z3Hkx2SnYmGF24jovs9Vvu8aFQ8s7dVxscQ07b0x/ZE=,tag:cu0U/5/E9nFdV6JsqBBnHA==,type:str]
	access_key_id: ENC[AES256_GCM,data:tR0=,iv:M9J4dWuZ1aWPR9tt5ePvbC5Jl/MGDI+mfGBY9FFQkIo=,tag:kSW5Fn6Z0yix4f1vKsujWw==,type:str]
sops:
	kms:
    	- arn: arn:aws:kms:eu-central-1:123456789:key/b9a704a8-41b7-4efe-8804-756a8d9289f0
      	created_at: "2024-08-05T17:18:23Z"
      	enc: AQICAHgZMQAwa/aMuzPnXjzZIt4Zjx0ZP5me29gDFY9YerBw8AEM9qMO7t3ROi61YkOeTb45AAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMeW7+qCL55odIz6GnAgEQgDuQhymat8UgTnlbz2j9fUCR22randELIsE5Aj+OOJma7AA9F+nMVzitMvQMBrkD+ACe+H7XxrRkf1Xt9g==
      	aws_profile: ""
	gcp_kms: []
	azure_kv: []
	hc_vault: []
	age: []
	lastmodified: "2024-08-05T17:18:24Z"
	mac: ENC[AES256_GCM,data:QYiyO5qEkFsqTVAIToWb1olfRksgFeEFeT2veHJ7jErBQMD9QX788xCv866mu9g4sqaNHcT2e5oYYgCTU76+Y5qBjX6GMADxderSgOaBDtH4JXI6eYLWMXt6TI6RLdMI62MQSsxyLltKpRNKMiQjZgqqHV5Y3cLEPE0QD2aI37k=,iv:nl1ikxniAJKSRaUiOMA0nlhNNpcN3yTujCS7u+5Bfz4=,tag:AOsN0DIJjDe6F5QixkuVmg==,type:str]
	pgp: []
	encrypted_regex: ^(data|stringData)$
	version: 3.7.3

Зверніть увагу на aws_profile поле. Якщо під час шифрування передати аргумент —aws_profile, то вказаний профіль буде записаний у файл. Це означає, що CD-системи, які далі будуть розшифровувати цей файл, повинні мати налаштований профіль з таким самим іменем. Інакше розшифрування не спрацює. Якщо ж лишити це поле пустим, такої проблеми не буде.

Розшифровуємо у Kubernetes з FluxCD

З FluxCD задача розшифрувати зашифровані SOPS-секрети стає доволі тривіальною. Бо їх Kustomization CRD має у собі нативну підтримку SOPS:

А оскільки ми тепер використовуємо AWS, нам навіть не треба вказувати secretRef. Достатньо лише надати Kustomization-контролеру права на використання KMS ключа.

В EKS-кластері це робилося б увімкненням OIDC-провайдеру та навішуванням анотації на ServiceAccount, як це описано в документації Flux. Але у мене кластер знаходиться не в AWS, тож я додам ключі до контролера через Secret. Юзер, який буде використовуватись контролером, повинен мати наступну політику:

{
	"Version": "2012-10-17",
	"Statement": [
    	{
        	"Action": [
            	"kms:Decrypt",
            	"kms:DescribeKey"
        	],
        	"Effect": "Allow",
        	"Resource": "arn:aws:kms:eu-central-1:123456789:key/b9a704a8-41b7-4efe-8804-756a8d9289f0"
    	}
	]
}

Щоб передати ключі контролеру, я створив секрет ключами для доступу до AWS API:

21:56:46 ❯ kgsec -o yaml flux-aws-credentials
apiVersion: v1
data:
  AWS_ACCESS_KEY_ID: SSB3b25kZXIgaWYgYW55Ym9keSB3aWxsIHJlYWQgaXQK
  AWS_DEFAULT_REGION: VWtyYWluZQo=
  AWS_SECRET_ACCESS_KEY: T2YgY291cnNlIHRoZXkgd2lsbCwgdGhleSBrbm93IHNvbWUgc3R1ZmYK
kind: Secret
metadata:
  creationTimestamp: "2024-08-06T18:52:08Z"
  name: flux-aws-credentials
  namespace: flux-system
  resourceVersion: "127984097"
  uid: 53efa465-dd06-4020-96a8-f4413f280a2e
type: Opaque

Та додав до маніфесту kustomize-controller Deployment підтягування змін оточення з цього секрету:

Маніфест до kustmize-controller знаходиться у репозиторії, який ви використовували для бутстрапу FluxCD. Шлях у моєму випадку наступний: cluster/flux-system/gotk-components.yaml.

Нарешті настає момент для деплою у кластер. Мій головний Kustomization виглядає таким чином (непотрібні поля я приховав):

22:23:49 ❯ k get kustomizations.kustomize.toolkit.fluxcd.io dou-sops-article -o yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: dou-sops-article
  namespace: flux-system
spec:
  decryption:
	provider: sops
  force: false
  interval: 5m0s
  path: ./deploy/production/secrets
  prune: true
  sourceRef:
	kind: GitRepository
	name: dou-sops-article

GitRepository, який тут читається — репозиторій, де ми зберігаємо маніфести. Час створити та зашифрувати секрет:

22:26:41 ❯ k create secret generic the-most-precious-secret --from-literal ALPACKAS="are better than lamas" -o yaml --dry-run=client > deploy/production/secrets/dou-secret.yaml
22:26:49 ❯ sops -e -i ./deploy/production/secrets/dou-secret.yaml
22:26:57 ❯ cat deploy/production/secrets/dou-secret.yaml
apiVersion: v1
data:
	ALPACKAS: ENC[AES256_GCM,data:HtWuO3JI51g09Sb+HK0ue0cbFzKVoowkTAFk9g==,iv:GJRy4mo7xOGJ4ySeVk0pPfKkSDADvCwxg9Kbp3i+y9Q=,tag:YrSUxDyR5sPCe065AKRxWw==,type:str]
kind: Secret
metadata:
	creationTimestamp: null
	name: the-most-precious-secret
	namespace: default
sops:
	kms:
    	- arn: arn:aws:kms:eu-central-1:341505313297:key/b9a704a8-41b7-4efe-8804-756a8d9289f0
      	created_at: "2024-08-06T19:29:33Z"
      	enc: AQICAHgZMQAwa/aMuzPnXjzZIt4Zjx0ZP5me29gDFY9YerBw8AFqFlnFToFqbSP10mKet96lAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMh+zcZRbIEynz8EWlAgEQgDvglPhLvgM9h6UZnSusIZSZasqkUFFMMF2Eyk9TRxkOtU81pjvwRitVaLUpmfqyF4MhlDpvb0DCsZ8txA==
      	aws_profile: ""
	gcp_kms: []
	azure_kv: []
	hc_vault: []
	age: []
	lastmodified: "2024-08-06T19:29:33Z"
	mac: ENC[AES256_GCM,data:kaAOz8ACNSWu9EI7PMQThKl24QzsVjuy4Ml74o6YPA0H++MzDI9M2WLtOiCK2w3ydX6R40SDQzrGaa8kVuS+tdBmJRv9g4Utfq1Py5rSn3bmc6B9u2/jTSBaxhm21Usefw9L0EuOQo8MQ8Lvw2rSpRiigF0VFU9xrtRQs5wwgGc=,iv:rPPPdrnVee42tiljg3q1BaDjBamGm/d1uKGcgzeHwIk=,tag:7/sgQxR/QmGcyd0x6u7GMg==,type:str]
	pgp: []
	encrypted_regex: ^(data|stringData)$
	version: 3.7.3

Комітимо, пушимо зміни у репозиторій і біжимо оновлювати GitRepository
, щоб Flux почав створювати ресурси одразу ж. У результаті бачимо, що секрет був успішно розшифрований і створений:

22:33:16 ❯ k get secrets -n default the-most-precious-secret -o yaml
apiVersion: v1
data:
  ALPACKAS: YXJlIGJldHRlciB0aGFuIGxhbWFz
kind: Secret
metadata:
  creationTimestamp: "2024-08-06T19:31:38Z"
  labels:
	kustomize.toolkit.fluxcd.io/name: dou-sops-article
	kustomize.toolkit.fluxcd.io/namespace: flux-system
  name: the-most-precious-secret
  namespace: default
  resourceVersion: "127996944"
  uid: 88f2efb4-ec8e-4236-83a1-be77ec7cb7c3
type: Opaque

Пару слів про HelmRelase

Щоб почати шифрувати values-значення для HelmRelease, варто звернутись до того самого алгоритму. Необхідно лише замість values використовувати valuesFrom і, відповідно, зберігати їх у секреті, який вже своєю чергою буде шифруватися.

Ідеї на майбутнє

Маючи процес шифрування та розшифрування секретів, можемо тепер подумати над тим, як нам впевнитись, що хтось випадково не запушив секрет, забувши його зашифрувати.

Отже, слід продумати декілька етапів:

  • Локальна перевірка за допомогою git precommit хуків — існує для зручності розробників, щоб ті не чекали на виконання пайплайну. Для цього існує багато різноманітних інструментів: trufflehog, gitleaks, yelp/detect-secrets тощо.
  • Якщо ваша платформа дозволяє, наприклад, як Gitlab — увімкнути захист від пушу секретів, варто це зробити. Тому що git-хуки за бажанням можна деактивувати.
  • Наступною стадією має бути пайплайн, або ж вбудований у вашу платформу механізм сканування, який перевіряє репозиторій на наявність секретів. Знову ж таки, тому що хуки можна вимкнути.
  • Останній етап — рев’ю. Наприклад для виявлення зміненого пайплайну.

Також варто зазначити, що усе це тримається на правильному керуванні доступами. Інакше налаштування репозиторію можуть бути змінені, а майстер/мейн може бути форс-пушнутий.

Висновок

Сподіваюсь, якщо серед читачів буде людина, у якої секрети зберігаються у відкритому вигляді в репозиторії, ця стаття покаже: їх шифрування — доволі тривіальна задача, що вирішується швидко. Але одного дня це може врятувати вас від доволі неприємного досвіду.

Усі статті, обговорення, новини про DevOps — в одному місці. Підписуйтеся на DOU | DevOps!

👍ПодобаєтьсяСподобалось7
До обраногоВ обраному5
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Може краще external-secrets ? Й без lock-in’у на Flux’і ...

External Secrets справді гарний варіант якщо маєте вже інфраструктуру під це, будь то будь-який з cloud провайдерів і відповідний сервіс для зберігання секретів, чи Hashicorp Vault, якщо говорити за self-hosted оточення. Тут я більше розглядав сетап у якому робота з секретами максимально спрощена для розробників: вони лежать поряд з вашими файлами у тому самому репозиторії і для того щоб їх оновити потрібно лише викликати команду для розшифрування, оновити секрет і зашифрувати його. Так, це підійде не усім командам та організаціям.

Стосовно локу на Flux: в даному сетапі він був обраний через нативну підтримку роботи з sops. Якщо сильно захотіти то з Argo можна добитися такого самого результати, використавши Custom Management Plugin який на етапі init розшифрує усі секрети які маєте в репозиторії.

Корисна стаття, дякую :)

Підписатись на коментарі