Biztonsági legjobb gyakorlatok mssql-python alkalmazásokhoz

Biztonságossá tegye mssql-python alkalmazásait ezeknek a legjobb gyakorlatoknak a hitelesítés, paraméterezett lekérdezések és adatvédelmi gyakorlatok követésével.

Kezdje jelszó nélküli hitelesítéssel, amikor csak lehet. Kezeld a helyi .env fájlokat és SQL jelszavakat ideiglenes fejlesztési segédeszközként, és a titkokat kezelt identitásokba vagy titkos tárolóba helyezzék, mielőtt a kód elérné a közös környezetet.

A hitelesítés biztonsága

Használd a Microsoft Entra hitelesítést SQL hitelesítésen keresztül

A Microsoft Entra hitelesítés megszünteti a tárolt jelszavakat, és támogatja a menedzselt identitásokat. Minden környezetben jobban választom az SQL hitelesítést helyett.

Az Azure-ban futtatott munkaterhelésekhez használjon egy menedzselt identitást a következővel: ActiveDirectoryMSI. Nincs szüksége tárolt titkos adatokra, és anélkül csatlakozik, hogy végigmenne egy hitelesítőadat-láncon:

import mssql_python

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

Helyi fejlesztéshez használd ActiveDirectoryDefault, amely automatikusan felveszi az Azure CLI-t vagy más fejlesztői hitelesítményeidet. Ne használja éles környezetben, mert DefaultAzureCredential az első kapcsolat során sorrendben végigpróbálja az összes hitelesítőadat-szolgáltatót, ami olyan késleltetést okoz, amelyre az éles környezetben futó számítási feladatoknak nincs szükségük:

import mssql_python

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

Kerüld el: SQL hitelesítés kódban/konfigurációban tárolja a hitelesítő adatokat, és sebezhető a szivárgásokkal.


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

Soha ne égesd bele a hitelesítő adatokat a kódba

Használj környezeti változókat csak helyi fejlesztéshez. Megosztott környezetben a jelszó nélküli hitelesítést részesítjük előnyben. Ha egy örökölt SQL-hitelesítési folyamat elkerülhetetlen, futásidőben kérje le a titkot egy titoktárból, ahelyett, hogy a teljes kapcsolati karakterláncot bejelentené a verziókezelőbe.

Az alábbi megközelítés rögzíti a hitelesítést, és soha nem szabad használni:

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

Helyi fejlesztéshez olvasd el a környezeti változókból származó hitelesítéseket:

import os

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

Azoknál a megosztott környezeteknél, amelyekben továbbra is szükség van titokra, futásidőben kérd le azt az Azure Key Vaultból:

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 injekció megelőzése

Mindig használj paraméterezett lekérdezéseket

A paraméterezett lekérdezések megakadályozzák az SQL beinjekcióját azáltal, hogy elválasztják a felhasználói bemenetet a lekérdezési struktúrától. Mindig paraméterezd a felhasználói bemenetet.

A következő string formátumú lekérdezés sebezhető az SQL injekció szempontjából. Soha ne építs lekérdezéseket így:

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

Az alábbi paraméterezett lekérdezés biztonságos, mert az illesztőprogram külön küldi az értéket a lekérdezési szövegtől:

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

Paraméterezzünk minden lekérdezési komponenseket

Nem lehet közvetlenül paraméterezni a táblázat- és oszlopneveket. Ha ezeket a felhasználói bemenetből helyettesíti be, az alkalmazás sebezhetővé válik az SQL-injekcióval szemben:

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

Ehelyett validáljuk a dinamikus azonosítókat egy engedélyezett értékek listájával szemben, és paraméterezzük a maradék értékeket.

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()

Validáld és sterilizáld a bemenetet

Amikor dinamikus SQL-t építesz azonosítókkal, minden értéket egy szigorú mintázat alapján validálj, mielőtt használnád:

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()}
    """)

Használjon tárolt eljárásokat összetett műveletekhez

A tárolt eljárások csökkentik az alkalmazás kódjának kitett SQL felületét:

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

Kapcsolatbiztonság

Titkosítás szükségessé tétele

Mindig titkosítsd a kapcsolatokat. Az Azure SQL alapértelmezett titkosítást érvényesít. On-premises SQL Server esetén Encrypt=yes állítsuk explicit:

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

Használj TDS 8.0 szigorú módot a legmagasabb biztonság érdekében

A TDS 8.0 a következőket biztosít:

  • TLS 1.3 a csatlakozás kezdetétől
  • Tanúsítvány érvényesítés szükséges
  • Nincs visszalépés a régebbi protokollokhoz
conn = mssql_python.connect(
    "Server=tcp:<server>.database.windows.net,1433;"
    "Database=<database>;"
    "Encrypt=strict"
)

Szerver tanúsítványok validálása

Éles környezetben mindig ellenőrizd a kiszolgáló tanúsítványát, hogy megelőzd a közbeékelődéses támadásokat. Ne állítsd be a(z) TrustServerCertificate=yes értéket, mert az megkerüli az érvényesítést. Ehelyett a TrustServerCertificate=no értéket állítsd be a CA-tanúsítványok ellenőrzésére, a HostNameInCertificate értéket pedig a gazdagépnév ellenőrzésére:

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

Adatvédelem

Érzékeny adatok védelme szerver szinten

A Always Encrypted jelenleg nem konfigurálható mssql-python kapcsolati karakterlánc kulcsszavakkal. Ha Always Encryptedre van szükséged, használd a pyodbc-t az ODBC Driver for SQL Server-lel, ami támogatja azt. Bár nem nyújtanak ugyanilyen szintű védelmet, az SQL Server funkciói, mint a dinamikus adatmaszkolás és a sorszintű biztonság használható az érzékeny oszlopok védelmére.

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

row = cursor.fetchone()

Az átvitel alatt álló adatok védelme

  • Használja a(z) Encrypt=yes kifejezést a kapcsolati sztringekben.
  • Használj VPN-t vagy privát végpontokat az on-premises kapcsolatokhoz.
  • Use Azure Private Link for Azure SQL.

A legkisebb jogosultság elve

Minimális adatbázis-jogosultságok használata

Az alkalmazásfiókoknak minimális jogosultságokkal kell rendelkeznie. Ne használd a sa vagy db_owner elemet alkalmazáskapcsolatokhoz.

  • Csak olvasható tudósítás: GRANT SELECT ON SCHEMA::dbo TO ReportingApp;
  • Konkrét táblázat-hozzáférés: GRANT SELECT, INSERT, UPDATE ON Orders TO OrderProcessor;
  • Csak tárolt eljárás: GRANT EXECUTE ON ProcessOrder TO OrderProcessor;

Különböző fiókokat használj különböző műveletekhez

Minden jogosultsági szintet saját identitással erősítsd meg, hogy egy olvasási út ne tudjon írást végrehajtani. Ez a példa két felhasználó által kijelölt menedzselt identitást használ: az egyik csak olvasási hozzáférést, a másik írási jogot, amelyeket kliensazonosító választ ki:

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;"
    )

Tartsd távol az alkalmazás szereptitkokat a forrásvezérlésből

Az alkalmazás szerepének jelszavak továbbra is titkosak. Tárold őket egy trezorban vagy titkos környezeti változóban, és forgatd őket ugyanolyan gondossággal, mint bármely más hitelminősítéssel.

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")

Ellenőrzés és naplózás

Biztonsági események naplózása

Ne logold az érzékeny adatokat, de jegyzed fel a biztonsági szempontból releváns eseményeket, mint a meghibásodott kapcsolatok, engedélyhibák és gyanús lekérdezések.

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

Auditérzékeny műveletek

Konfiguráld az adatbázis-auditálást érzékeny táblákhoz és műveletekhez. Alkalmazásszintű auditálást is bevezethetsz kritikus műveletekhez.

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()
    })

Sose naplózz érzékeny adatokat

Ne vegyél fel paraméterértékeket a naplóüzenetekben. Naplózza a műveletet, ne az adatokat.

Kerüld el – kiszivárogtatja az értéket a naplóba:

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

Ajánlott – a szándékot logolja értékek felfedése nélkül:

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

Hibakezelés

Ne fedd fel a belső részleteket

A részletes hibákat belsőleg naplózzuk, de a felhasználóknak csak általános hibaüzeneteket adjunk vissza, hogy elkerüljük az adatbázis szerkezetére vonatkozó információk kiszivárgását:

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")

Tisztítsd meg hibaüzeneteket

Térképezze fel az adatbázis kivételeket felhasználóbarát üzenetekhez, amelyek nem fedik meg a megvalósítás részleteit:

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."

Biztonsági ellenőrzőlista

Kapcsolatbiztonsági ellenőrzőlista

  • [ ] Használd a Microsoft Entra hitelesítést, amikor lehetséges.
  • [ ] Titkosítás engedélyezése (Encrypt=yes).
  • [ ] Szerver tanúsítványok validálása.
  • [ ] Tárold a hitelesítési adatokat egy biztonságos trezorban.
  • [ ] A .env fájlokat csak helyben tartsa, és megosztott környezetekben a titkos adatokat a célplatformon keresztül adja meg.
  • [ ] Használd TDS 8.0 strict módot Azure SQL-hez.

Lekérdezésbiztonság

  • [ ] Mindig használj paraméterezett lekérdezéseket.
  • [ ] Validáld a dinamikus azonosítókat.
  • [ ] Használj tárolt eljárásokat összetett logikához.
  • [ ] Korlátozzuk a lekérdezési eredmény méretét.

Adatbiztonság

  • [ ] Használj szerverszintű adatvédelmet (maszkolás, sorszintű biztonság).
  • [ ] Használj sorszintű biztonságot, ahol megfelelő.
  • [ ] Maszkolj érzékeny adatokat a naplókban.

Hozzáférés-kezelés

  • [ ] Használd a legkisebb kiváltság elvét.
  • [ ] Külön olvasási és írási fiókok.
  • [ ] Rendszeresen auditálják a jogosultságokat.
  • [ ] Kapcsolódási időkorlátokat kell bevezetni.

Monitoring

  • [ ] Naplózz biztonsági eseményeket.
  • [ ] Figyeld meg az anomáliákat.
  • [ ] Állíts be riasztásokat hiba esetén.
  • [ ] Rendszeres biztonsági ellenőrzéseket végezzenek.