Mozilla SOPS: GitOps için Git'te Secret Şifreleme
Git'te secret'ları şifrelemek için pratik Mozilla SOPS rehberi: age encryption, AWS CDK ve Lambda pattern'leri, production-ready serverless güvenlik.
Mozilla SOPS (Secrets OPerationS), her GitOps kurulumunun karşılaştığı tek bir soruya cevap veriyor: secret’ları yayınlamadan version control’de nasıl tutarsın? SOPS, YAML ve JSON dosyalarındaki value’ları şifreliyor, key’leri okunur bırakıyor; böylece dosya yapısı code review’da ayakta kalırken password’ler ve API key’leri ciphertext olarak duruyor. Secret’lar da onları okuyan kodla aynı pull request’ten geçiyor.
AWS CDK ile deploy eden serverless takımlar için işe yarayan varsayılan şu: development’ta age key’leri, production’da AWS KMS. Git’te şifrele, sonra her secret’ı synthesize edilen template’e gömülü bir string olarak değil, deploy zamanında çözülen bir referans olarak function’a ulaştır. Kendi takvimine göre rotate olan credential’lar (otomatik yenilenen database password’leri gibi) yine Secrets Manager veya Parameter Store’a ait.
GitOps’ta Secret Yönetimi Sorunu
Infrastructure as Code ile çalışırken hemen bir sorunla karşılaşıyorsun. Serverless configuration dosyalarını, Terraform variable’larını ve environment config’lerini version-control altına alman gerekiyor. Ama bu dosyalar database password’leri, API key’leri ve service credential’ları içeriyor. Secret’ları Git’e commit ettiğin anda bir security vulnerability yaratmış oluyorsun.
Geleneksel çözümler friction yaratıyor. HashiCorp Vault infrastructure çalıştırmayı ve deployment zamanında API call’ları gerektiriyor. AWS Secrets Manager secret başına ayda $0.40’a mal oluyor ve Lambda function’larına runtime API call’ları ekliyor. AWS Systems Manager Parameter Store ücretsiz ama yine de runtime fetching gerektiriyor. Her yaklaşım secret’ları GitOps workflow’undan çıkarıp external system’lere taşıyor.
Şifreli dosya repository’nin içinde durduğu için, bir API key’i değiştirmek ve onu okuyan Lambda function’ını güncellemek tek bir commit oluyor. Revert ikisini birlikte geri alıyor, Git history de hangi secret’ı kimin ne zaman değiştirdiğini kaydediyor.
SOPS Mimarisi
SOPS envelope encryption kullanıyor. Bir dosyayı encrypt ettiğinde, SOPS rastgele bir 256-bit data key oluşturuyor ve dosya içeriğini AES256-GCM ile encrypt ediyor. Sonra bu data key’i bir veya daha fazla master key (AWS KMS, age, PGP, GCP KMS veya Azure Key Vault) ile encrypt ediyor ve encrypted data key’i dosyanın metadata’sında saklıyor. Age, SOPS data key’ini X25519 + ChaCha20-Poly1305 kullanarak encrypt ediyor.
YAML ve JSON gibi structured format’lar için SOPS sadece value’ları encrypt ediyor, key’leri değil. Bu dosya yapısını code review için görünür tutuyor ve tool’ların value’lar encrypted olsa bile schema’yı parse etmesine izin veriyor.
Encrypted bir YAML dosyası şöyle görünüyor:
database:
host: ENC[AES256_GCM,data:Zm9vYmFy,iv:abc123,tag:def456,type:str]
port: ENC[AES256_GCM,data:NTQzMg==,iv:xyz789,tag:uvw012,type:int]
password: ENC[AES256_GCM,data:c3VwZXI=,iv:secret,tag:hash,type:str]
sops:
kms:
- arn: arn:aws:kms:us-east-1:123456789012:key/abc-123
created_at: '2025-12-17T10:00:00Z'
enc: AQICAHh...encrypted_data_key...
age:
- recipient: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBl...
-----END AGE ENCRYPTED FILE-----
version: 3.11.0
Yapı ayakta kalıyor: bir host, bir port, bir password field’ı; sadece değerler ciphertext’e dönüşüyor. Diff de değişen base64 duvarı yerine hangi field’ın değiştiğini söylüyor.
Kurulum ve Hazırlık
Installation platform’a göre değişiyor ama beş dakikadan az sürüyor:
# macOS with Homebrew
brew install sops
# Linux - en son release'i indir
wget https://github.com/getsops/sops/releases/download/v3.11.0/sops-v3.11.0.linux.amd64
chmod +x sops-v3.11.0.linux.amd64
sudo mv sops-v3.11.0.linux.amd64 /usr/local/bin/sops
# Installation'ı doğrula
sops --version
Container environment’lar için official image’i kullan:
FROM ghcr.io/getsops/sops:v3.11.0
# Encrypted dosyalarını kopyala
COPY secrets.enc.yaml /app/
# Runtime'da decrypt et
CMD ["sops", "--decrypt", "/app/secrets.enc.yaml"]
Age: Modern Encryption Seçimi
SOPS birden fazla key management system’i destekliyor. Team environment’ları için age, PGP’ye göre önerilen seçim haline geldi.
Age public key’i 62 karakter, private key’i 74 karakter. Public key Slack mesajına yapıştıracak kadar kısa; private key ise o recipient için şifrelenmiş her dosyayı çözüyor, yeri chat geçmişi değil bir password manager. Armor’lanmış bir PGP public key bloğu ise binlerce karakter tutuyor ve satırlara bölünüyor. Age’in yüzey alanı da çok daha dar: X25519 ve ChaCha20-Poly1305, GPG’nin on yıllar içinde biriktirdiği keyring makinesi yok.
Age key pair oluştur:
# Age'i install et (v1.2.0 veya üzeri)
brew install age # macOS
apt install age # Ubuntu
# Key pair oluştur
age-keygen -o ~/.config/sops/age/keys.txt
# Output public key'i gösteriyor
# Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
Private key ~/.config/sops/age/keys.txt dosyasında saklanıyor. Public key team member’larla paylaştığın ve SOPS’ta configure ettiğin şey.
Age ile dosya encrypt etmek için:
# Private key lokasyonunu ayarla
export SOPS_AGE_KEY_FILE=$HOME/.config/sops/age/keys.txt
# Public key kullanarak encrypt et
sops --age age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p \
--encrypt secrets.yaml > secrets.enc.yaml
# Decrypt et (SOPS_AGE_KEY_FILE'dan private key kullanır)
sops --decrypt secrets.enc.yaml > secrets.yaml
Team distribution için, herkes kendi age key’ini oluşturuyor ve public key’ini paylaşıyor. SOPS’u tüm team member’ların public key’leri ile encrypt edecek şekilde configure ediyorsun. Private key’i olan herkes decrypt edebilir.
.sops.yaml Yapılandırma Dosyası
Repository root’unda .sops.yaml dosyası oluşturmak manuel key management’ı ortadan kaldırıyor. SOPS bu dosyayı okuyarak dosya path’lerine göre hangi key’leri kullanacağını belirliyor.
İşte production-ready bir configuration:
creation_rules:
# Development - tüm developer'lar için basit age key'leri
- path_regex: \.dev\.yaml$
age: >-
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p,
age1cy0su9fwf8gzkdqh3r4r6xgc92fp8jqrjp4fvd4ak6vd3mc0jjpqnhymkw
# Staging - production flow'u test etmek için AWS KMS
- path_regex: \.staging\.yaml$
kms: arn:aws:kms:us-west-2:111111111111:key/staging-key-id
age: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
# Production - redundancy ile birden fazla KMS key
- path_regex: \.prod\.yaml$
key_groups:
- kms:
- arn: arn:aws:kms:us-east-1:222222222222:key/prod-key-id
role: arn:aws:iam::222222222222:role/sops-decrypt-role
- arn: arn:aws:kms:eu-west-1:222222222222:key/prod-key-eu
age:
- age1yx3z8r0hnzjy9wh6fq5gldq3p7hxg6nfkz5vgqcdqhsj8tqxj8xq8w6qur
# Serverless secret'ları - AWS KMS
- path_regex: serverless/.*\.yaml$
kms: arn:aws:kms:us-east-1:222222222222:key/serverless-key-id
# Terraform variable'ları - infrastructure için
- path_regex: terraform/.*\.tfvars$
kms: arn:aws:kms:us-east-1:222222222222:key/terraform-key-id
# Match olmayan dosyalar için default fallback
- age: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
Artık encryption otomatik hale geliyor:
# SOPS key'leri bulmak için .sops.yaml'i okuyor
sops --encrypt config.dev.yaml > config.dev.enc.yaml # Age key'leri kullanır
sops --encrypt config.staging.yaml > config.staging.enc.yaml # AWS KMS kullanır
sops --encrypt config.prod.yaml > config.prod.enc.yaml # KMS + age kullanır
Path-based rule’lar human error’u ortadan kaldırıyor. Developer’lar hangi environment için hangi key’leri kullanacaklarını hatırlamak zorunda kalmıyor.
AWS KMS Entegrasyonu
Production environment’lar için AWS KMS, IAM-based access control ve CloudTrail üzerinden audit logging ile centralized key management sağlıyor.
KMS key oluştur:
# Production için SOPS encryption key'i oluştur
aws kms create-key \
--description "SOPS encryption key for production" \
--key-usage ENCRYPT_DECRYPT
# Daha kolay referans için alias oluştur
aws kms create-alias \
--alias-name alias/sops-production \
--target-key-id <key-id-from-previous-command>
# Key ARN'i al
aws kms describe-key --key-id alias/sops-production
SOPS’u KMS kullanacak şekilde configure et:
export SOPS_KMS_ARN="arn:aws:kms:us-east-1:123456789012:key/abc-123-def"
# Dosyayı encrypt et
sops --kms $SOPS_KMS_ARN --encrypt secrets.yaml > secrets.enc.yaml
CI/CD environment’lar için credential’ları saklamak yerine IAM role’leri kullan:
# OIDC ile GitHub Actions
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsSOPS
aws-region: us-east-1
- name: KMS ile Decrypt Et
run: sops --decrypt secrets.enc.yaml > secrets.yaml
IAM role KMS decrypt permission’larına ihtiyaç duyuyor:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"kms:Decrypt",
"kms:DescribeKey"
],
"Resource": "arn:aws:kms:us-east-1:123456789012:key/abc-123"
}]
}
Multi-Account AWS Kurulumu
Enterprise environment’lar nadiren tek bir AWS account içinde çalışıyor. Development, staging ve production environment’lar security isolation ve blast radius containment için ayrı account’larda çalışıyor. SOPS bu architecture’ı environment-specific KMS key’leri ve cross-account IAM permission’ları ile destekliyor.
Mimariye Genel Bakış
Tipik bir multi-account setup, environment’ları dedicated KMS key’leri olan farklı AWS account’larına ayırıyor. Bu developer’ların production secret’lara yanlışlıkla erişmesini önlüyor ve net security boundary’leri sağlıyor.
Bu architecture birkaç security principle’ı enforce ediyor. Developer’ların her environment’a erişmek için explicit cross-account role assumption’a ihtiyacı var. KMS key’leri account-local, yani bir environment’ın compromise olması diğerlerini expose etmiyor. Her account’taki CloudTrail log’ları bağımsız audit trail’ler sağlıyor.
Environment Account Başına KMS Key
Her AWS account kendi KMS key’ini maintain ediyor. .sops.yaml configuration dosya path’lerini account-specific KMS key’lere map ediyor.
# .sops.yaml - Multi-account configuration
creation_rules:
# Development account secret'ları
- path_regex: secrets/dev/.*\.yaml$
kms: arn:aws:kms:eu-central-1:111111111111:key/aaaaaaaa-dev-1111-1111-111111111111
# Staging account secret'ları
- path_regex: secrets/staging/.*\.yaml$
kms: arn:aws:kms:eu-central-1:222222222222:key/bbbbbbbb-stg-2222-2222-222222222222
# Production account secret'ları
- path_regex: secrets/prod/.*\.yaml$
kms: arn:aws:kms:eu-central-1:333333333333:key/cccccccc-prd-3333-3333-333333333333
# Development için age ile fallback
- age: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
Directory yapısı account separation’ı yansıtıyor:
secrets/
├── dev/
│ ├── database.yaml # Dev account KMS ile encrypted
│ └── api-keys.yaml
├── staging/
│ ├── database.yaml # Staging account KMS ile encrypted
│ └── api-keys.yaml
└── prod/
├── database.yaml # Prod account KMS ile encrypted
└── api-keys.yaml
secrets/prod/ içinde bir dosyayı encrypt ettiğinde, SOPS otomatik olarak production account KMS key’ini kullanıyor. Manuel key selection gerekmez.
Cross-Account KMS Erişimi
CI/CD pipeline’larının account’lar arası secret’ları decrypt edebilmesi için KMS key policy’leri cross-account access’e izin vermeli. Bu hem KMS key policy’sinde hem IAM role permission’larında configuration gerektiriyor.
Production account’taki (333333333333) KMS key policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Enable IAM User Permissions",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::333333333333:root"
},
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "Allow CI/CD account to decrypt",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::444444444444:role/GitHubActionsDeployRole"
},
"Action": [
"kms:Decrypt",
"kms:DescribeKey"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"kms:ViaService": [
"secretsmanager.eu-central-1.amazonaws.com",
"lambda.eu-central-1.amazonaws.com"
]
}
}
}
]
}
Condition KMS kullanımını specific AWS service’leri ile kısıtlıyor, legitimate deployment context’leri dışında direct key access’i önlüyor.
CI/CD account’taki (444444444444) IAM role:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"kms:Decrypt",
"kms:DescribeKey"
],
"Resource": [
"arn:aws:kms:eu-central-1:111111111111:key/aaaaaaaa-dev-1111-1111-111111111111",
"arn:aws:kms:eu-central-1:222222222222:key/bbbbbbbb-stg-2222-2222-222222222222",
"arn:aws:kms:eu-central-1:333333333333:key/cccccccc-prd-3333-3333-333333333333"
]
},
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": [
"arn:aws:iam::111111111111:role/LambdaDeployRole",
"arn:aws:iam::222222222222:role/LambdaDeployRole",
"arn:aws:iam::333333333333:role/LambdaDeployRole"
]
}
]
}
Bu IAM policy CI/CD role’ünün hem KMS key’leri ile decrypt etmesine hem de target account’larda deployment role’leri assume etmesine izin veriyor.
Role Assumption ile CI/CD
GitHub Actions workflow’ları farklı environment’lar için farklı role’leri assume ediyor. AWS credentials action multi-account deployment’lar için role chaining’i destekliyor.
name: Multi-Account Lambda Deploy
on:
push:
branches: [main]
jobs:
deploy-dev:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Dev için AWS Credential'larini Configure Et
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::111111111111:role/GitHubActionsDeployRole
aws-region: eu-central-1
- name: SOPS Install Et
run: |
wget -q https://github.com/getsops/sops/releases/download/v3.11.0/sops-v3.11.0.linux.amd64
chmod +x sops-v3.11.0.linux.amd64
sudo mv sops-v3.11.0.linux.amd64 /usr/local/bin/sops
- name: Dev Secret'lari Decrypt Et
run: sops --decrypt secrets/dev/database.yaml > /tmp/secrets.yaml
- name: Dev Lambda'ya Deploy Et
run: |
# Deployment command'ları /tmp/secrets.yaml kullanır
serverless deploy --stage dev
deploy-prod:
runs-on: ubuntu-latest
needs: [deploy-dev]
environment: production
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Prod için AWS Credential'larini Configure Et
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::333333333333:role/GitHubActionsDeployRole
aws-region: eu-central-1
- name: SOPS Install Et
run: |
wget -q https://github.com/getsops/sops/releases/download/v3.11.0/sops-v3.11.0.linux.amd64
chmod +x sops-v3.11.0.linux.amd64
sudo mv sops-v3.11.0.linux.amd64 /usr/local/bin/sops
- name: Prod Secret'lari Decrypt Et
run: sops --decrypt secrets/prod/database.yaml > /tmp/secrets.yaml
- name: Prod Lambda'ya Deploy Et
run: |
# Deployment command'ları /tmp/secrets.yaml kullanır
serverless deploy --stage prod
Workflow deployment job’larını environment’a göre ayırıyor. Her job secret’ları decrypt etmeden önce uygun account role’ünü assume ediyor. needs dependency dev’in production’dan önce deploy olmasını garantiliyor. environment: production manuel approval gate’leri ekliyor.
AWS Profile’ları ile Geliştirici Akışı
Local çalışan developer’ların her account için AWS profile configuration’larına ihtiyacı var. ~/.aws/config dosyası role assumption chain’lerini define ediyor.
# ~/.aws/config
[profile dev]
role_arn = arn:aws:iam::111111111111:role/DeveloperRole
source_profile = default
region = eu-central-1
output = json
[profile staging]
role_arn = arn:aws:iam::222222222222:role/DeveloperRole
source_profile = default
region = eu-central-1
output = json
[profile prod]
role_arn = arn:aws:iam::333333333333:role/DeveloperRole
source_profile = default
region = eu-central-1
output = json
mfa_serial = arn:aws:iam::444444444444:mfa/ayhan.sipahi
Production access MFA gerektiriyor. Developer production secret’ları için SOPS command’ları çalıştırdığında, AWS MFA token soruyor.
Farklı environment’lar için secret’ları encrypt etmek:
# Dev secret'ı encrypt et
AWS_PROFILE=dev sops --encrypt secrets/dev/database.yaml > secrets/dev/database.enc.yaml
# Staging secret'ı encrypt et
AWS_PROFILE=staging sops --encrypt secrets/staging/database.yaml > secrets/staging/database.enc.yaml
# Prod secret'ı encrypt et (MFA sorar)
AWS_PROFILE=prod sops --encrypt secrets/prod/database.yaml > secrets/prod/database.enc.yaml
Local test için decrypt etmek:
# Dev secret'larını locally decrypt et
AWS_PROFILE=dev sops --decrypt secrets/dev/database.enc.yaml > .env.dev
# Staging'i decrypt et (staging role ile)
AWS_PROFILE=staging sops --decrypt secrets/staging/database.enc.yaml > .env.staging
Profile seçimi AWS_PROFILE environment variable üzerinden gerçekleşiyor. SOPS dosya path’ine göre otomatik olarak doğru KMS key’i kullanıyor ve aktif profile’a göre uygun role’ü assume ediyor.
Güvenlik Gereksinimleri
Multi-account SOPS deployment’ları IAM policy’leri ve organizational control’ler ile enforce edilmesi gereken birkaç security requirement getiriyor.
Principle of Least Privilege: Developer’lar sadece aktif olarak çalıştıkları environment’lara erişmeli. Development environment’larında çalışan junior bir developer production KMS decrypt permission’larına sahip olmamalı. Role policy’leri bu segregation’ı yansıtmalı.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"kms:Decrypt",
"kms:DescribeKey"
],
"Resource": [
"arn:aws:kms:eu-central-1:111111111111:key/aaaaaaaa-dev-1111-1111-111111111111"
]
},
{
"Effect": "Deny",
"Action": [
"kms:Decrypt",
"kms:DescribeKey"
],
"Resource": [
"arn:aws:kms:eu-central-1:333333333333:key/cccccccc-prd-3333-3333-333333333333"
]
}
]
}
Explicit deny higher-level policy’ler access grant etse bile bu developer’ın production secret’larını decrypt edemeyeceğini garantiliyor.
CloudTrail ile Audit Logging: Her AWS account CloudTrail enabled olmalı ve log’lar centralized bir security account’a ship edilmeli. Bu tüm KMS operation’larının immutable audit trail’ini oluşturuyor.
{
"eventVersion": "1.08",
"userIdentity": {
"type": "AssumedRole",
"principalId": "AROAEXAMPLE:ayhan.sipahi",
"arn": "arn:aws:sts::333333333333:assumed-role/DeveloperRole/ayhan.sipahi"
},
"eventTime": "2025-12-17T10:30:00Z",
"eventSource": "kms.amazonaws.com",
"eventName": "Decrypt",
"requestParameters": {
"keyId": "arn:aws:kms:eu-central-1:333333333333:key/cccccccc-prd-3333-3333-333333333333"
},
"responseElements": null,
"requestID": "abc-123-def-456",
"resources": [{
"accountId": "333333333333",
"type": "AWS::KMS::Key",
"ARN": "arn:aws:kms:eu-central-1:333333333333:key/cccccccc-prd-3333-3333-333333333333"
}]
}
CloudTrail log’ları kimin hangi KMS key’e ne zaman eriştiğini gösteriyor. Bu unauthorized access attempt’lerin tespitini veya compliance audit’leri mümkün kılıyor.
Key Policy’ler vs IAM Policy’ler: Defense in depth için her ikisini de kullan. KMS key policy’leri resource level’da key’i kimin kullanabileceğini define ediyor. IAM policy’leri identity’nin ne yapabileceğini define ediyor. Operation başarılı olması için her ikisi de allow etmeli.
Production KMS key restrictive bir key policy’ye sahip olmalı ki sadece specific deployment role’lere izin versin, başka yerde daha geniş IAM policy’ler olsa bile. Bu IAM policy değişiklikleri ile privilege escalation’ı tek başına önlüyor.
Break-Glass Procedure’ları: MFA ve restrictive policy’lerle bile emergency’ler rapid production access gerektiriyor. Kullanıldığında otomatik alert’le time-limited credential’lara sahip emergency access role maintain et.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::333333333333:role/EmergencyBreakGlassRole"
},
"Action": [
"kms:Decrypt",
"kms:DescribeKey"
],
"Resource": "*",
"Condition": {
"DateGreaterThan": {
"aws:CurrentTime": "2025-12-17T00:00:00Z"
},
"DateLessThan": {
"aws:CurrentTime": "2025-12-18T00:00:00Z"
}
}
}
]
}
Bu policy emergency access’e izin veriyor ama sadece bir time window içinde. Role assume edildiğinde CloudWatch Events security team’lere ve management’a alert’ler trigger ediyor.
SOPS ile AWS Lambda Entegrasyonu
Lambda function’lar runtime’da secret’lara ihtiyaç duyuyor, ama bu secret’ları function kodunla birlikte version-control altında tutmak istiyorsun. SOPS bunu runtime’da değil deployment sırasında secret’ları decrypt ederek mümkün kılıyor.
AWS CDK Entegrasyonu
AWS CDK, SOPS ile en zarif entegrasyonu sunuyor. Synthesis şifreli dosyayı secret olmayan konfigürasyon için okuyor; secret değerler ise Lambda’ya bir Secrets Manager referansıyla ya da deploy zamanında çalışan bir custom resource üzerinden ulaşıyor. Bir construct property’sine verdiğin decrypt edilmiş string synthesize edilen template’e yazılıyor ve orada cdk.out, CI artifact’leri ve deployment log’ları içinde yaşamaya devam ediyor. Bu yaklaşım GitOps pratiklerini korurken infrastructure kodunu temiz tutuyor.
Encrypted secret’lar oluştur:
# secrets/prod.enc.yaml
database:
host: prod-db.example.com
username: admin
password: super_secret_password
stripe:
secret_key: sk_live_abc123
webhook_secret: whsec_xyz789
redis:
host: prod-redis.example.com
port: 6379
Dosyayı encrypt et:
sops --encrypt secrets/prod.yaml > secrets/prod.enc.yaml
Pattern 1: Direct Environment Variable Injection
En basit pattern değerleri doğrudan Lambda environment variable’larına inject ediyor. Secret olmayan konfigürasyon SOPS dosyasından synthesis time’da gelebiliyor. Secret değerler ise CloudFormation’ın function’ı güncellerken çözdüğü bir {{resolve:secretsmanager:...}} referansı olarak geliyor:
// lib/lambda-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
import { execSync } from 'child_process';
import * as yaml from 'js-yaml';
export class LambdaStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Synthesis time'da SOPS dosyasını yalnızca secret olmayan konfigürasyon için decrypt et
const decrypted = execSync('sops --decrypt secrets/prod.enc.yaml', {
encoding: 'utf-8'
});
const config = yaml.load(decrypted) as any;
// Secret değerler Secrets Manager'da duruyor, aynı SOPS dosyasından populate ediliyor
const dbSecret = secretsmanager.Secret.fromSecretNameV2(this, 'DbSecret', 'prod/database');
const stripeSecret = secretsmanager.Secret.fromSecretNameV2(this, 'StripeSecret', 'prod/stripe');
new lambda.Function(this, 'ApiFunction', {
runtime: lambda.Runtime.NODEJS_20_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('src'),
environment: {
DB_HOST: config.database.host,
DB_USERNAME: config.database.username,
REDIS_HOST: config.redis.host,
DB_PASSWORD: dbSecret.secretValueFromJson('password').unsafeUnwrap(),
STRIPE_SECRET_KEY: stripeSecret.secretValueFromJson('secret_key').unsafeUnwrap(),
},
});
}
}
prod/database ve prod/stripe secret’larını deploy adımında şifreli dosyadan doldur: sops --decrypt --output-type json çıktısı aws secretsmanager put-secret-value’ya besleniyor. config.database.password’ü doğrudan environment girdisine atarsan password synthesize edilen template’e yazılır, oradan da cdk.out’a, CI cache’lerine ve deployment log’larına geçer.
Pattern 2: SSM Parameter Store Population
Daha esnek bir pattern SOPS’u SSM Parameter Store’u populate etmek için kullanıyor, sonra Lambda’da parameter’ları reference ediyor:
// lib/lambda-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as ssm from 'aws-cdk-lib/aws-ssm';
import { execSync } from 'child_process';
import * as yaml from 'js-yaml';
export class LambdaStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const decrypted = execSync('sops --decrypt secrets/prod.enc.yaml', {
encoding: 'utf-8'
});
const secrets = yaml.load(decrypted) as any;
// Secret'ları SSM Parameter Store'da sakla
const dbPassword = new ssm.StringParameter(this, 'DbPassword', {
parameterName: '/prod/database/password',
stringValue: secrets.database.password,
tier: ssm.ParameterTier.ADVANCED,
description: 'Production database password',
});
const stripeKey = new ssm.StringParameter(this, 'StripeKey', {
parameterName: '/prod/stripe/secret_key',
stringValue: secrets.stripe.secret_key,
tier: ssm.ParameterTier.ADVANCED,
});
// SSM parameter'larını reference eden Lambda oluştur
const fn = new lambda.Function(this, 'ApiFunction', {
runtime: lambda.Runtime.NODEJS_20_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('src'),
environment: {
DB_HOST: secrets.database.host,
DB_PASSWORD_PARAM: dbPassword.parameterName,
STRIPE_KEY_PARAM: stripeKey.parameterName,
},
});
// Read permission'larını grant et
dbPassword.grantRead(fn);
stripeKey.grantRead(fn);
}
}
Bir uyarı: CloudFormation SecureString parameter oluşturamıyor, bu yüzden ssm.StringParameter değeri düz String olarak yazıyor; decrypt edilmiş değer ayrıca synthesize edilen template’e de düşüyor. Değerin cdk.out’a girmemesi ve Parameter Store’da şifreli durması gerekiyorsa parameter’ı stack dışında oluştur ya da SSM API’sini doğrudan çağıran bir construct kullan.
Lambda kodun parameter’ları runtime’da fetch ediyor:
import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm';
const ssm = new SSMClient({});
async function getSecret(paramName: string): Promise<string> {
const response = await ssm.send(
new GetParameterCommand({
Name: paramName,
WithDecryption: true,
})
);
return response.Parameter!.Value!;
}
export async function handler() {
const dbPassword = await getSecret(process.env.DB_PASSWORD_PARAM!);
const stripeKey = await getSecret(process.env.STRIPE_KEY_PARAM!);
// Secret'ları kullan...
}
Pattern 3: cdk-sops-secrets Construct Kullanmak
cdk-sops-secrets npm paketi daha yüksek seviyeli bir construct sağlıyor:
npm install cdk-sops-secrets
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { SopsSecret } from 'cdk-sops-secrets';
export class LambdaStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// SOPS secret'larını CDK construct olarak yükle; master key dosyanın
// içindeki sops metadata'sından geliyor
const secrets = new SopsSecret(this, 'Secrets', {
sopsFilePath: 'secrets/prod.enc.yaml',
});
const fn = new lambda.Function(this, 'ApiFunction', {
runtime: lambda.Runtime.NODEJS_20_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('src'),
environment: {
DB_HOST: secrets.secretValueFromJson('database.host').unsafeUnwrap(),
DB_PASSWORD: secrets.secretValueFromJson('database.password').unsafeUnwrap(),
STRIPE_SECRET_KEY: secrets.secretValueFromJson('stripe.secret_key').unsafeUnwrap(),
},
});
}
}
unsafeUnwrap() plaintext yerine {{resolve:secretsmanager:...}} dinamik referansını üretiyor, yani synthesize edilen template temiz kalıyor. Çözülen değer ise diğer environment variable’lar gibi Lambda konsolunda okunabilir kalıyor.
Pattern 4: Multi-Stack Secret Sharing
Karmaşık uygulamalar için SOPS secret’larını birden fazla stack arasında paylaş:
// lib/secrets-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as ssm from 'aws-cdk-lib/aws-ssm';
import { execSync } from 'child_process';
import * as yaml from 'js-yaml';
export class SecretsStack extends cdk.Stack {
public readonly dbPasswordParam: ssm.IStringParameter;
public readonly stripeKeyParam: ssm.IStringParameter;
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const decrypted = execSync('sops --decrypt secrets/prod.enc.yaml', {
encoding: 'utf-8'
});
const secrets = yaml.load(decrypted) as any;
this.dbPasswordParam = new ssm.StringParameter(this, 'DbPassword', {
parameterName: '/prod/database/password',
stringValue: secrets.database.password,
});
this.stripeKeyParam = new ssm.StringParameter(this, 'StripeKey', {
parameterName: '/prod/stripe/secret_key',
stringValue: secrets.stripe.secret_key,
});
}
}
// lib/api-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
interface ApiStackProps extends cdk.StackProps {
dbPasswordParam: cdk.aws_ssm.IStringParameter;
stripeKeyParam: cdk.aws_ssm.IStringParameter;
}
export class ApiStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props: ApiStackProps) {
super(scope, id, props);
const fn = new lambda.Function(this, 'ApiFunction', {
runtime: lambda.Runtime.NODEJS_20_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('src'),
environment: {
DB_PASSWORD_PARAM: props.dbPasswordParam.parameterName,
STRIPE_KEY_PARAM: props.stripeKeyParam.parameterName,
},
});
props.dbPasswordParam.grantRead(fn);
props.stripeKeyParam.grantRead(fn);
}
}
// bin/app.ts
import * as cdk from 'aws-cdk-lib';
import { SecretsStack } from '../lib/secrets-stack';
import { ApiStack } from '../lib/api-stack';
const app = new cdk.App();
const secretsStack = new SecretsStack(app, 'SecretsStack');
new ApiStack(app, 'ApiStack', {
dbPasswordParam: secretsStack.dbPasswordParam,
stripeKeyParam: secretsStack.stripeKeyParam,
});
Synthesize edip deploy et:
# CDK synthesis sırasında SOPS'u decrypt ediyor
cdk synth
cdk deploy --all
AWS SAM Entegrasyonu
AWS SAM kullanan team’ler için function template, secret’ları deployment sırasında resolve:secretsmanager dinamik referansıyla çözüyor. Şifreli kaynağın sahibi SOPS, populate edilmiş değerleri Secrets Manager tutuyor, SAM ise bunları environment variable olarak enjekte ediyor:
# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Globals:
Function:
Runtime: python3.12
Timeout: 30
Resources:
ApiFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: app.handler
Environment:
Variables:
DB_HOST: !Sub '{{resolve:secretsmanager:prod/database:SecretString:host}}'
DB_PASSWORD: !Sub '{{resolve:secretsmanager:prod/database:SecretString:password}}'
Template çalışmadan önce SOPS dosyasını decrypt edip Secrets Manager’ı, anahtarları template referanslarıyla eşleşen bir JSON secret ile populate et, sonra deploy et:
# SOPS JSON'a decrypt eder; resolve:secretsmanager referanslarındaki anahtarlarla
# (host, password) eşleşmesi için database nesnesini ayıkla
DB_SECRET=$(sops --decrypt --output-type json secrets/prod.enc.yaml | jq -c '.database')
aws secretsmanager create-secret \
--name prod/database \
--secret-string "$DB_SECRET"
# Sonra SAM'i deploy et
sam deploy --stack-name my-api --capabilities CAPABILITY_IAM
Yerel Geliştirme Akışı
CDK ile local test için secret’ları geçici olarak decrypt et:
# Local development için decrypt et
sops --decrypt secrets/dev.enc.yaml > .env.local
# Decrypt edilmiş secret'larla locally çalıştır
npm run dev
# Temizle
rm .env.local
Veya command’ları decrypt edilmiş environment ile çalıştırmak için SOPS exec mode kullan:
sops exec-env secrets/dev.enc.yaml 'npm run dev'
CDK synthesis için locally, SOPS otomatik decrypt ediyor:
# SOPS decryption ile CDK synth
cdk synth
# Belirli bir stack'i deploy et
cdk deploy ApiStack
SSM Parameter Store vs SOPS Karşılaştırması
SOPS kullan:
- Secret’lar code deployment’larla değişiyor
- Git-based audit trail istiyorsun
- Secret’lar static (API key’leri, OAuth credential’lar)
- Team collaboration önemli
- Maliyet optimizasyonu priority
SSM Parameter Store kullan:
- Secret’lar deployment’lardan bağımsız rotate oluyor
- Birden fazla servis aynı secret’ları paylaşıyor
- AWS-native secret rotation lazım
- Redeployment olmadan runtime secret update’leri
- Cross-region secret replication gerekiyor
Hybrid yaklaşım:
// CDK: Bazı secret'lar SOPS'tan, diğerleri SSM'den
const sopsSecrets = loadSopsSecrets('secrets/prod.enc.yaml');
new lambda.Function(this, 'ApiFunction', {
environment: {
// SOPS'tan static secret'lar
STRIPE_PUBLIC_KEY: sopsSecrets.stripe.public_key,
OAUTH_CLIENT_ID: sopsSecrets.oauth.client_id,
// SSM'den dynamic secret'lar
DB_PASSWORD: cdk.aws_ssm.StringParameter.valueForStringParameter(
this, '/prod/database/password'
),
},
});
Terraform Entegrasyonu
Terraform SOPS provider, state file’ını temiz tutarken encrypted variable dosyalarını okumayı mümkün kılıyor.
Provider’ı configure et:
terraform {
required_providers {
sops = {
source = "carlpett/sops"
version = "~> 1.3"
}
}
}
provider "sops" {}
Encrypted variable dosyası oluştur:
# secrets.enc.yaml
database:
username: postgres_admin
password: super_secret_password
host: prod-db.example.com
port: 5432
aws:
access_key: AKIAIOSFODNN7EXAMPLE
secret_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Encrypt et:
sops --encrypt secrets.yaml > secrets.enc.yaml
git add secrets.enc.yaml
Terraform’da referans et:
data "sops_file" "secrets" {
source_file = "secrets.enc.yaml"
}
resource "aws_db_instance" "main" {
identifier = "production-db"
engine = "postgres"
instance_class = "db.t3.medium"
username = data.sops_file.secrets.data["database.username"]
password = data.sops_file.secrets.data["database.password"]
lifecycle {
ignore_changes = [password]
}
}
output "database_endpoint" {
value = aws_db_instance.main.endpoint
sensitive = true
}
Password hiçbir zaman Terraform state dosyanda plaintext olarak görünmüyor çünkü ignore_changes kullanıyoruz. İlk creation için SOPS değeri decrypt ediyor. Sonraki apply’larda Terraform password değişikliklerini ignore ediyor.
Daha iyi bir pattern SOPS’u AWS Secrets Manager’ı populate etmek için kullanıp sonra secret ARN’ini reference etmek:
resource "aws_secretsmanager_secret" "db_password" {
name = "prod/database/password"
}
resource "aws_secretsmanager_secret_version" "db_password" {
secret_id = aws_secretsmanager_secret.db_password.id
secret_string = data.sops_file.secrets.data["database.password"]
}
resource "aws_ecs_task_definition" "app" {
container_definitions = jsonencode([{
secrets = [{
name = "DB_PASSWORD"
valueFrom = aws_secretsmanager_secret.db_password.arn
}]
}])
}
Artık application runtime’da secret’ları Secrets Manager’dan fetch ediyor, ama initial secret değerleri SOPS ile version-controlled.
AWS CDK için CI/CD Entegrasyon Pattern’leri
CDK ile GitHub Actions
CDK deployment’ları için SOPS ile önerilen pattern. Synthesis şifreli dosyayı okuyor, secret referanslarını ise CloudFormation deploy sırasında çözüyor:
name: SOPS ile CDK Lambda Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: AWS Credential'larini Configure Et
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsCDK
aws-region: us-east-1
- name: SOPS Install Et
run: |
wget -q https://github.com/getsops/sops/releases/download/v3.11.0/sops-v3.11.0.linux.amd64
chmod +x sops-v3.11.0.linux.amd64
sudo mv sops-v3.11.0.linux.amd64 /usr/local/bin/sops
- name: Node.js Setup
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Dependency'leri Install Et
run: npm ci
- name: CDK Synth
run: npx cdk synth
- name: CDK Deploy
run: npx cdk deploy --all --require-approval never
CDK kodu synthesis sırasında SOPS dosyasını okuyor, bu yüzden CI/CD’de explicit decrypt step’i gerekmiyor.
IAM role CDK deployment permission’larına, KMS decrypt’e ve referans verilen secret’ları doldurup çözmek için Secrets Manager erişimine ihtiyaç duyuyor:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"kms:Decrypt",
"kms:DescribeKey"
],
"Resource": "arn:aws:kms:us-east-1:123456789012:key/abc-123"
},
{
"Effect": "Allow",
"Action": [
"cloudformation:*",
"lambda:*",
"iam:*",
"s3:*",
"ssm:*",
"secretsmanager:*"
],
"Resource": "*"
}
]
}
Multi-Environment CDK Deployment
Farklı environment’lar için environment-specific secret’larla deploy et:
name: Multi-Environment CDK Deploy
on:
push:
branches: [main, develop]
jobs:
deploy-dev:
if: github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: AWS Credential'larini Configure Et
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::111111111111:role/GitHubActionsCDK
aws-region: us-east-1
- name: SOPS Install Et
run: |
wget -q https://github.com/getsops/sops/releases/download/v3.11.0/sops-v3.11.0.linux.amd64
chmod +x sops-v3.11.0.linux.amd64
sudo mv sops-v3.11.0.linux.amd64 /usr/local/bin/sops
- name: Node.js Setup
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Dependency'leri Install Et
run: npm ci
- name: CDK Deploy Dev
run: npx cdk deploy DevStack --require-approval never
deploy-prod:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: AWS Credential'larini Configure Et
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::222222222222:role/GitHubActionsCDK
aws-region: us-east-1
- name: SOPS Install Et
run: |
wget -q https://github.com/getsops/sops/releases/download/v3.11.0/sops-v3.11.0.linux.amd64
chmod +x sops-v3.11.0.linux.amd64
sudo mv sops-v3.11.0.linux.amd64 /usr/local/bin/sops
- name: Node.js Setup
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Dependency'leri Install Et
run: npm ci
- name: CDK Deploy Prod
run: npx cdk deploy ProdStack --require-approval never
AWS SAM ile GitHub Actions
AWS SAM kullanan team’ler için:
name: SOPS ile SAM Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: AWS Credential'larini Configure Et
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsSAM
aws-region: us-east-1
- name: SOPS Install Et
run: |
wget -q https://github.com/getsops/sops/releases/download/v3.11.0/sops-v3.11.0.linux.amd64
chmod +x sops-v3.11.0.linux.amd64
sudo mv sops-v3.11.0.linux.amd64 /usr/local/bin/sops
- name: SAM CLI Setup
uses: aws-actions/setup-sam@v2
- name: Decrypt edip Secrets Manager'a populate et
run: |
sops exec-file secrets/prod.enc.yaml \
'aws secretsmanager put-secret-value \
--secret-id prod/app-secrets \
--secret-string file://{}'
- name: SAM Build & Deploy
run: |
sam build
sam deploy --stack-name my-lambda-app --no-confirm-changeset
GitLab CI
GitLab CI benzer pattern’ler kullanıyor:
variables:
SOPS_VERSION: "3.11.0"
stages:
- decrypt
- deploy
decrypt-secrets:
stage: decrypt
image: alpine:latest
before_script:
- apk add --no-cache wget
- wget -q https://github.com/getsops/sops/releases/download/v${SOPS_VERSION}/sops-v${SOPS_VERSION}.linux.amd64
- chmod +x sops-v${SOPS_VERSION}.linux.amd64
- mv sops-v${SOPS_VERSION}.linux.amd64 /usr/local/bin/sops
script:
- mkdir -p ~/.config/sops/age
- echo "$SOPS_AGE_KEY" > ~/.config/sops/age/keys.txt
- chmod 600 ~/.config/sops/age/keys.txt
- sops --decrypt secrets.enc.yaml > secrets.yaml
artifacts:
paths:
- secrets.yaml
expire_in: 10 minutes
Decrypt edilmiş secret’lar artifact’i sonraki stage’lerde available ama 10 dakika sonra expire oluyor.
Developer Experience ve IDE Entegrasyonu
Encrypted dosyaları manuel olarak edit etmek zor olurdu. SOPS encryption’ı transparent şekilde handle eden bir edit mode sağlıyor.
Editor’ünü ayarla:
export EDITOR="code --wait" # VS Code
# veya
export EDITOR="vim" # Vim
Encrypted dosyayı edit et:
sops secrets.enc.yaml
SOPS dosyayı decrypt ediyor, editor’ünde açıyor, save edip kapatmanı bekliyor, sonra updated value’larla re-encrypt ediyor. Edit sırasında hiçbir zaman encrypted content’i görmüyorsun.
VS Code için SOPS extension’ı install et:
// .vscode/settings.json
{
"sops.enable": true,
"sops.defaults": {
"age": "age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p"
},
"files.associations": {
"*.enc.yaml": "yaml",
"*.enc.json": "json"
}
}
Extension dosyaları VS Code’da açtığında otomatik decrypt ediyor ve save’de re-encrypt ediyor.
Anlamlı Git diff’leri için custom differ configure et:
# .gitattributes
*.enc.yaml diff=sopsdiffer
*.enc.json diff=sopsdiffer
# .git/config veya ~/.gitconfig
[diff "sopsdiffer"]
textconv = sops --decrypt
Artık git diff secrets.enc.yaml encrypted blob farklarını değil gerçek value değişikliklerini gösteriyor.
Pre-commit Hook’ları ve Doğrulama
Pre-commit hook’ları ile decrypt edilmiş secret’ları commit etmeyi önle:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/yuvipanda/pre-commit-hook-ensure-sops
rev: v1.1
hooks:
- id: sops-encryption
files: (secrets|prod).*\.(yaml|json)$
Bu hook pattern’e match eden dosyaların commit’e izin vermeden önce encrypted olduğunu doğruluyor.
Custom validation ekle:
# .git/hooks/pre-commit
#!/bin/bash
# Encrypted olmayan sensitive pattern'leri kontrol et
FORBIDDEN_PATTERNS="password|api_key|secret_token"
for file in $(git diff --cached --name-only); do
if [[ $file =~ \.(yaml|json)$ ]] && [[ ! $file =~ \.enc\. ]]; then
if grep -qiE "$FORBIDDEN_PATTERNS" "$file"; then
echo "ERROR: $file'da encrypted olmayan secret olabilir"
echo "${file%.yaml}.enc.yaml yapmayı mı kastettin?"
exit 1
fi
fi
done
# Encrypted dosyaların SOPS metadata'sı olduğunu doğrula
for file in $(git diff --cached --name-only | grep '\.enc\.'); do
if ! grep -q "^sops:" "$file"; then
echo "ERROR: $file SOPS metadata'sı eksik"
exit 1
fi
done
Hook hem plaintext secret’ları yanlışlıkla commit etmeyi hem de encrypted olduğunu iddia eden ama olmayan dosyaları commit etmeyi önlüyor.
Key Rotation Stratejileri
Age key’lerinin bir rotasyon takvimi olmalı ve bu takvim ancak adımlar script’e bağlıysa yaşıyor. Birinin hatırlamasına bağlı kalan rotasyon, atlanan rotasyon oluyor.
Yeni age key oluştur:
age-keygen -o new-key.txt
OLD_KEY=$(grep "public key:" old-key.txt | cut -d' ' -f3)
NEW_KEY=$(grep "public key:" new-key.txt | cut -d' ' -f3)
Yeni key’i tüm encrypted dosyalara ekle:
find . -name "*.enc.yaml" -type f | while read file; do
sops --add-age "$NEW_KEY" "$file"
done
Data key’leri rotate et (yeni rastgele key’ler oluşturur):
find . -name "*.enc.yaml" -type f | while read file; do
sops --rotate --in-place "$file"
done
Eski key’i kaldır:
find . -name "*.enc.yaml" -type f | while read file; do
sops --rm-age "$OLD_KEY" "$file"
done
.sops.yaml’i güncelle:
sed -i "s/$OLD_KEY/$NEW_KEY/g" .sops.yaml
Commit et ve yeni private key’i secure bir kanal üzerinden (password manager, encrypted email, secure messaging) team’e dağıt.
KMS key’leri için süreç benzer ama yeni KMS key oluşturmayı, dosyalara eklemeyi, rotate etmeyi ve eski KMS key ARN’ini kaldırmayı içeriyor.
Key Group’lar ile Çoklu Takım Erişimi
Production environment’lar genellikle birden fazla team’in secret’lara erişimini gerektiriyor. SOPS bunu key group’lar ve Shamir’s Secret Sharing ile destekliyor.
# .sops.yaml
keys:
platform: &platform
- &platform_admin age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
- &platform_sre age1cy0su9fwf8gzkdqh3r4r6xgc92fp8jqrjp4fvd4ak6vd3mc0jjpqnhymkw
security: &security
- &sec_team age1yx3z8r0hnzjy9wh6fq5gldq3p7hxg6nfkz5vgqcdqhsj8tqxj8xq8w6qur
creation_rules:
- path_regex: prod/.*\.enc\.yaml$
key_groups:
- age:
- *platform_admin
- *platform_sre
- age:
- *sec_team
shamir_threshold: 2
İki key group ve shamir_threshold: 2 ile decryption her group’tan birer key istiyor; bu, separation of duties’in konfigürasyona dökülmüş hali: ne platform takımı ne de security takımı bir production secret’ını tek başına açabiliyor. Üçüncü bir group eklersen (örneğin offline bir backup key), aynı threshold üçünden herhangi ikisinin decrypt etmesine izin veriyor.
SOPS, data key’ini Shamir’s Secret Sharing ile her group’a bir fragment düşecek şekilde bölüyor ve her fragment’i o group’un key’leriyle şifreliyor. Threshold ise data key’in yeniden oluşturulabilmesi için kaç fragment’in bir araya gelmesi gerektiğini belirliyor.
Maliyet Karşılaştırması ve Trade-off’lar
100 secret’lı bir senaryo için:
AWS KMS ile SOPS:
- KMS key’leri: 3 × $1/ay = $3
- API call’ları: ~10,000 decryption/ay = $0.03
- Git storage: $0 (mevcut repository)
- Toplam: $3.03/ay
AWS Secrets Manager:
- Secret’lar: 100 × $0.40/ay = $40
- API call’ları: 10,000/ay × $0.05/10k = $0.05
- Toplam: $40.05/ay
Tasarruf: $37/ay (%92 azalma)
Ama maliyet tek consideration değil. Secrets Manager automated rotation sağlıyor, SOPS scripting gerektiriyor. Secrets Manager CloudTrail üzerinden built-in audit log’lara sahip. SOPS Git history’ye güveniyor.
SOPS static secret’lar için kazanıyor (seyrek değişen API key’leri, OAuth credential’ları, database connection string’leri). Secrets Manager dynamic secret’lar için kazanıyor (haftalık rotate olan database password’leri, automated renewal’lı service credential’ları).
Hybrid yaklaşım iyi çalışıyor:
- Development ve staging: age key’leri ile SOPS
- Production static secret’lar: KMS ile SOPS
- Production dynamic secret’lar: AWS Secrets Manager
- Database root password’leri: Secrets Manager
- Third-party API key’leri: SOPS
Yaygın Tuzaklar ve Çözümler
Tuzak: Decrypt Edilmiş Dosyaları Commit Etmek
.gitignore’a ekle:
secrets.yaml
config/production.yaml
*.decrypted.yaml
# Encrypted dosyalara izin ver
!*.enc.yaml
Tuzak: Age Private Key’leri Kaybetmek
Backup’ları birden fazla yerde sakla:
- Password manager’da (1Password, LastPass)
- Physical safe’te encrypted USB drive’da
- Offline saklanan emergency recovery key
Emergency key oluştur ve tüm production secret’larına ekle:
age-keygen -o emergency-key.txt
AGE_PUBLIC_KEY=$(grep "public key:" emergency-key.txt | cut -d' ' -f3)
find prod/ -name "*.enc.yaml" | while read file; do
sops --add-age "$AGE_PUBLIC_KEY" "$file"
done
Tuzak: KMS Permission Sorunları
Decrypt ederken “AccessDeniedException” hatası genellikle IAM permission’larının yanlış olduğu anlamına geliyor. Doğrula:
aws sts get-caller-identity # Assumed role'u onayla
aws kms describe-key --key-id $KMS_KEY_ID # KMS access'i test et
sops --decrypt --verbose secrets.enc.yaml # Detaylı hatayı gör
IAM role’ünün KMS key için hem kms:Decrypt hem de kms:DescribeKey permission’larına sahip olduğundan emin ol.
Tuzak: Git Merge Conflict’leri
İki developer aynı anda aynı encrypted dosyayı edit ettiğinde, Git encrypted blob’larla merge conflict oluşturuyor.
Note
Normal editing için sops secrets.enc.yaml komutu dosyayı decrypt edip editörde açar, kaydedip çıktığında otomatik re-encrypt eder. Ama merge conflict’lerde iki farklı versiyonu karşılaştırman gerektiği için manuel decrypt → merge → re-encrypt workflow’u zorunlu.
Resolve etmek için:
# İki versiyonu da decrypt et
sops --decrypt secrets.enc.yaml > mine.yaml
git show origin/main:secrets.enc.yaml | sops --decrypt /dev/stdin > theirs.yaml
# Manuel olarak merge et
vimdiff mine.yaml theirs.yaml
# Merge edilmiş versiyonu kaydet
mv merged.yaml secrets.yaml
# Re-encrypt et
sops --encrypt secrets.yaml > secrets.enc.yaml
git add secrets.enc.yaml
Daha iyi: shared secret’ları edit ederken communicate et, veya collision olasılığını azaltmak için büyük dosyaları daha küçük domain-specific dosyalara böl.
SOPS Ne Zaman Doğru Varsayılan Olur
SOPS, secret’lar onları kullanan kodla birlikte değiştiğinde işini yapıyor: API key’leri, OAuth credential’ları, üçüncü taraf token’ları, environment başına konfigürasyon. Development’ta age, production’da KMS ile şifrele, key seçimini path’e göre .sops.yaml’a bırak. Secret değerleri deploy zamanında çözülen referanslar olarak geçir; böylece function runtime’da secret okumak için bir bedel ödemiyor ve cdk.out içinde plaintext kalmıyor. Production dosyalarında KMS key’ini bir age recipient’ıyla eşlemek hem merkezî IAM kontrolü hem de KMS’e erişilemediği gün için bir kurtarma yolu bırakıyor.
Secret’lar deploy’lardan bağımsız rotate oluyorsa, aynı değeri runtime’da birden fazla servis okuyorsa ya da yönetilen rotation ve cross-region replication gerekiyorsa varsayılanı değiştir. Bunlar Secrets Manager veya Parameter Store’un işi; iki model de çakışmadan bir arada yaşıyor: statik konfigürasyon için SOPS, rotasyon takvimi olan her şey için bir AWS store.
Bu çizginin hangi tarafından başlarsan başla, ilk şifreli dosya repository’ye girmeden önce pre-commit hook’unu kur. Otomatik kontrol olmadan eninde sonunda biri plaintext bir dosyayı commit ediyor.
Kaynaklar
- SOPS: Secrets OPerationS - KMS, Age ve PGP kullanan YAML/JSON şifrelemesi için belgeler içeren resmi SOPS deposu
- SOPS Belgeleme Sitesi - Yapılandırma, anahtar yönetimi ve editör entegrasyonunu kapsayan resmi belgeler
- age Şifreleme Aracı - SOPS arka ucu olarak kullanılan basit, modern dosya şifreleme aracı
- AWS Key Management Service - SOPS anahtar şifrelemesi ve IAM tabanlı erişim denetimi için AWS KMS
- AWS Secrets Manager - Dönen kimlik bilgileri ve dinamik sırlar için yönetilen sır hizmeti
- AWS Systems Manager Parameter Store - Statik sırlar için hiyerarşik yapılandırma ve SecureString depolama
- Argo CD: Kubernetes için Deklaratif GitOps CD - SOPS ile şifrelenmiş manifest’lerle entegre olan GitOps teslimat aracı
İlgili yazılar
Secrets Manager ve Parameter Store'u karşılaştıran teknik rehber: hangi servisi ne zaman seçmeli ve production implementation pattern'leri.
Bir kodlama ajanının evi olarak devcontainer, Codespaces ve AWS Lambda MicroVM: her basamak ne katıyor ve ajanı laptoptan çıkarmak ne zaman kazandırıyor.
Gelişmiş Amazon Cognito teknik rehberi: özel auth akışları, federation, multi-tenancy, migration stratejileri ve CDK ile production-grade güvenlik.
AWS Lambda, API Gateway, DynamoDB ve Step Functions için hızlı geri bildirim ve production güvenilirliği sağlayan kapsamlı bir test stratejisi oluşturmayı öğrenin.
AWS CDK, Lambda ve GitHub Actions kullanarak otomatik preview ortamları oluşturmayı öğrenin - sorunsuz PR test ve inceleme süreçleri için