116 lines
3.4 KiB
Go
116 lines
3.4 KiB
Go
package mysql_test
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"git.yuntuo.hk/k2safe/go-omni/database"
|
|
adapter "git.yuntuo.hk/k2safe/go-omni/database/mysql"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestStructuredMySQLAdapterTLS(t *testing.T) {
|
|
if os.Getenv("GO_OMNI_CONNECTOR_MYSQL_TEST") != "1" {
|
|
t.Skip("use scripts/verifyConnector.py with an isolated local MySQL")
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
|
defer cancel()
|
|
cfg := adapter.DefaultConfig()
|
|
host, port, err := net.SplitHostPort(os.Getenv("GO_OMNI_CONNECTOR_ADDR"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cfg.Host = host
|
|
cfg.Port, _ = strconv.Atoi(port)
|
|
cfg.User = os.Getenv("GO_OMNI_CONNECTOR_USER")
|
|
cfg.Password = os.Getenv("GO_OMNI_CONNECTOR_PASSWORD")
|
|
cfg.Database = os.Getenv("GO_OMNI_CONNECTOR_DATABASE")
|
|
cfg.TLS.CAFile = os.Getenv("GO_OMNI_CONNECTOR_CA_FILE")
|
|
cfg.TLS.ServerName = "localhost"
|
|
cfg.Pool.MaxOpenConns = 2
|
|
cfg.Pool.MaxIdleConns = 2
|
|
cfg.Pool.PingTimeout = 2 * time.Second
|
|
cfg.Session = map[string]string{"time_zone": "'+00:00'"}
|
|
checked := false
|
|
first, err := adapter.Open(ctx, cfg, adapter.WithStartupCheck(func(ctx context.Context, db *database.DB) error {
|
|
var one int
|
|
err := db.QueryRowContext(ctx, "SELECT 1").Scan(&one)
|
|
checked = one == 1
|
|
return err
|
|
}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer first.Close()
|
|
if !checked {
|
|
t.Fatal("startup check not called")
|
|
}
|
|
cfg.Session = map[string]string{"time_zone": "'+08:00'"}
|
|
second, err := adapter.Open(ctx, cfg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer second.Close()
|
|
for i, db := range []*database.DB{first, second} {
|
|
var key, cipher, zone string
|
|
if err = db.QueryRowContext(ctx, "SHOW SESSION STATUS LIKE 'Ssl_cipher'").Scan(&key, &cipher); err != nil || cipher == "" {
|
|
t.Fatal("not encrypted", err)
|
|
}
|
|
if err = db.QueryRowContext(ctx, "SELECT @@session.time_zone").Scan(&zone); err != nil || zone != []string{"+00:00", "+08:00"}[i] {
|
|
t.Fatal("session isolation failed", zone, err)
|
|
}
|
|
if db.Stats().MaxOpenConnections != 2 {
|
|
t.Fatal("pool limit ignored")
|
|
}
|
|
}
|
|
for _, bad := range []string{"untrusted", "hostname"} {
|
|
invalid := cfg
|
|
if bad == "untrusted" {
|
|
invalid.TLS.CAFile = ""
|
|
} else {
|
|
invalid.TLS.ServerName = "wrong.invalid"
|
|
}
|
|
db, e := adapter.Open(ctx, invalid)
|
|
if db != nil {
|
|
_ = db.Close()
|
|
t.Fatal("bad certificate accepted")
|
|
}
|
|
var verify *tls.CertificateVerificationError
|
|
if !errors.As(e, &verify) {
|
|
t.Fatal("lost TLS verification cause", e)
|
|
}
|
|
}
|
|
cause := errors.New("project schema check failed")
|
|
if db, e := adapter.Open(ctx, cfg, adapter.WithStartupCheck(func(context.Context, *database.DB) error { return cause })); db != nil || !errors.Is(e, cause) {
|
|
t.Fatal("failed startup escaped", e)
|
|
}
|
|
if err = first.Health(ctx); err != nil {
|
|
t.Fatal("failed pool affected healthy pool", err)
|
|
}
|
|
// The server requires a client certificate for this separate fixture user.
|
|
cfg.User = "omni_mtls"
|
|
cfg.Password = ""
|
|
cfg.Database = ""
|
|
cfg.Session = nil
|
|
if db, e := adapter.Open(ctx, cfg); e == nil {
|
|
db.Close()
|
|
t.Fatal("missing required client certificate accepted")
|
|
}
|
|
cfg.TLS.CertFile = os.Getenv("GO_OMNI_CONNECTOR_CLIENT_CERT")
|
|
cfg.TLS.KeyFile = os.Getenv("GO_OMNI_CONNECTOR_CLIENT_KEY")
|
|
mtls, err := adapter.Open(ctx, cfg)
|
|
if err != nil {
|
|
t.Fatal("mTLS failed", err)
|
|
}
|
|
if err = mtls.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = mtls.Close(); err != nil {
|
|
t.Fatal("pool close is not idempotent", err)
|
|
}
|
|
}
|