CRM entegrasyonlarında API anahtarı yönetimi: Önekli anahtar, hash ile saklama, kapsam, rotasyon ve sızıntı müdahalesi
Bir CRM projesinde ilk entegrasyon genellikle aceleyle kurulur. Reklam formundan lead gelecektir, bir çağrı merkezi yazılımı arama sonuçlarını geri yazacaktır, muhasebe tarafı satış kapanınca fatura bilgisini çekecektir.
Bir CRM projesinde ilk entegrasyon genellikle aceleyle kurulur. Reklam formundan lead gelecektir, bir çağrı merkezi yazılımı arama sonuçlarını geri yazacaktır, muhasebe tarafı satış kapanınca fatura bilgisini çekecektir. Hepsi için "bir API anahtarı verelim, sonra düzeltiriz" denir. Ben Koray Yalçın; lead yönetimi, CRM mimarisi, dijital pazarlama ve yapay zekâ destekli otomasyon üzerine, ağırlıklı olarak gayrimenkul ve PropTech projelerinde çalışıyorum. Sahada gördüğüm şu: "Sonra düzeltiriz" denen anahtar, iki yıl sonra hâlâ aynı yerdedir. Üstelik kimin kullandığı, neye erişebildiği ve hangi Slack mesajında, hangi .env dosyasında kopyalarının durduğu artık kimse tarafından bilinmez.
Bu yazıda API anahtarlarını bir geliştirici gözüyle ele alıyorum. Anahtar nasıl üretilir, veritabanında nasıl saklanır, hangi yetkiyle sınırlanır, kesintisiz nasıl değiştirilir ve sızdığında ilk bir saatte ne yapılır? Örnekler PostgreSQL ve Python üzerinden, ama fikirler dil ve veritabanından bağımsız.
API anahtarı neyi çözer, neyi çözmez?
API anahtarı, bir yazılımın başka bir yazılıma "ben buyum" demesinin en basit yoludur. Kullanıcı oturumu yoktur, tarayıcı yoktur; bir sunucu diğerine istek atar ve başlıkta bir sır taşır. Bu yüzden makineden makineye entegrasyonlarda hâlâ en yaygın yöntemdir: web formu sağlayıcısı, ilan portalı köprüsü, çağrı merkezi, BI aracı, müşterinin kendi geliştirdiği küçük betik.
Ama anahtarın çözmediği şeyleri baştan kabul etmek gerekir. Anahtar bir kişiyi değil, bir entegrasyonu temsil eder; "bu kaydı kim değiştirdi" sorusuna tek başına cevap vermez. Taşınabilir bir sırdır; kopyalanan anahtar, kopyalayan için de aynı şekilde çalışır. Kendiliğinden süresi dolmaz. Bu üç özellik, tasarımın geri kalanını belirler: anahtar her zaman bir entegrasyon kaydına bağlı olmalı, mümkün olan en dar yetkiye sahip olmalı ve değiştirilmesi rutin bir iş olmalıdır.
Kullanıcı adına hareket eden, kullanıcının onayını gerektiren senaryolarda (örneğin bir pazarlama aracının danışmanın takvimine erişmesi) API anahtarı yerine OAuth daha doğrudur. API anahtarını, arkasında belirli bir sistemin durduğu, sunucu tarafında saklanabilen entegrasyonlar için ayırıyorum.
Anahtarın biçimi: Önek, kimlik ve sır
Rastgele 32 karakterlik bir dizi üretip vermek işe yarar, ama operasyonu zorlaştırır. Destek ekibine gelen "bu anahtar hangisi?" sorusuna cevap veremezsiniz, kod taramasında anahtarı tanıyamazsınız, sızıntı bildiriminde hangi ortama ait olduğunu anlayamazsınız. Ben anahtarı üç parçalı bir biçimde üretiyorum:
crm_live_8f3k2q_Zt7wQ1n9VxR4mB6pL0sYc2HdE5uJaK3f
│ │ │ └─ sır (yalnızca bir kez gösterilir)
│ │ └─ kısa kimlik (veritabanında aranır, loglarda görünebilir)
│ └─ ortam (live / test)
└─ ürün öneki
Önek, gizli bilgi tarama araçlarının (secret scanning) anahtarı tanıması için düzenli ifade yazmayı kolaylaştırır. Ortam bilgisi, test anahtarının canlıda, canlı anahtarın test ortamında kullanılmasını ilk satırda reddetmenizi sağlar. Kısa kimlik ise anahtarın "adı"dır: destek talebinde, denetim kaydında ve panelde bu görünür; sır asla görünmez.
import secrets, hashlib, hmac
ALPHABET = "abcdefghijkmnpqrstuvwxyz23456789"
def generate_key(env: str = "live") -> tuple[str, str, str]:
key_id = "".join(secrets.choice(ALPHABET) for _ in range(6))
secret = secrets.token_urlsafe(32) # ~256 bit entropi
full = f"crm_{env}_{key_id}_{secret}"
return full, key_id, secret
def hash_secret(secret: str, pepper: bytes) -> str:
return hmac.new(pepper, secret.encode(), hashlib.sha256).hexdigest()
Burada iki ayrıntı önemli. Birincisi secrets modülü: random ile üretilen anahtar tahmin edilebilir. İkincisi, sır yeterince uzun ve rastgele olduğu için parolalarda kullandığımız yavaş algoritmalara (bcrypt, Argon2) gerek yoktur. Rastgele 256 bitlik bir değeri kaba kuvvetle bulmak pratikte mümkün değildir. Hızlı bir HMAC-SHA256 yeterlidir ve her istekte doğrulama yapacağınız için hız da gerçekten önemlidir. Pepper, veritabanından ayrı tutulan (ortam değişkeni ya da gizli bilgi yöneticisi) bir anahtardır; veritabanı yedeği sızsa bile hash'ler tek başına işe yaramaz.
Saklama: Sır veritabanına hiç girmez
Kural basit: sır, üretildiği anda kullanıcıya bir kez gösterilir ve bir daha hiçbir yerde düz metin olarak durmaz. Veritabanında yalnızca hash, kısa kimlik ve meta veri tutulur.
CREATE TABLE api_key (
id bigserial PRIMARY KEY,
key_id text NOT NULL UNIQUE, -- "8f3k2q"
env text NOT NULL CHECK (env IN ('live','test')),
secret_hash text NOT NULL,
integration_id bigint NOT NULL REFERENCES integration(id),
scopes text[] NOT NULL,
allowed_cidrs cidr[],
created_by bigint NOT NULL REFERENCES app_user(id),
created_at timestamptz NOT NULL DEFAULT now(),
expires_at timestamptz,
last_used_at timestamptz,
last_used_ip inet,
revoked_at timestamptz,
revoked_reason text
);
Dikkat edin, anahtar bir kullanıcıya değil bir integration kaydına bağlı. Entegrasyon kaydında sorumlu kişi, karşı taraf firma, amaç ve iletişim bilgisi durur. Anahtarı oluşturan danışman işten ayrıldığında entegrasyon çalışmaya devam eder; ama sorumluluğun kime devredileceği açıkça görünür. Gayrimenkul projelerinde bu çok yaşanır: ajans değişir, çağrı merkezi tedarikçisi değişir, ama eski tedarikçinin anahtarı hâlâ aktiftir. Entegrasyon kaydı olmadan bu anahtarı fark etmek neredeyse imkânsızdır.
last_used_at alanını her istekte yazmak veritabanına gereksiz yük bindirir. Ben bunu bellekte biriktirip dakikada bir toplu güncelliyorum; amaç saniye hassasiyeti değil, "bu anahtar 90 gündür kullanılmıyor" sorusunu cevaplayabilmek.
Doğrulama: Sabit zamanlı karşılaştırma ve erken ret
İstek geldiğinde doğrulama sırası, hem güvenliği hem performansı etkiler.
def authenticate(raw: str, client_ip: str, required_scope: str):
try:
_, env, key_id, secret = raw.split("_", 3)
except ValueError:
raise Unauthorized("malformed")
if env != CURRENT_ENV:
raise Unauthorized("wrong_env")
row = db.fetch_one("SELECT * FROM api_key WHERE key_id = %s", [key_id])
if row is None or row.revoked_at or (row.expires_at and row.expires_at < now()):
raise Unauthorized("invalid")
expected = row.secret_hash
if not hmac.compare_digest(expected, hash_secret(secret, PEPPER)):
raise Unauthorized("invalid")
if row.allowed_cidrs and not ip_in_any(client_ip, row.allowed_cidrs):
raise Forbidden("ip_not_allowed")
if required_scope not in row.scopes:
raise Forbidden("scope_missing")
usage_buffer.touch(row.id, client_ip)
return row
hmac.compare_digest karşılaştırmayı sabit sürede yapar; normal == ilk farklı karakterde durduğu için zamanlama farkından bilgi sızdırabilir. Dışarıya dönen hata mesajları ise kasıtlı olarak belirsizdir: "böyle bir anahtar yok" ile "sır yanlış" aynı cevabı alır. Ayrıntılı sebep yalnızca iç loglara yazılır. Bu loglara da sırrın kendisi değil, kısa kimlik yazılır. Başlıkları olduğu gibi loglayan bir ara katman (middleware), bütün bu özeni tek satırda boşa çıkarabilir; log maskeleme kuralına Authorization ve X-Api-Key başlıklarını mutlaka ekleyin.
Anahtar her zaman başlıkta taşınmalı, sorgu parametresinde (?api_key=...) değil. URL'ler proxy loglarına, tarayıcı geçmişine ve analiz araçlarına düşer.
Kapsam: Her anahtar yalnızca işini yapabilsin
En sık gördüğüm hata, tek bir "tam yetkili" anahtarın her entegrasyona verilmesi. Web formu sağlayıcısının yalnızca lead oluşturması gerekir; lead listesini okuması, hele silmesi hiç gerekmez. Ama tam yetkili anahtar sızdığında, saldırgan bütün müşteri veritabanını dışa aktarabilir.
Kapsamları kaynak ve eylem çifti olarak tanımlıyorum:
| Entegrasyon | Kapsamlar |
|---|---|
| Web formu / reklam formu köprüsü | leads:create |
| Çağrı merkezi |
leads:read_assigned, activities:create
|
| Muhasebe |
deals:read_won, invoices:write
|
| BI / raporlama |
reports:read (kişisel veri maskeli) |
Kapsamın yanında veri sınırı da düşünülmeli. Çağrı merkezinin "lead okuma" yetkisi bütün lead'leri değil, yalnızca kendisine atanmış kuyruğu kapsamalı. BI aracı telefon numarasını değil, maskelenmiş ya da hash'lenmiş bir değeri görmeli. KVKK açısından da bu önemli: veriyi işleyen her üçüncü taraf, yalnızca amaca uygun veriye erişmelidir. Bu yüzden kapsam kontrolünü yalnızca uç nokta girişinde değil, sorgu katmanında da uyguluyorum; rol tabanlı erişimde kullandığım satır düzeyi filtreler, entegrasyon kimliği için de çalışır.
IP kısıtlaması ek bir katmandır. Sabit çıkış IP'si olan sunucu entegrasyonlarında allowed_cidrs doldurulur; sızan anahtar başka bir yerden kullanılamaz. Bulut tabanlı ve değişken IP'li servislerde bu mümkün olmayabilir; o durumda kapsam ve hız sınırı daha da önem kazanır.
Rotasyon: Kesintisiz değiştirme
Anahtar değiştirmek korkutucu bir iş olarak görülürse kimse yapmaz. Rotasyonu rutin hâle getirmenin yolu, bir entegrasyonun aynı anda iki geçerli anahtara sahip olabilmesidir.
- Yeni anahtar üretilir ve entegrasyon sorumlusuna güvenli bir kanaldan iletilir. Eski anahtar çalışmaya devam eder.
- Karşı taraf yeni anahtarı devreye alır.
- Panel,
last_used_atüzerinden eski anahtarın kullanımının durduğunu gösterir. - Eski anahtara bir bitiş tarihi (
expires_at) atanır; tarih gelince otomatik olarak geçersiz olur.
-- 30 gündür kullanılmayan ya da 180 günden eski aktif anahtarlar
SELECT k.key_id, i.name AS integration, i.owner_email,
k.created_at, k.last_used_at
FROM api_key k
JOIN integration i ON i.id = k.integration_id
WHERE k.revoked_at IS NULL
AND (k.last_used_at < now() - interval '30 days'
OR k.created_at < now() - interval '180 days')
ORDER BY k.last_used_at NULLS FIRST;
Bu sorguyu haftalık bir rapora bağlamak, işin büyük kısmını çözer. Kullanılmayan anahtar kapatılır, eski anahtar için sorumluya rotasyon talebi açılır. Benim projelerde varsayılan ömür 180 gün; kritik yetkili anahtarlarda daha kısa.
Sızıntı müdahalesi: İlk bir saat
Anahtarlar sızar. Bir geliştirici .env dosyasını herkese açık bir depoya iter, bir ekran görüntüsünde başlık görünür, bir tedarikçi anahtarı e-postayla paylaşır. Önemli olan, sızıntının fark edildiği anda ne yapılacağının önceden yazılmış olmasıdır.
-
İptal et, sonra soruştur. Anahtar hemen
revoked_atile kapatılır. Entegrasyonun kısa süre kesilmesi, verinin dışarı çıkmasından her zaman ucuzdur. Yeni anahtar rotasyon akışıyla verilir. - Kullanımı incele. Denetim kaydında o kısa kimlikle yapılan istekler; IP, uç nokta ve zaman bazında çıkarılır. Alışılmadık IP'ler, toplu okuma ya da dışa aktarma istekleri işaretlenir.
- Etkiyi belirle. Kapsam dar tutulduysa cevap hızlı gelir: "Bu anahtar yalnızca lead oluşturabiliyordu, okuma yetkisi yoktu." Tam yetkili anahtarda ise kişisel veri ihlali değerlendirmesi ve gerekirse bildirim süreci başlar.
- Kök nedeni kapat. Depoya gizli bilgi taraması, ön işleme kancası (pre-commit hook), tedarikçiyle güvenli paylaşım kanalı.
Önekli anahtar biçimi burada da işe yarar: birçok kod barındırma platformu ve tarama aracı, tanımlı bir desene uyan sırları otomatik yakalayabilir. Rastgele bir dizi bu şansı size vermez.
Hız sınırı ve kötüye kullanım sinyalleri
Her anahtarın kendi hız sınırı olmalı: dakikada istek sayısı ve günlük toplam. Web formu köprüsünün dakikada bin lead oluşturması, normal bir kampanya trafiği değil, bir döngü hatası ya da kötüye kullanımdır. Sınırı aşan istekler 429 ile reddedilir ve Retry-After başlığı döner; iyi yazılmış bir istemci buna göre bekler.
Hız sınırının yanında birkaç basit sinyal de izlenebilir: anahtarın yeni bir ülkeden ya da IP aralığından kullanılmaya başlaması, alışılmadık saatlerde toplu okuma, kapsam dışı uç noktalara tekrar tekrar gelen 403 cevapları. Bunlar tek başına saldırı kanıtı değildir, ama entegrasyon sorumlusuna "bu sizin mi?" diye sormak için yeterli sebeptir.
Sık sorulan sorular (SSS)
API anahtarını veritabanında şifreleyerek (encrypt) saklamak yeterli mi?
Şifreleme geri döndürülebilir; şifre çözme anahtarına erişen herkes sırrı görür. Doğrulama için sırrın kendisine ihtiyacınız yoktur, yalnızca eşleşip eşleşmediğini bilmeniz gerekir. Bu yüzden hash (pepper ile HMAC) daha doğrudur. Şifreleme, yalnızca sizin bir dış servise göndermeniz gereken anahtarlar için uygundur.
Kullanıcı anahtarını kaybederse ne olur?
Anahtar yeniden gösterilmez, çünkü sistem onu bilmez. Yeni anahtar üretilir, eskisi iptal edilir. Bu bir eksiklik değil, tasarımın gereğidir.
Bir entegrasyona birden fazla anahtar vermek karmaşa yaratmaz mı?
Aynı anda en fazla iki aktif anahtar kuralı (biri yeni, biri bitiş tarihli eski) karmaşayı sınırlar ve kesintisiz rotasyonu mümkün kılar.
API anahtarı mı, OAuth mu?
Kullanıcı adına, kullanıcının onayıyla erişim gerekiyorsa OAuth. Arkasında belirli bir sunucu olan, makineden makineye entegrasyonlarda kapsamlı ve rotasyonlu API anahtarı yeterli ve daha sadedir.
Webhook imzası ile API anahtarı aynı şey mi?
Hayır. API anahtarı, size gelen isteğin kimden geldiğini doğrular. Webhook imzası ise sizin gönderdiğiniz ya da size gönderilen olayın yolda değiştirilmediğini kanıtlar. İkisi farklı sırlarla, ayrı ayrı yönetilmelidir.
Test anahtarlarına da bu kadar özen gerekir mi?
Test ortamı gerçek müşteri verisinin bir kopyasını içeriyorsa, evet. İçermiyorsa bile test anahtarının canlıda reddedilmesi, ortam karışıklığından doğan hataları ilk satırda yakalar.
Kapanış
API anahtarı, bir CRM'in dış dünyaya açılan en küçük ama en sık kullanılan kapısıdır. Önekli biçim, hash ile saklama, entegrasyon kaydına bağlılık, dar kapsam, rutin rotasyon ve önceden yazılmış bir sızıntı planı; bunların hiçbiri büyük bir yatırım değildir, ama hepsi birlikte "sonra düzeltiriz" denen anahtarın iki yıl sonra bir veri ihlaline dönüşmesini engeller. Kavramın kısa tanımı ve CRM entegrasyonlarındaki yeri için API Key nedir? sayfasına bakabilirsiniz.
Koray Yalçın — lead yönetimi, CRM mimarisi ve yapay zekâ destekli satış otomasyonu
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.