macOS'ta yerel HTTPS ve asma kilidin göründüğünden zor olmasının sebebi

Modern tarayıcı API'leri düz HTTP üzerinde çalışmayı reddediyor. Yani yerel siteniz sertifika istiyor — ama tek başına sertifika yetmiyor ve sebebi çoğu rehberin atladığı kısımda.

macOS geliştiricileri için pratik rehber · mkcert, sistem güven deposu, .local ile .test farkı ve Apache/Nginx ayrımı

Yerelde neden birdenbire HTTPS gerekiyor

Service worker'lar, Clipboard API, getUserMedia, Web Crypto'nun subtle arayüzü, pratikte HTTP/2 — hepsi tarayıcıların güvenli bağlam dediği koşulun arkasında. http://localhost özel bir istisnayla güvenli sayılıyor. http://projem.test sayılmıyor.

localhost'tan çıkıp gerçek bir ana makine adına geçtiğiniz anda — ki birden fazla projeniz olduğunda, alt alan adı gerektiğinde ya da çerezlerin üretimdeki gibi kapsanmasını istediğinizde bu kaçınılmaz — gerçek bir sertifikaya ihtiyacınız var.

Kendi imzalı sertifika çözüm değil

Akla ilk gelen openssl req -x509. Sertifikayı üretir, sunucu kabul eder, tarayıcı yine de reddeder.

Sebebi şu: tarayıcılar sertifikayı kendi başına değerlendirmez, bir güven zinciri yürür. Bu sertifikayı şu otorite imzalamış, onu bir başkası imzalamış, ta ki işletim sisteminin zaten güvendiği bir köke ulaşana kadar. Kendi imzalı sertifika kendi köküdür ve ona kimse güvenmez.

Uyarıyı tıklayıp geçebilirsiniz. Bunu alışkanlık hâline getirmemelisiniz — üstelik service worker'lar yine kaydolmayı reddeder, çünkü tıklayıp geçmek geçerli bir zincirle aynı şey değildir.

İşe yarayan yol: kendi sertifika otoriteniz

Çözüm, kendi makineniz için sertifika otoritesi olmak. Bir kök sertifika üretirsiniz, macOS'a ona güvenmesini söylersiniz, sonra o kökten istediğiniz kadar site sertifikası çıkarırsınız. Her biri sistemin zaten kabul ettiği bir köke bağlandığı için tarayıcılar susar.

mkcert tam olarak bunu yapar, fazlasını değil. Kökü tek bir yerde durur:

$ mkcert -CAROOT
/Users/siz/Library/Application Support/mkcert

$ ls "$(mkcert -CAROOT)"
rootCA-key.pem    rootCA.pem

rootCA.pem sistem güven deposuna kurulan açık sertifika. rootCA-key.pem ise site sertifikalarınızı imzalayan özel anahtar.

O özel anahtara parola gibi davranın. rootCA-key.pem elinde olan biri istediği ana makine adı için — google.com dahil — sertifika üretebilir ve Mac'iniz buna itirazsız güvenir. Bu dosya makinenizden çıkmaz, depoya girmez, "zaman kazanmak için" ikinci bir bilgisayara kopyalanmaz.

macOS neden bir kez parola soruyor

Kökü kurmak Sistem anahtar zincirine yazar, oturum anahtar zincirinize değil. Orası ayrıcalıklı bir depo, bu yüzden macOS yönetici parolasını tam olarak bir kez ister: CA ilk kurulduğunda. Sonrasında üretilen her sertifika yerelde imzalanır ve hiçbir istem çıkmaz.

Size sürekli parola soruluyorsa, bir şey CA'yı her çalıştırmada yeniden kuruyor demektir — bu araçtaki bir hatadır, olağan davranış değil.

.local tuzağı

Rehberlerin çoğu projem.local kullanmanızı söyler. macOS'ta genelde çalışır. Aynı zamanda altında standart sorunu olan tek seçenektir.

RFC 6762, .local uzantısını Multicast DNS'e ayırır — yani ağınızdaki yazıcıları ve AirPlay hedeflerini bulan Bonjour'a. macOS'ta /etc/hosts kaydı öncelik aldığı için çözülür:

$ ping -c1 projem.local
PING projem.local (127.0.0.1): 56 data bytes

Ama başkasına ait bir ad alanını ödünç alıyorsunuz. Başka işletim sistemlerinde, konteyner içinde ya da mDNS'i yakalayan VPN'lerde aynı ad yavaş çözülebilir veya hiç çözülmez.

RFC 6761 ise .test uzantısını tam bu iş için ayırır: internette asla tahsis edilmeyeceği garanti edilen, test amaçlı adlar. İkinci bir anlamı yok, onu dinleyen bir multicast yanıtlayıcı yok ve ileride satın alacağınız gerçek bir alan adıyla çakışma ihtimali yok.

Yeni projelerde .test tercih edin. projem.test macOS'ta birebir aynı davranır, diğer her yerde belirsizliği ortadan kaldırır. Mevcut bir proje .local'a bağlıysa taşımak için acele etmeyin — sadece yeni işe oradan başlamamak için bir sebep var.

Hizalanması gereken üç parça

Çalışan bir yerel HTTPS kurulumu tek şey değil, üç şeydir; ve herhangi birindeki arıza tarayıcıda aynı görünür:

ParçaNe yaparBozulunca
/etc/hostsAdı 127.0.0.1'e yönlendirirTarayıcı siteye hiç ulaşamaz
SertifikaSunucunun o ad olduğunu kanıtlarUyarı sayfası veya ERR_CERT_COMMON_NAME_INVALID
Sanal sunucu443'te hangi dosyaların sunulacağını söylerYanlış site ya da varsayılan sayfa

Sertifika ana makine adını birebir taşımalı. projem.test için üretilmiş sertifika api.projem.test'i kapsamaz — onu ek ad olarak listelemeniz ya da joker karakter kullanmanız gerekir.

Apache ve Nginx port konusunda anlaşamaz

443 portunu aynı anda tek süreç tutabilir. İkisini birden çalıştırıyorsanız — ki bunun iyi sebepleri var, örneğin WordPress'i Apache'de tutup Next.js uygulamasını Nginx'in arkasına almak — birinin taşınması gerekir.

Alışılmış bölüşüm Apache 80/443, Nginx 8080/8443. Çalışır, ama Nginx siteleri adreslerinde port taşır: https://uygulamam.test:8443. Sertifikalar portu umursamadığı için aynı sertifika ikisini de kapsar.

Yapılandırmada akılda tutulacak bir fark var: Apache sertifika ve anahtarı iki ayrı direktif olarak ister, Nginx ise sertifika dosyasının zinciri zaten içermesini bekler.

# Apache
SSLCertificateFile      /yol/uygulamam.test.pem
SSLCertificateKeyFile   /yol/uygulamam.test-key.pem

# Nginx
ssl_certificate         /yol/uygulamam.test.pem;
ssl_certificate_key     /yol/uygulamam.test-key.pem;

Hataları okumak

CA kurulduğu hâlde tarayıcı hâlâ uyarıyor

Neredeyse her zaman kendi güven deposunu tutan bir tarayıcı ya da eski sertifikayı önbelleğe almış bir oturum. Firefox varsayılan olarak kendi deposunu kullanır — mkcert oraya da kurabilir, ama yalnızca CA kurulurken Firefox yüklüyse. Kurulum yanlış demeden önce tarayıcıyı yeniden başlatın.

Safari'de çalışıyor, Chrome'da çalışmıyor

Chrome sertifika ömrü konusunda daha katı. 398 günden uzun süre geçerli sertifikaları doğrudan reddeder. mkcert bu sınırın altında kalır; elle yazılmış -days 3650 içeren OpenSSL komutları kalmaz.

Service worker yine kaydolmuyor

Geçerli sertifika gerekli ama yeterli değil — sayfanın tamamı HTTPS üzerinden gelmeli, karışık içerik olmamalı. Tek bir http:// betik etiketi sayfayı güvenli bağlamın dışına düşürmeye yeter.

Elle yapmak, bir kereliğine

Tek proje için işin tamamı dört komut:

brew install mkcert
mkcert -install                          # parolayı bir kez sorar
mkcert uygulamam.test "*.uygulamam.test" # .pem çiftini buraya yazar
sudo sh -c 'echo "127.0.0.1 uygulamam.test" >> /etc/hosts'

Sonra sanal sunucuyu bu iki dosyaya yönlendirip sunucuyu yeniden yüklersiniz. Zorluk bunu bir kez yapmakta değil — on birinci projede yapmakta, hangi sertifikanın hangi vhost'a ait olduğunu hatırlamakta ve projeler gelip giderken hosts dosyasını dürüst tutmakta.

BRAMPP bunu nasıl yapıyor

BRAMPP alan adı eklediğinizde aynı adımları sizin için yürütüyor: vhost'u oluşturuyor, sertifikayı mkcert ile üretiyor, hosts kaydını ekliyor ve doğru web sunucusunu yeniden yüklüyor. Hiçbir şey gizli değil — çalıştırdığı her komut konsola anında yazılıyor ve üretilen sertifikalar inceleyebileceğiniz, başka yerde kullanabileceğiniz sıradan mkcert dosyaları.

MIT lisanslı, ücretsiz ve açık kaynak; kendi Apache'sini, PHP'sini ya da MySQL'ini paketlemiyor, makinenizde zaten kurulu Homebrew servislerini yönetiyor.

← BRAMPP'e dön