Files
grafana/pkg/tsdb/mssql/kerberos/kerberos.go
Andreas Christou cc09f38421 MSSQL: Correctly type UDPConnectionLimit (#99011)
* Correctly type UDPConnectionLimit

* Update tests

* Handle legacy case
2025-01-27 15:53:46 +00:00

132 lines
4.3 KiB
Go

package kerberos
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
)
type KerberosLookup struct {
User string `json:"user"`
DBName string `json:"database"`
Address string `json:"address"`
CredentialCacheFilename string `json:"credentialCache"`
}
type KerberosAuth struct {
KeytabFilePath string
CredentialCache string
CredentialCacheLookupFile string
ConfigFilePath string
UDPConnectionLimit int
EnableDNSLookupKDC string
}
func GetKerberosSettings(settings backend.DataSourceInstanceSettings) (kerberosAuth KerberosAuth, err error) {
kerberosAuth = KerberosAuth{
KeytabFilePath: "",
CredentialCache: "",
CredentialCacheLookupFile: "",
ConfigFilePath: "",
UDPConnectionLimit: 1,
EnableDNSLookupKDC: "",
}
err = json.Unmarshal(settings.JSONData, &kerberosAuth)
var unmarshalErr *json.UnmarshalTypeError
if err != nil && errors.As(err, &unmarshalErr) {
stringMap := map[string]any{}
err = json.Unmarshal(settings.JSONData, &stringMap)
if err != nil {
return kerberosAuth, err
}
if stringMap["UDPConnectionLimit"] != "" {
udpConnLimit, err := strconv.Atoi(stringMap["UDPConnectionLimit"].(string))
if err != nil {
return kerberosAuth, err
}
kerberosAuth.UDPConnectionLimit = udpConnLimit
}
}
return kerberosAuth, err
}
func Krb5ParseAuthCredentials(host string, port string, db string, user string, pass string, kerberosAuth KerberosAuth) string {
//params for driver conn str
//More details: https://github.com/microsoft/go-mssqldb#kerberos-active-directory-authentication-outside-windows
krb5CCLookupFile := kerberosAuth.CredentialCacheLookupFile
krb5CacheCredsFile := kerberosAuth.CredentialCache
// if there is a lookup file specified, use it to find the correct credential cache file and overwrite var
// getCredentialCacheFromLookup implementation taken from mysql kerberos solution - https://github.com/grafana/mysql/commit/b5e73c8d536150c054d310123643683d3b18f0da
if krb5CCLookupFile != "" {
krb5CacheCredsFile = getCredentialCacheFromLookup(krb5CCLookupFile, host, port, db, user)
if krb5CacheCredsFile == "" {
logger.Error("No valid credential cache file found in lookup.")
return ""
}
}
krb5DriverParams := fmt.Sprintf("authenticator=krb5;krb5-configfile=%s;", kerberosAuth.ConfigFilePath)
// There are 3 main connection types:
// - credentials cache
// - user, realm, keytab
// - realm, user, pass
if krb5CacheCredsFile != "" {
krb5DriverParams += fmt.Sprintf("server=%s;database=%s;krb5-credcachefile=%s;", host, db, krb5CacheCredsFile)
} else if kerberosAuth.KeytabFilePath != "" {
krb5DriverParams += fmt.Sprintf("server=%s;database=%s;user id=%s;krb5-keytabfile=%s;", host, db, user, kerberosAuth.KeytabFilePath)
} else if kerberosAuth.KeytabFilePath == "" {
krb5DriverParams += fmt.Sprintf("server=%s;database=%s;user id=%s;password=%s;", host, db, user, pass)
} else {
logger.Error("invalid kerberos configuration")
return ""
}
if kerberosAuth.UDPConnectionLimit != 1 {
krb5DriverParams += fmt.Sprintf("krb5-udppreferencelimit=%d;", kerberosAuth.UDPConnectionLimit)
}
if kerberosAuth.EnableDNSLookupKDC != "" {
krb5DriverParams += "krb5-dnslookupkdc=" + kerberosAuth.EnableDNSLookupKDC + ";"
}
return krb5DriverParams
}
func getCredentialCacheFromLookup(lookupFile string, host string, port string, dbName string, user string) string {
logger.Info(fmt.Sprintf("reading credential cache lookup: %s", lookupFile))
content, err := os.ReadFile(filepath.Clean(lookupFile))
if err != nil {
logger.Error(fmt.Sprintf("error reading: %s, %v", lookupFile, err))
return ""
}
var lookups []KerberosLookup
err = json.Unmarshal(content, &lookups)
if err != nil {
logger.Error(fmt.Sprintf("error parsing: %s, %v", lookupFile, err))
return ""
}
// find cache file
for _, item := range lookups {
if port == "0" {
item.Address = host + ":0"
}
if item.Address == host+":"+port && item.DBName == dbName && item.User == user {
logger.Info(fmt.Sprintf("matched: %+v", item))
return item.CredentialCacheFilename
}
}
logger.Error(fmt.Sprintf("no match found for %s", host+":"+port))
return ""
}