diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a7e2a67..5c4bb5f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2,5 +2,5 @@ # the repo. Unless a later match takes precedence, these # users will be requested for review when someone opens a # pull request. -* @yunbodeng-db @andrefurlan-db @rcypher-databricks @jadewang-db @kravets-levko +* @yunbodeng-db @andrefurlan-db @rcypher-databricks @jadewang-db @kravets-levko @jackyhu-db diff --git a/CHANGELOG.md b/CHANGELOG.md index f52bb0c..9aac66a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Release History +## v1.6.0 (2024-07-31) + +- Security: Resolve HIGH vulnerability in x/net (CVE-2023-39325) (databricks/databricks-sql-go#233 by @anthonycrobinson) +- Expose `dbsql.ConnOption` type (databricks/databricks-sql-go#202 by @shelldandy) +- Fix a connection leak in PingContext (databricks/databricks-sql-go#240 by @jackyhu-db) + ## v1.5.7 (2024-06-05) - Reverted dependencies upgrade because of compatibility issues (databricks/databricks-sql-go#228) diff --git a/auth/oauth/m2m/m2m.go b/auth/oauth/m2m/m2m.go index 7b68404..fd67a1f 100644 --- a/auth/oauth/m2m/m2m.go +++ b/auth/oauth/m2m/m2m.go @@ -1,8 +1,5 @@ package m2m -// clientid e92aa085-4875-42fe-ad75-ba38fb3c9706 -// secretid vUdzecmn4aUi2jRDamaBOy3qThu9LSgeV_BW4UnQ - import ( "context" "fmt" diff --git a/connection.go b/connection.go index 857f9f0..b3d0479 100644 --- a/connection.go +++ b/connection.go @@ -78,11 +78,12 @@ func (c *conn) Ping(ctx context.Context) error { ctx1, cancel := context.WithTimeout(ctx, c.cfg.PingTimeout) defer cancel() - _, err := c.QueryContext(ctx1, "select 1", nil) + rows, err := c.QueryContext(ctx1, "select 1", nil) if err != nil { log.Err(err).Msg("databricks: failed to ping") return dbsqlerrint.NewBadConnectionError(err) } + defer rows.Close() log.Debug().Msg("databricks: ping successful") return nil diff --git a/connection_test.go b/connection_test.go index 620a194..7c60709 100644 --- a/connection_test.go +++ b/connection_test.go @@ -1370,9 +1370,14 @@ func TestConn_Ping(t *testing.T) { return getOperationStatusResp, nil } + var closeCount int testClient := &client.TestClient{ FnExecuteStatement: executeStatement, FnGetOperationStatus: getOperationStatus, + FnCloseOperation: func(ctx context.Context, req *cli_service.TCloseOperationReq) (_r *cli_service.TCloseOperationResp, _err error) { + closeCount++ + return nil, nil + }, } testConn := &conn{ @@ -1384,6 +1389,7 @@ func TestConn_Ping(t *testing.T) { assert.Nil(t, err) assert.Equal(t, 1, executeStatementCount) + assert.Equal(t, 1, closeCount) }) } diff --git a/connector.go b/connector.go index bab81fd..a5b8000 100644 --- a/connector.go +++ b/connector.go @@ -51,7 +51,6 @@ func (c *connector) Connect(ctx context.Context) (driver.Conn, error) { }, CanUseMultipleCatalogs: &c.cfg.CanUseMultipleCatalogs, }) - if err != nil { return nil, dbsqlerrint.NewRequestError(ctx, fmt.Sprintf("error connecting: host=%s port=%d, httpPath=%s", c.cfg.Host, c.cfg.Port, c.cfg.HTTPPath), err) } @@ -84,11 +83,11 @@ func (c *connector) Driver() driver.Driver { var _ driver.Connector = (*connector)(nil) -type connOption func(*config.Config) +type ConnOption func(*config.Config) // NewConnector creates a connection that can be used with `sql.OpenDB()`. // This is an easier way to set up the DB instead of having to construct a DSN string. -func NewConnector(options ...connOption) (driver.Connector, error) { +func NewConnector(options ...ConnOption) (driver.Connector, error) { // config with default options cfg := config.WithDefaults() cfg.DriverVersion = DriverVersion @@ -102,14 +101,14 @@ func NewConnector(options ...connOption) (driver.Connector, error) { return &connector{cfg: cfg, client: client}, nil } -func withUserConfig(ucfg config.UserConfig) connOption { +func withUserConfig(ucfg config.UserConfig) ConnOption { return func(c *config.Config) { c.UserConfig = ucfg } } // WithServerHostname sets up the server hostname. Mandatory. -func WithServerHostname(host string) connOption { +func WithServerHostname(host string) ConnOption { return func(c *config.Config) { protocol, hostname := parseHostName(host) if protocol != "" { @@ -143,7 +142,7 @@ func parseHostName(host string) (protocol, hostname string) { } // WithPort sets up the server port. Mandatory. -func WithPort(port int) connOption { +func WithPort(port int) ConnOption { return func(c *config.Config) { c.Port = port } @@ -153,7 +152,7 @@ func WithPort(port int) connOption { // By default retryWaitMin = 1 * time.Second // By default retryWaitMax = 30 * time.Second // By default retryMax = 4 -func WithRetries(retryMax int, retryWaitMin time.Duration, retryWaitMax time.Duration) connOption { +func WithRetries(retryMax int, retryWaitMin time.Duration, retryWaitMax time.Duration) ConnOption { return func(c *config.Config) { c.RetryWaitMax = retryWaitMax c.RetryWaitMin = retryWaitMin @@ -162,7 +161,7 @@ func WithRetries(retryMax int, retryWaitMin time.Duration, retryWaitMax time.Dur } // WithAccessToken sets up the Personal Access Token. Mandatory for now. -func WithAccessToken(token string) connOption { +func WithAccessToken(token string) ConnOption { return func(c *config.Config) { if token != "" { c.AccessToken = token @@ -175,7 +174,7 @@ func WithAccessToken(token string) connOption { } // WithHTTPPath sets up the endpoint to the warehouse. Mandatory. -func WithHTTPPath(path string) connOption { +func WithHTTPPath(path string) ConnOption { return func(c *config.Config) { if !strings.HasPrefix(path, "/") { path = "/" + path @@ -185,7 +184,7 @@ func WithHTTPPath(path string) connOption { } // WithMaxRows sets up the max rows fetched per request. Default is 10000 -func WithMaxRows(n int) connOption { +func WithMaxRows(n int) ConnOption { return func(c *config.Config) { if n != 0 { c.MaxRows = n @@ -194,7 +193,7 @@ func WithMaxRows(n int) connOption { } // WithTimeout adds timeout for the server query execution. Default is no timeout. -func WithTimeout(n time.Duration) connOption { +func WithTimeout(n time.Duration) ConnOption { return func(c *config.Config) { c.QueryTimeout = n } @@ -202,7 +201,7 @@ func WithTimeout(n time.Duration) connOption { // Sets the initial catalog name and schema name in the session. // Use -func WithInitialNamespace(catalog, schema string) connOption { +func WithInitialNamespace(catalog, schema string) ConnOption { return func(c *config.Config) { c.Catalog = catalog c.Schema = schema @@ -210,7 +209,7 @@ func WithInitialNamespace(catalog, schema string) connOption { } // Used to identify partners. Set as a string with format . -func WithUserAgentEntry(entry string) connOption { +func WithUserAgentEntry(entry string) ConnOption { return func(c *config.Config) { c.UserAgentEntry = entry } @@ -218,7 +217,7 @@ func WithUserAgentEntry(entry string) connOption { // Sessions params will be set upon opening the session by calling SET function. // If using connection pool, session params can avoid successive calls of "SET ..." -func WithSessionParams(params map[string]string) connOption { +func WithSessionParams(params map[string]string) ConnOption { return func(c *config.Config) { for k, v := range params { if strings.ToLower(k) == "timezone" { @@ -227,7 +226,6 @@ func WithSessionParams(params map[string]string) connOption { } else { c.Location = loc } - } } c.SessionParams = params @@ -238,7 +236,7 @@ func WithSessionParams(params map[string]string) connOption { // WARNING: // When this option is used, TLS is susceptible to machine-in-the-middle attacks. // Please only use this option when the hostname is an internal private link hostname -func WithSkipTLSHostVerify() connOption { +func WithSkipTLSHostVerify() ConnOption { return func(c *config.Config) { if c.TLSConfig == nil { c.TLSConfig = &tls.Config{MinVersion: tls.VersionTLS12, InsecureSkipVerify: true} // #nosec G402 @@ -249,35 +247,35 @@ func WithSkipTLSHostVerify() connOption { } // WithAuthenticator sets up the Authentication. Mandatory if access token is not provided. -func WithAuthenticator(authr auth.Authenticator) connOption { +func WithAuthenticator(authr auth.Authenticator) ConnOption { return func(c *config.Config) { c.Authenticator = authr } } // WithTransport sets up the transport configuration to be used by the httpclient. -func WithTransport(t http.RoundTripper) connOption { +func WithTransport(t http.RoundTripper) ConnOption { return func(c *config.Config) { c.Transport = t } } // WithCloudFetch sets up the use of cloud fetch for query execution. Default is false. -func WithCloudFetch(useCloudFetch bool) connOption { +func WithCloudFetch(useCloudFetch bool) ConnOption { return func(c *config.Config) { c.UseCloudFetch = useCloudFetch } } // WithMaxDownloadThreads sets up maximum download threads for cloud fetch. Default is 10. -func WithMaxDownloadThreads(numThreads int) connOption { +func WithMaxDownloadThreads(numThreads int) ConnOption { return func(c *config.Config) { c.MaxDownloadThreads = numThreads } } // Setup of Oauth M2m authentication -func WithClientCredentials(clientID, clientSecret string) connOption { +func WithClientCredentials(clientID, clientSecret string) ConnOption { return func(c *config.Config) { if clientID != "" && clientSecret != "" { authr := m2m.NewAuthenticator(clientID, clientSecret, c.Host) diff --git a/driver.go b/driver.go index 34ef2b1..60ef432 100644 --- a/driver.go +++ b/driver.go @@ -13,7 +13,7 @@ func init() { sql.Register("databricks", &databricksDriver{}) } -var DriverVersion = "1.5.7" // update version before each release +var DriverVersion = "1.6.0" // update version before each release type databricksDriver struct{}