Osvědčené postupy bezpečnosti s go-mssqldb

Tento článek se zabývá bezpečnostními postupy pro aplikace Go, které se připojují k SQL Server pomocí ovladačego-mssqldb. Zabývá se prevencí injekcí SQL, správou přihlašovacích údajů, konfigurací šifrování a principem nejmenších oprávnění.

Prevence injektáže SQL

SQL injection je nejčastější zranitelnost v oblasti bezpečnosti databází. Ovladač go-mssqldb poskytuje parametrizované dotazy, které oddělují SQL kód od datových hodnot. Vždy používejte parametry pro vstupy zadané uživatelem.

Použití parametrizovaných dotazů

Parametry jsou odesílány odděleně od SQL textu, takže uživatelský vstup nelze nikdy interpretovat jako SQL kód:

// CORRECT: Parameters are sent separately from the SQL text.
rows, err := db.QueryContext(ctx,
    "SELECT * FROM Sales.vSalesPerson WHERE FirstName = @name AND CountryRegionName = @loc",
    sql.Named("name", userName),
    sql.Named("loc", userLocation))

Nikdy nespojujte uživatelský vstup do SQL

Konkatenace řetězců umožňuje útočníkům vkládat libovolné SQL:

// WRONG: SQL injection vulnerability.
query := fmt.Sprintf("SELECT * FROM Sales.vSalesPerson WHERE FirstName = '%s'", userName)
rows, err := db.QueryContext(ctx, query) // If userName is "'; DROP TABLE HumanResources.Department;--" ...

Názvy dynamických tabulek nebo sloupců

Parametrizované dotazy nelze použít pro názvy tabulek, sloupců nebo jiné identifikátory. Pokud vaše aplikace vyžaduje dynamické identifikátory, ověřte je podle seznamu povolených:

// Validate against known-safe values.
var validColumns = map[string]bool{
    "FirstName": true, "CountryRegionName": true, "TerritoryName": true,
}

func queryByColumn(ctx context.Context, db *sql.DB, column, value string) (*sql.Rows, error) {
    if !validColumns[column] {
        return nil, fmt.Errorf("invalid column name: %q", column)
    }
    // Safe to use column directly because it was validated against the allowlist.
    query := fmt.Sprintf("SELECT * FROM Sales.vSalesPerson WHERE [%s] = @p1", column)
    return db.QueryContext(ctx, query, sql.Named("p1", value))
}

Caution

Nikdy neukládejte uživatelem zadaný vstup přímo do SQL identifikátorů, ani při úniku závorek. Vždy ověřujte podle seznamu povolených hodnot.

Uložené procedury a SQL injekce

Uložené procedury automaticky nezabrání SQL injekci. Pokud postup interně používá EXECUTE nebo sp_executesql se spojenými řetězci, může být stále zranitelný. Používejte parametrizované hovory:

// Parameters are passed safely to the procedure.
_, err := db.ExecContext(ctx, "dbo.SearchEmployees",
    sql.Named("searchTerm", userInput))

Spravovat tajné údaje připojovacího řetězce

Spojovací řetězce mohou obsahovat hesla, klientská tajemství a cesty k certifikátům. Nikdy je neukládejte do zdrojového kódu.

Používání proměnných prostředí

Přečtěte celý připojovací řetězec z proměnné prostředí:

connString := os.Getenv("MSSQL_CONNECTION_STRING")
if connString == "" {
    log.Fatal("MSSQL_CONNECTION_STRING environment variable is required")
}
db, err := sql.Open("sqlserver", connString)

Sestavte spojovací řetězce z jednotlivých tajemství

Sestavte připojovací řetězec ze samostatných proměnných prostředí pro každou součást:

import (
    "net/url"
    "os"
)

query := url.Values{}
query.Add("database", os.Getenv("DB_NAME"))
query.Add("encrypt", "true")

password := os.Getenv("DB_PASSWORD")

u := &url.URL{
    Scheme:   "sqlserver",
    User:     url.UserPassword(os.Getenv("DB_USER"), password),
    Host:     os.Getenv("DB_HOST"),
    RawQuery: query.Encode(),
}
db, err := sql.Open("sqlserver", u.String())

Úplně odstraňte hesla

Nejbezpečnější připojovací řetězec nemá vůbec žádné heslo. Použijte autentizaci Microsoft Entra ID s řízenou identitou:

import _ "github.com/microsoft/go-mssqldb/azuread"

// No password, no secret. The managed identity handles authentication.
db, err := sql.Open("azuresql",
    "sqlserver://<server>.database.windows.net?database=AdventureWorks2025&fedauth=ActiveDirectoryDefault&encrypt=true&TrustServerCertificate=false")

Možnosti tajného úložiště

Životní prostředí Doporučené úložiště Poznámky
Místní rozvoj Proměnné prostředí nebo soubor uživatelských tajemství Neodhlašujte .env soubory do správy zdrojového kódu.
Azure App Service / Container Apps Nastavení aplikace nebo odkazy na Key Vault Odkazy na Key Vault vkládají tajné klíče do proměnných prostředí za běhu.
Kubernetes Tajné klíče Kubernetes nebo Azure Key Vault s ovladačem CSI Tajemství jsou uložena jako soubory nebo proměnné prostředí.
Kanály CI/CD Tajné klíče pipeline / tajné klíče GitHub Actions Use ActiveDirectoryServicePrincipal or ActiveDirectoryAzurePipelines for Azure SQL.

Důležité

Přidejte .env, *.pfx a *.pem do souboru .gitignore. Náhodné uložení tajemství do úložiště je jedním z nejčastějších zdrojů úniku přihlašovacích údajů.

Konfigurace šifrování

Používejte šifrování pro všechna vzdálená připojení

Nastavte encrypt=true pro všechna vzdálená připojení. Šifrování, které je potřeba pouze serveru, je zranitelné vůči útokům typu útočník uprostřed (adversary-in-the-middle). Klient musí vynucovat šifrování. Azure SQL umožňuje šifrování ve výchozím nastavení:

sqlserver://<user>:<password>@<server>:1433?database=AdventureWorks2025&encrypt=true

Pro nejsilnější šifrování použijte TDS 8.0

TDS 8.0 (přísný režim) provádí TLS handshake před výměnou jakýchkoli dat protokolu TDS, čímž zabraňuje útokům na snížení protokolu:

sqlserver://<user>:<password>@<server>:1433?database=AdventureWorks2025&encrypt=strict

TDS 8.0 vyžaduje SQL Server 2022 nebo Azure SQL.

Nepoužívej TrustServerCertificate v produkci

Nastavení TrustServerCertificate=true deaktivuje validaci certifikátů, což umožňuje útoky útočníka uprostřed (adversary-in-the-middle):

// DEVELOPMENT ONLY. Never deploy to production.
connString := "sqlserver://<user>:<password>@<server>?database=AdventureWorks2025&TrustServerCertificate=true"

Pro produkci nastavte správnou validaci certifikátů. Viz Šifrování a certifikáty.

Nastavte minimální verzi TLS

Vynuťte TLS 1.2 nebo novější, abyste zabránili útokům na degradaci protokolu.

sqlserver://<user>:<password>@<server>:1433?database=AdventureWorks2025&encrypt=true&tlsmin=1.2

Použití principu nejnižšího oprávnění

Používejte uživatele s databázovým zaměřením

Vytvářejte uzavřené databázové uživatele zaměřené na jednu databázi místo serverových přihlašovacích údajů, které přistupují k více databázím.

-- Create a contained database user (no server-level login needed).
CREATE USER [app_user] WITH PASSWORD = '<password>';

-- Grant only the permissions the application needs.
ALTER ROLE db_datareader ADD MEMBER [app_user];
ALTER ROLE db_datawriter ADD MEMBER [app_user];
GRANT EXECUTE ON SCHEMA::dbo TO [app_user];

Samostatná čtecí a zápisná spojení

Pokud má vaše aplikace odlišné cesty pro čtení a zápis, vytvořte samostatné databázové uživatele s odpovídajícími oprávněními.

// Read-only queries use a restricted user.
readDB, _ := sql.Open("sqlserver",
    "sqlserver://app_reader:password@<server>?database=AdventureWorks2025&ApplicationIntent=ReadOnly&encrypt=true")

// Write operations use a user with write permissions.
writeDB, _ := sql.Open("sqlserver",
    "sqlserver://app_writer:password@<server>?database=AdventureWorks2025&encrypt=true")

Vyhněte se použití SA nebo DBO v aplikacích

sa přihlášení a dbo uživatel mají neomezený přístup. Pokud má aplikace připojená k AS sa zranitelnost proti SQL injekci, útočník získává plnou kontrolu nad databázovým serverem.

Chraňte klíče funkce Always Encrypted

Když používáte Always Encrypted, hlavní klíč sloupců (CMK) chrání šifrovací klíče sloupců (CEK). Zabezpečte CMK odpovídajícím způsobem:

Poskytovatel klíčů Nejlepší praxe
Místní certifikát (PFX) Uložit soubor PFX mimo adresář aplikací. Nastavte omezující oprávnění souboru. Použijte proměnnou prostředí pro heslo.
úložiště certifikátů Windows Použití CurrentUser\My pro servisní účty. Nastavte oprávnění certifikátu pomocí certutil.
Azure Key Vault Používejte spravovanou identitu pro přístup. Nastavte zásady přístupu k trezoru klíčů s nejmenšími oprávněními. Povolte obnovitelné odstranění a ochranu před trvalým odstraněním.

Pro konfiguraci poskytovatele klíčů viz Always Encrypted.

Přihlaste se bezpečně

Nelogujte spojovací řetězce ani přihlašovací údaje

Spojovací řetězce mohou obsahovat hesla, která se nakonec objeví v logovacích souborech:

// WRONG: Logs the password.
log.Printf("Connecting to %s", connString)

// CORRECT: Log only the server and database.
log.Printf("Connecting to server=%s database=%s", serverName, dbName)

Pokud musíte zaznamenat detaily připojení pro diagnostiku, nejprve redigujte připojovací řetězec:

func redactSQLServerURL(raw string) string {
    u, err := url.Parse(raw)
    if err != nil {
        return "<invalid connection string>"
    }

    if u.User != nil {
        username := u.User.Username()
        if username != "" {
            u.User = url.UserPassword(username, "REDACTED")
        }
    }

    q := u.Query()
    for _, key := range []string{"password", "clientassertion", "systemtoken"} {
        if q.Has(key) {
            q.Set(key, "REDACTED")
        }
    }
    u.RawQuery = q.Encode()
    return u.String()
}

Zaznamenávejte cenzurovanou hodnotu jen tehdy, když ji potřebujete pro krátkodobou diagnostiku. Preferujte samostatné zaznamenávání jména serveru, názvu databáze a autentizačního režimu.

Nezaznamenávejte citlivé parametry dotazu

Vyhněte se zaznamenávání hodnot parametrů, které obsahují osobní nebo citlivá data:

// WRONG: Logs sensitive parameter values.
log.Printf("Query: SELECT * FROM HumanResources.Employee WHERE NationalIDNumber = %s", nationalIDNumber)

// CORRECT: Log the query structure without parameter values.
log.Printf("Querying HumanResources.Employee by NationalIDNumber")

Používejte logové vlajky při výrobě opatrně

Parametr ovladače může vypisovat log SQL příkazy a hodnoty parametrů:

Flag Riziko Produkční bezpečí?
1 (chyby) Nízká úroveň Ano
2 (zprávy) Nízká úroveň Ano
4 (řady) Střední, zpřístupňuje data Ne
8 (SQL) Medium, zpřístupňuje dotazy Podmíněný
16 (params) Vysoká, zobrazuje hodnoty parametrů Ne
32 (transakce) Nízká úroveň Ano
64 (ladění) Vysoko, odhaluje detaily protokolu Ne

Pro produkci použijte log=1 (pouze chyby) nebo log=3 (chyby + zprávy).

Kontrolní seznam zabezpečení

Area Recommendation
Injektáž SQL Pro veškerý uživatelský vstup používejte parametrizované dotazy. Ověřte dynamické identifikátory vůči seznamu povolených.
Credentials Pokud je to možné, používejte spravovanou identitu Microsoft Entra. Nikdy nevkládejte hesla napevno do zdrojového kódu.
Encryption Nastavte encrypt=true nebo encrypt=strict pro všechny produkční spojení. Nastavte tlsmin=1.2.
Certifikáty Nepoužívejte TrustServerCertificate=true v produkčním prostředí. Ověřte serverové certifikáty.
Nejnižší oprávnění Vytvořte uživatele databáze specifické pro aplikaci pouze s požadovanými oprávněními.
Secrets Ukládejte spojovací řetězce do proměnných prostředí, do Key Vaultu nebo do úložišť tajných údajů platformy.
Logging Nezaznamenávejte spojovací řetězce, hesla ani hodnoty citlivých parametrů.
Správa zdrojového kódu Přidejte .env, *.pfx, a *.pem k .gitignore.