mssql-python uygulamaları için en iyi güvenlik uygulamaları

Kimlik doğrulama, parametreli sorgular ve veri koruması için bu en iyi uygulamaları takip ederek mssql-python uygulamalarınızı güvence altına alın.

Mümkün olduğunda şifresiz kimlik doğrulama ile başlayın. Yerel .env dosyaları ve SQL şifrelerini geçici geliştirme yardımcısı olarak ele alın ve kod paylaşılan bir ortama ulaşmadan önce sırları yönetilen kimliklere veya gizli bir depoya taşıyın.

Kimlik doğrulaması güvenliği

SQL doğrulaması yerine Microsoft Entra doğrulaması kullanın

Microsoft Entra doğrulaması, saklanan şifreleri ortadan kaldırır ve yönetilen kimlikleri destekler. Tüm ortamlarda SQL doğrulamasına tercih ederim.

Azure'da barındırılan iş yükleri için, yönetilen bir kimlik ile ActiveDirectoryMSIkullanın. Depolanmış kimlik bilgilerine ihtiyaç duymaz ve bir kimlik bilgisi zincirini izlemeden bağlanır:

import mssql_python

def connect_with_managed_identity():
    return mssql_python.connect(
        "Server=<server>.database.windows.net;"
        "Database=<database>;"
        "Authentication=ActiveDirectoryMSI;"
        "Encrypt=yes;"
    )

Yerel geliştirme için, Azure CLI veya diğer geliştirici kimlik bilgilerini otomatik olarak kullanan ActiveDirectoryDefault öğesini kullanın. Üretimde bundan kaçının, çünkü DefaultAzureCredential ilk bağlantıda her kimlik doğrulama sağlayıcısını sırayla deniyor ve bu da üretim iş yüklerinin ihtiyaç duymadığı gecikmeyi ekler:

import mssql_python

def connect_with_default_credential():
    return mssql_python.connect(
        "Server=<server>.database.windows.net;"
        "Database=<database>;"
        "Authentication=ActiveDirectoryDefault;"
        "Encrypt=yes;"
    )

Kaçının: SQL kimlik doğrulama, kimlik bilgilerini kod/yapılandırma içinde saklıyor ve sızıntılara karşı savunmasızdır.


conn = mssql_python.connect("Server=...;UID=user;PWD=password")

Asla kimlik bilgilerini sert kodlamayın

Sadece yerel geliştirme için ortam değişkenleri kullanın. Paylaşılan ortamlarda, şifresiz kimlik doğrulamayı tercih edin. Eski bir SQL kimlik doğrulama akışı kaçınılmaz olduğunda, tam bağlantı dizesini kaynak denetimine eklemek yerine sırrı çalışma zamanında bir gizli bilgi deposundan alın.

Aşağıdaki yaklaşım kimlik bilgilerini sabit kodlamaktadır ve asla kullanılmamalıdır:

conn_str = "Server=<server>;UID=<login>;PWD=<password>"

Yerel geliştirme için, ortam değişkenlerinden kimlik bilgilerini okuyun:

import os

conn_str = (
    f"Server={os.environ['DB_SERVER']};"
    f"Database={os.environ['DB_NAME']};"
)

Gizli anahtar gerektirmeye devam eden paylaşılan ortamlar için, bunu çalışma zamanında Azure Key Vault’tan alın:

from azure.keyvault.secrets import SecretClient
from azure.identity import DefaultAzureCredential

def get_connection_string():
    credential = DefaultAzureCredential()
    secret_client = SecretClient(
        vault_url="https://myvault.vault.azure.net/",
        credential=credential
    )
    return secret_client.get_secret("db-connection-string").value

SQL enjeksiyon önleme

Her zaman parametreli sorgular kullanın

Parametrize sorgular, kullanıcı girdisini sorgu yapısından ayırarak SQL enjeksiyonunu engeller. Her zaman kullanıcı girişini parametreleştirin.

Aşağıdaki diziyle biçimlendirilmiş sorgu, SQL enjeksiyonuna karşı savunmasızdır. Sorguları asla böyle oluşturmayın:

user_input = "'; DROP TABLE Person.Person; --"
cursor.execute(f"SELECT * FROM Person.Person WHERE FirstName = '{user_input}'")

Aşağıdaki parametreli sorgu güvenlidir, çünkü sürücü değeri sorgu metninden ayrı gönderir:

user_input = "'; DROP TABLE Person.Person; --"
cursor.execute(
    "SELECT * FROM Person.Person WHERE FirstName = %(name)s",
    {"name": user_input}
)

Tüm sorgu bileşenlerini parametrize edin

Tablo ve sütun adlarını doğrudan parametre edemezsiniz. Kullanıcı girdisinden bunları interpolasyon yapmak uygulamanızı SQL enjeksiyonuna karşı savunmasız hale getirir:

table = user_input
cursor.execute(f"SELECT * FROM {table}")

Bunun yerine, dinamik tanımlayıcıları izin verilen değerler listesine göre doğrulayın ve kalan değerleri parametreleştirin.

ALLOWED_TABLES = {"Person.Person", "Production.Product", "Sales.SalesOrderHeader"}

def query_table(cursor, table_name: str, conditions: dict):
    """Query with validated table name."""
    if table_name not in ALLOWED_TABLES:
        raise ValueError(f"Invalid table: {table_name}")
    
    # Table name is safe, parameters are parameterized
    where_clauses = [f"{k} = %({k})s" for k in conditions.keys()]
    query = f"SELECT * FROM {table_name} WHERE {' AND '.join(where_clauses)}"
    
    cursor.execute(query, conditions)
    return cursor.fetchall()

Girdiyi doğrulayın ve temizleyin

Tanımlayıcılarla dinamik SQL oluştururken, her değeri kullanmadan önce katı bir desenle doğrulayın:

import re

def validate_identifier(value: str) -> bool:
    """Validate SQL identifier (table/column name)."""
    # Only allow alphanumeric and underscore
    return bool(re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', value))

def safe_order_by(cursor, table: str, order_column: str, direction: str):
    """Order by with validation."""
    if not validate_identifier(order_column):
        raise ValueError(f"Invalid column name: {order_column}")
    
    if direction.upper() not in ("ASC", "DESC"):
        raise ValueError(f"Invalid direction: {direction}")
    
    cursor.execute(f"""
        SELECT * FROM {table}
        ORDER BY {order_column} {direction.upper()}
    """)

Karmaşık işlemler için depolanmış prosedürler kullanın

Depolanan prosedürler, uygulama koduna maruz kalan SQL yüzey alanını azaltır:

employee_id = 5
cursor.execute("""
    EXECUTE dbo.uspGetEmployeeManagers @BusinessEntityID = %(id)s
""", {"id": employee_id})

Bağlantı güvenliği

Şifreleme gerektiriyor

Bağlantıları her zaman şifreleyin. Azure SQL varsayılan olarak şifrelemeyi zorunlu kılar. Şirket içi SQL Server için Encrypt=yes öğesini açıkça ayarlayın:

conn = mssql_python.connect(
    "Server=<server>.database.windows.net;"
    "Database=<database>;"
    "Encrypt=yes;"
    "TrustServerCertificate=no"
)

En yüksek güvenlik için TDS 8.0 katı modu kullanın

TDS 8.0 şunları sağlar:

  • Bağlantı başlangıcından itibaren TLS 1.3
  • Sertifika doğrulaması gerekli
  • Eski protokollere geri dönüş yok
conn = mssql_python.connect(
    "Server=tcp:<server>.database.windows.net,1433;"
    "Database=<database>;"
    "Encrypt=strict"
)

Sunucu sertifikalarını doğrulama

Sunucu sertifikasını üretimde her zaman doğrulayın, böylece ortada rakip saldırılarını önleyin. Ayarlamayın TrustServerCertificate=yes, çünkü doğrulamayı atlar. Bunun yerine, TrustServerCertificate=no öğesini CA sertifikalarına göre doğrulama yapacak şekilde ayarlayın ve HostNameInCertificate öğesini ana bilgisayar adını doğrulayacak şekilde ayarlayın:

conn = mssql_python.connect(
    "Server=<server>;"
    "Database=<database>;"
    "Encrypt=yes;"
    "TrustServerCertificate=no;"
    "HostNameInCertificate=<server>.domain.com"
)

Veri koruma

Sunucu düzeyinde hassas verileri korumak

Always Encrypted şu anda mssql-python bağlantı dizesi anahtar kelimeleriyle yapılandırılamıyor. Always Encrypted kullanmanız gerekiyorsa, bu özelliği destekleyen SQL Server için ODBC Driver ile pyodbc kullanın. Aynı koruma seviyesini sağlamasalar da, hassas sütunları korumak için dinamik veri maskeleme ve satır düzeyinde güvenlik gibi SQL Server özelliklerini kullanabilirsiniz.

employee_id = 1
cursor.execute("""
    SELECT NationalIDNumber, LoginID
    FROM HumanResources.Employee
    WHERE BusinessEntityID = %(id)s
""", {"id": employee_id})

row = cursor.fetchone()

Geçiş halindeki verileri koruma

  • Bağlantı dizelerinde Encrypt=yes kullanın.
  • On-premises bağlantılar için VPN veya özel uç noktaları kullanın.
  • Use Azure Özel Bağlantı for Azure SQL.

En az ayrıcalık ilkesi

Minimum veritabanı izinleri kullanın

Uygulama hesaplarının minimum izinlere sahip olması gerekir. Uygulama bağlantıları için sa veya db_owner kullanmayın.

  • Sadece okunabilir haber:GRANT SELECT ON SCHEMA::dbo TO ReportingApp;
  • Özel tablo erişimi: GRANT SELECT, INSERT, UPDATE ON Orders TO OrderProcessor;
  • Yalnızca saklanan prosedür: GRANT EXECUTE ON ProcessOrder TO OrderProcessor;

Farklı işlemler için farklı hesaplar kullanın

Her ayrıcalık seviyesini kendine ait bir kimlikle destekleyin; böylece bir okuma yolu yazma işlemi gerçekleştiremez. Bu örnek, istemci kimliği ile seçilen, biri yalnızca okunma erişimi ve diğeri yazma erişimi verilen iki kullanıcı tarafından atanan yönetilen kimlik kullanır:

readonly_client_id = os.environ["READONLY_IDENTITY_CLIENT_ID"]
readwrite_client_id = os.environ["READWRITE_IDENTITY_CLIENT_ID"]

def get_readonly_connection():
    """Connection for read-only operations."""
    return mssql_python.connect(
        f"Server={server};Database={db};"
        f"Authentication=ActiveDirectoryMSI;UID={readonly_client_id};"
        f"Encrypt=yes;ApplicationIntent=ReadOnly;"
    )

def get_readwrite_connection():
    """Connection for write operations."""
    return mssql_python.connect(
        f"Server={server};Database={db};"
        f"Authentication=ActiveDirectoryMSI;UID={readwrite_client_id};"
        f"Encrypt=yes;"
    )

Uygulama rol sırlarını kaynak kontrolünden uzak tutun

Uygulama rol şifreleri hâlâ gizli kalır. Bunları bir kasada veya gizli enjekte ortam değişkeninde sakla ve diğer tüm belgeler gibi özenle döndür.

def execute_with_role(cursor, role: str, query: str, params: dict):
    """Execute query with specific application role."""
    # Activate application role
    cursor.execute(
        "EXECUTE sp_setapprole @rolename = %(role)s, @password = %(pwd)s",
        {"role": role, "pwd": os.environ[f"ROLE_{role.upper()}_PWD"]}
    )
    
    try:
        cursor.execute(query, params)
        return cursor.fetchall()
    finally:
        # Reset to original context
        cursor.execute("EXECUTE sp_unsetapprole")

Denetim ve günlüğe kaydetme

Güvenlik olaylarını günlüğe kaydet

Hassas verileri kaydetmeyin, ancak bağlantıların başarısız olması, izin hataları ve şüpheli sorgular gibi güvenlik açısından ilgili olayları kaydedin.

import logging

logger = logging.getLogger("db_security")

def secure_connect(connection_string: str):
    """Connect with security logging."""
    logger.info("Attempting database connection")
    
    try:
        conn = mssql_python.connect(connection_string)
        logger.info("Database connection established")
        return conn
    except mssql_python.OperationalError as e:
        logger.warning(f"Database connection failed: {type(e).__name__}")
        raise

Denetime duyarlı işlemler

Hassas tablolar ve işlemler için veritabanı denetimini yapılandırın. Kritik eylemler için uygulama düzeyinde denetim de uygulayabilirsiniz.

def audit_data_access(cursor, user_id: str, action: str, resource: str):
    """Log data access for audit trail."""
    cursor.execute("""
        INSERT INTO AuditLog (UserID, Action, Resource, Timestamp, IPAddress)
        VALUES (%(user)s, %(action)s, %(resource)s, GETUTCDATE(), %(ip)s)
    """, {
        "user": user_id,
        "action": action,
        "resource": resource,
        "ip": get_client_ip()
    })

Asla hassas verileri kaydetme

Günlük mesajlarında parametre değerlerini eklemeyin. İşlemi kaydet, verileri değil.

Kaçının - değeri günlüğe kaydeder:

nid = "295847284"
logger.debug(f"Query: SELECT * FROM HumanResources.Employee WHERE NationalIDNumber = '{nid}'")

Önerilen - değerleri açığa çıkarmadan niyeti kaydeder:

logger.debug("Executing employee lookup query")
nid = "295847284"
cursor.execute("SELECT * FROM HumanResources.Employee WHERE NationalIDNumber = %(nid)s", {"nid": nid})

Hata yönetimi

İç detayları açığa çıkarmayın

Detaylı hataları dahili olarak kaydlayın ancak veritabanı yapısının sızmasını önlemek için kullanıcılara genel mesajlar gönderin:

def safe_query(cursor, query: str, params: dict):
    """Execute query with safe error handling."""
    try:
        cursor.execute(query, params)
        return cursor.fetchall()
    except mssql_python.ProgrammingError as e:
        # Log full error internally
        logging.error(f"Query error: {e}")
        # Return generic error to user
        raise UserFacingError("An error occurred processing your request")
    except mssql_python.IntegrityError:
        raise UserFacingError("Invalid data provided")

Hata mesajlarını temizle

Veritabanı istisnalarını, uygulama detaylarını ortaya çıkarmayan kullanıcı dostu mesajlara eşleyin:

class UserFacingError(Exception):
    """Exception safe to show to users."""
    pass

def handle_database_error(error: Exception) -> str:
    """Convert database errors to safe user messages."""
    if isinstance(error, mssql_python.IntegrityError):
        if "UNIQUE" in str(error):
            return "A record with this value already exists"
        if "FOREIGN KEY" in str(error):
            return "Referenced item not found"
    
    return "An error occurred. Please try again later."

Güvenlik denetim listesi

Bağlantı güvenlik kontrol listesi

  • [ ] Mümkün olduğunda Microsoft Entra doğrulamasını kullanın.
  • [ ] Şifrelemeyi etkinleştir (Encrypt=yes).
  • [ ] Sunucu sertifikalarını doğrulayın.
  • [ ] Kimlik bilgilerini güvenli bir kasada sakla.
  • [ ] .env dosyalarını yalnızca yerelde tutun ve paylaşılan ortamlarda gizli bilgileri hedef platform aracılığıyla sağlayın.
  • [ ] Azure SQL için TDS 8.0 strict mode kullanın.

Sorgu güvenliği

  • [ ] Her zaman parametrizlenmiş sorgular kullanın.
  • [ ] Dinamik tanımlayıcıları doğrulayın.
  • [ ] Karmaşık mantık için depolanmış prosedürler kullanın.
  • [ ] Sorgu sonuç boyutlarını sınırlayın.

Veri güvenliği

  • [ ] Sunucu düzeyinde veri koruması kullanın (maskeleme, satır seviyesinde güvenlik).
  • [ ] Uygun olduğunda sıra düzeyinde güvenlik kullanın.
  • [ ] Hassas verileri loglarda maskeleyin.

Erişim denetimi

  • [ ] En az ayrıcalık ilkesini kullanın.
  • [ ] Ayrı okuma/yazma hesapları.
  • [ ] İzinleri düzenli olarak denetleyin.
  • [ ] Bağlantı zaman aşımlarını uygulayın.

Monitoring

  • [ ] Güvenlik olaylarını kaydet.
  • [ ] Anormallikleri izle.
  • [ ] Başarısızlıklar için uyarılar ayarlayın.
  • [ ] Düzenli güvenlik denetimleri yapın.