Skip to content

Commit

Permalink
GH-35042: [Go][FlightSQL driver] Add TLS configuration (#35051)
Browse files Browse the repository at this point in the history
This PR adds the ability to enable and customize TLS parameters for Golang's `database/sql` driver.
* Closes: #35042

Lead-authored-by: Sven Rebhan <[email protected]>
Co-authored-by: Sven Rebhan <[email protected]>
Co-authored-by: Kemal <[email protected]>
Signed-off-by: Matt Topol <[email protected]>
  • Loading branch information
srebhan and disq authored Apr 12, 2023
1 parent 5184d7c commit 631c68a
Show file tree
Hide file tree
Showing 5 changed files with 618 additions and 13 deletions.
81 changes: 78 additions & 3 deletions go/arrow/flight/flightsql/driver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ connection pooling, transactions combined with ease of use (see (#usage)).

---------------------------------------

## Prerequisits
## Prerequisites

* Go 1.19+
* Go 1.17+
* Installation via `go get -u github.com/apache/arrow/go/v12/arrow/flight/flightsql`
* Backend speaking FlightSQL

Expand Down Expand Up @@ -111,6 +111,23 @@ to limit the maximum time an operation can take. This prevents calls that wait
forever, e.g. if the backend is down or a query is taking very long. When
not set, the driver will use an _infinite_ timeout.

#### `tls`

The `tls` parameter allows to enable and customize Transport-Layer-Security
settings. There are some special values for the parameters:

* `disabled` or `false` will disable TLS for this server connection. In this
case all other settings are ignored.
* `enabled` or `true` will force TLS for this server connection. In this case
the system settings for trusted CAs etc will be used.
* `skip-verify` will enable TLS for this server connection but will not verify
the server certificate. **This is a security risk and should not be used!**

Any other value will be interpreted as the name of a custom configuration. Those
configurations must be registered either by
[creating the DSN from configuration](#driver-config-usage) or by calling
`RegisterTLSConfig()` (see [TLS setup](#tls-setup) for details).

## Driver config usage

Alternatively to specifying the DSN directly you can fill the `DriverConfig`
Expand Down Expand Up @@ -148,4 +165,62 @@ func main() {

## TLS setup

Currently TLS is not yet supported and will be added later.
By specifying the [`tls` parameter](#tls) you can enable
Transport-Layer-Security. Using `tls=enabled` the system settings are used for
verifying the server's certificate. Custom TLS configurations, e.g. when using
self-signed certificates, are referenced by a user-selected name. The underlying
TLS configuration needs to be registered (using the same name) in two ways.

### TLS setup using `DriverConfig`

The first way is to create a `DriverConfig` with the `TLSConfig` field set to
the custom config and `TLSConfigName` set to the chosen name. For example

```golang
...

config := flightsql.DriverConfig{
Address: "localhost:12345",
TLSEnabled: true,
TLSConfigName: "myconfig",
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
},
}
dsn := config.DSN()

...
```

will enable TLS forcing the minimum TLS version to 1.2. This custom config will
be registered with the name `myconfig` and the resulting DSN reads

```text
flightsql://localhost:12345?tls=myconfig`
```

If the `TLSConfigName` is omitted a random unique name (UUID) is generated and
referenced in the DSN. This prevents errors from using an already registered
name leading to errors.

### TLS setup using manual registration

The second alternative is the manual registration of the custom TLS
configuration. In this case you need to call `RegisterTLSConfig()` in your code

```golang
myconfig := &tls.Config{MinVersion: tls.VersionTLS12}
if err := flightsql.RegisterTLSConfig("myconfig", myconfig); err != nil {
...
}
dsn := "flightsql://localhost:12345?tls=myconfig"

...
```

This will register the custom configuration, constraining the minimim TLS
version, as `myconfig` and then references the registered configuration by
name in the DSN. You can reuse the same TLS configuration by registering once
and then reference in multiple DSNs. Registering multiple configurations with
the same name will throw an error to prevent unintended side-effects due to the
driver-global registry.
90 changes: 87 additions & 3 deletions go/arrow/flight/flightsql/driver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,53 @@ import (
"crypto/tls"
"fmt"
"net/url"
"sync"
"time"

"github.com/google/uuid"
)

// TLS configuration registry
var (
tlsConfigRegistry = map[string]*tls.Config{
"skip-verify": {InsecureSkipVerify: true},
}
tlsRegistryMutex sync.Mutex
)

func RegisterTLSConfig(name string, cfg *tls.Config) error {
tlsRegistryMutex.Lock()
defer tlsRegistryMutex.Unlock()

// Prevent name collisions
if _, found := tlsConfigRegistry[name]; found {
return ErrRegistryEntryExists
}
tlsConfigRegistry[name] = cfg

return nil
}

func UnregisterTLSConfig(name string) error {
tlsRegistryMutex.Lock()
defer tlsRegistryMutex.Unlock()

if _, found := tlsConfigRegistry[name]; !found {
return ErrRegistryNoEntry
}

delete(tlsConfigRegistry, name)
return nil
}

func GetTLSConfig(name string) (*tls.Config, bool) {
tlsRegistryMutex.Lock()
defer tlsRegistryMutex.Unlock()

cfg, found := tlsConfigRegistry[name]
return cfg, found
}

type DriverConfig struct {
Address string
Username string
Expand All @@ -30,14 +74,15 @@ type DriverConfig struct {
Timeout time.Duration
Params map[string]string

TLSEnabled bool
TLSConfig *tls.Config
TLSEnabled bool
TLSConfigName string
TLSConfig *tls.Config
}

func NewDriverConfigFromDSN(dsn string) (*DriverConfig, error) {
u, err := url.Parse(dsn)
if err != nil {
return nil, err
return nil, fmt.Errorf("invalid URL: %w", err)
}

// Sanity checks on the given connection string
Expand Down Expand Up @@ -83,6 +128,21 @@ func NewDriverConfigFromDSN(dsn string) (*DriverConfig, error) {
if err != nil {
return nil, err
}
case "tls":
switch v {
case "true", "enabled":
config.TLSEnabled = true
case "false", "disabled":
config.TLSEnabled = false
default:
config.TLSEnabled = true
config.TLSConfigName = v
cfg, found := GetTLSConfig(config.TLSConfigName)
if !found {
return nil, fmt.Errorf("%q TLS %w", config.TLSConfigName, ErrRegistryNoEntry)
}
config.TLSConfig = cfg
}
default:
config.Params[key] = v
}
Expand Down Expand Up @@ -112,6 +172,30 @@ func (config *DriverConfig) DSN() string {
if config.Timeout > 0 {
values.Add("timeout", config.Timeout.String())
}
if config.TLSEnabled {
switch config.TLSConfigName {
case "skip-verify":
values.Add("tls", "skip-verify")
case "":
// Use system defaults if no config is given
if config.TLSConfig == nil {
values.Add("tls", "enabled")
break
}
// We got a custom TLS configuration but no name, create a unique one
config.TLSConfigName = uuid.NewString()
fallthrough
default:
values.Add("tls", config.TLSConfigName)
if config.TLSConfig != nil {
// Ignore the returned error as we do not care if the config
// was registered before. If this fails and the config is not
// yet registered, the driver will error out when parsing the
// DSN.
_ = RegisterTLSConfig(config.TLSConfigName, config.TLSConfig)
}
}
}
for k, v := range config.Params {
values.Add(k, v)
}
Expand Down
Loading

0 comments on commit 631c68a

Please sign in to comment.