PEM'den Çalışan Stack'e — Bölüm 3: Üç Parola, İki Dizin, Bir Karıştırılmış Sertifika
Serinin 3. bölümü. Sertifikalar teknik olarak doğruydu. Yine de hiçbir şey başlamıyordu. Bu bölüm, kriptoda değil konfigürasyonda saklanan hataları anlatıyor — tam da her şeyin "doğru göründüğü" için en zor bulunanları.
Hata neden hiç değişmiyor?
Keystore dosyası nihayet doğru formata girip gerçek bir girdi tutmaya başladıktan sonra servisi yeniden başlattım — ve daha öncekiyle birebir aynı hatayı aldım:
ERROR: keystore password was incorrect javax.crypto.BadPaddingException: Given final block not properly padded
İşte burada projenin en önemli derslerinden biri yatıyor: bir değişiklikten sonra hiçbir şey değişmiyorsa, muhtemelen yanlış yeri değiştiriyorsunuzdur. Dosyayla daha fazla uğraşmak yerine geri adım atıp servisin gerçekten düzenlediğiniz dosyayı okuyup okumadığını — ve beklediğinizi sandığınız parola ile mi okuduğunu — kontrol etmek gerekiyor.
Dedektiflik: servis aslında hangi parolayı bekliyor?
Stack, konfigürasyonunu birbirini geçersiz kılan birden fazla env dosyasından alıyordu (varsayılanlar *-default.env'de, özel ayarlar *-custom.env'de). Bu tür katmanlı sistemler kullanışlıdır; ama hangi değerin gerçekte geçerli olduğunu belirsizleştirirler.
Belirleyici adım, tahmin etmek değil, servise kendi ucuna ne geldiğini doğrudan sormaktı:
docker compose run --rm --entrypoint sh keycloak -c \ 'printf "PW=[%s]\n" "$KC_SPI_TRUSTSTORE_FILE_PASSWORD"'
Köşeli parantezler kasıtlı: değerin sonunda görünmez bir boşluk ya da Windows satır sonu (\r) olup olmadığını gözle görülür kılıyorlar — yoksa [changeit ] veya [wert\r] gibi bir şeyi fark etmek olanaksız olurdu.
Sonuç gözleri açıcıydı: servis, dosyayı oluşturduğum paroladan farklı bir parolayı bekliyordu. Aslında farklı amaçlar için oyunda birden fazla parola değişkeni vardı — ve ben başından beri yanlış olanı kullanıyordum.
Bu stack'te birbirine benzer ama farklı şeyleri kontrol eden gerçekten üç farklı parola ayarı vardı:
- auth sunucusunun sistem truststore'u için biri,
- HTTPS sunucu keystore'u için biri (servisin TLS için kendi sunduğu sertifika),
- üçüncü, servise özgü bir varyant için biri.
Buna ek olarak, truststore parolası başlatma betiğine sabit kodlanmış olan ve env dosyaları aracılığıyla hiç yapılandırılamayan başka bir servis (bir REST API ara katmanı) da vardı.
KANIT: servisler bir dosyayı paylaşıyorsa tek bir parolada anlaşmaları gerekir — bir dosyanın yalnızca bir parolası olabilir.
Çözüm teknik değil, organizasyonel bir karardı: hepsini aynı değere eşitlemek. O servisin parolası sabit kodlanmış ve değiştirilemeyeceğinden, geri kalan tüm değişkenleri tam olarak o değere ayarladım:
KC_HTTPS_KEY_STORE_PASSWORD=<TRUSTSTORE_PW> KC_HTTPS_TRUST_STORE_PASSWORD=<TRUSTSTORE_PW> KC_SPI_TRUSTSTORE_FILE_PASSWORD=<TRUSTSTORE_PW>
— ve tabii ki tüm keystore dosyalarını bu parola ile yeniden oluşturdum.
HÜKÜM: parola uyumsuzluklarında soru yalnızca "dosyanın parolası doğru mu?" değil, "bu belirli servisin beklediği ile eşleşiyor mu?" olmalı. Ve birden fazla servis aynı dosyayı paylaşıyorsa bir parolada anlaşmak zorundalar.
Kenara not: env dosyaları ve satır sonları
Değer Windows'ta oluşturulmuş olabilecek bir env dosyasından geldiğinden, her düzenlemeden sonra şunu çalıştırmaya değer:
sed -i 's/\r$//' service-custom.env
Parola değerinin sonundaki bir \r görünmezdir; ama changeit'ı changeit\r'a dönüştürür — ve böylece farklı bir parola yapar.
Parolalar çözüldükten sonra auth sunucusu çalışmaya başladı. Ama ara katman bileşeni, eşleşen CA sertifikasını içe aktarmış olmama karşın auth sunucusuna olan TLS bağlantısında güven hatası vermeye devam etti.
Yine geçerliydi: bir onarıma rağmen hiçbir şey değişmiyorsa servis muhtemelen başka bir dosyayı okuyor. O hâlde container'ın gerçekte neleri mount ettiğine bakın:
docker inspect <container> \
--format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
KANIT: auth sunucusu sertifikalarını bir dizinden (/host/ssl/) alıyordu, ara katman ise tamamen farklı bir dizinden (/host/installation/certs/). Ben başından beri doğru dosyayı yanlış yerde düzeltiyordum.
HÜKÜM: "container'ın gerçekten okuduğu dosya nerede?" sorusunu erken ve açıkça yanıtlamalısınız — tüm servislerin aynı yolu paylaştığını varsaymayın. docker inspect (ya da docker compose config) gerçeği gösterir.
Projenin en sinsi karışıklığı: truststore'umda corp-ca alias'lı ve geçerli bir trustedCertEntry'li bir girdi vardı. Mükemmel görünüyordu. Yine de güven kontrolü başarısız oldu.
Nedeni şuydu: girdi corp-ca adını taşıyordu, ama içeriği gerçek CA root sertifikası değil, sunucu son sertifikasıydı (ERP host'ununkiydi). Başlarda bir dosyayı yanlış atamıştım ve o günden bu yana doğru ad altında yanlış sertifikayı sürekli beraberinde taşıyordum.
Bunu ortaya çıkaran şey bir parmak izi karşılaştırması oldu. Alias'a güvenilemez — yalnızca parmak izi gerçeği söyler:
KANIT: keystore'daki bir girdi içeriği hakkında hiçbir şey kanıtlamaz. Güvenilir tek kimlik parmak izidir.
# What's in the truststore?
keytool -list -keystore root.p12 -storetype PKCS12 -storepass <TRUSTSTORE_PW>
# -> corp-ca ... Fingerprint (SHA-256): 79:75:09:...
# What does the server actually serve? Look at the chain:
echo | openssl s_client -connect keycloak.example.local:5443 -showcerts 2>/dev/null \
| awk '/BEGIN CERT/{c++} {print > "cert"c".pem"}'
for f in cert*.pem; do
openssl x509 -in "$f" -noout -subject -issuer -fingerprint -sha256
done
Gerçek bir CA root sertifikasında subject ve issuer özdeştir (self-signed'dır). Truststore'a tam da o ait — ve onun parmak izi, içeride tuttuğumdan tamamen farklıydı. Yalnızca doğru sertifikayı içe aktarmak (eşleşen parmak izi aracılığıyla doğrulanmış) güven sorununu çözdü.
HÜKÜM: sertifikaları parmak izine göre doğrulayın, adına göre değil. Ad yalan söyler, parmak izi söylemez. Güven zinciri için CA root sertifikasına (subject = issuer) ihtiyacınız var, son sertifikaya değil.
Bölüm 3'ten Öğrendiklerim
- Değişiklikten sonra hata değişmiyorsa, değişiklik yanlış yerdeydi. Geri adım atın.
- Env katmanlarını kafanızda çözmeye çalışmak yerine servise kendi ucuna ne geldiğini sorun (parantezlerle
printf "[%s]"). - Birbirine benzeyen birden fazla parola değişkeni klasik bir tuzak — servisin gerçekte hangisini okuduğunu netleştirin.
- Servisler bir dosyayı paylaşıyorsa aynı parolaya ihtiyaçları var. Eşitleme, karmaşıklıkla boğuşmaktan çoğu zaman daha pragmatik.
docker inspect, bir container'ın gerçekte hangi dosyayı mount ettiğini gösterir — yol varsayımlarına güvenmeyin.- Sertifikaları parmak iziyle doğrulayın, alias ile asla. Ad yalan söyler, parmak izi söylemez.
- Truststore = CA root sertifikası (subject = issuer), son sertifika değil.
Finalede artık bozuk dosyalarla değil, güven mimarisinin kendisiyle ilgili son engel ele alınıyor: bir servisin aynı anda hem dahili hem de public bir sertifika otoritesine güvenmesi gerektiğinde ne yaparsınız?