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) } }