mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-01 05:27:24 +08:00
* refactor(sqlserver): change to microsoft fork * chore(gomod): update mssqldb dep * test(sqlserver): add server on parsing PLP RPC packets * fix(db): change mssql import * chore(gomod): update go-mssqldb reference * test(protocol): use fuzz arguments * chore: code review suggestions * test(sqlserver): add missing protocols attribute
241 lines
6.9 KiB
Go
241 lines
6.9 KiB
Go
/*
|
|
Copyright 2022 Gravitational, Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package sqlserver
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net"
|
|
"strconv"
|
|
|
|
"github.com/gravitational/trace"
|
|
mssql "github.com/microsoft/go-mssqldb"
|
|
"github.com/microsoft/go-mssqldb/msdsn"
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"github.com/gravitational/teleport/lib/defaults"
|
|
"github.com/gravitational/teleport/lib/srv/db/common"
|
|
"github.com/gravitational/teleport/lib/srv/db/sqlserver/protocol"
|
|
"github.com/gravitational/teleport/lib/utils"
|
|
)
|
|
|
|
// MakeTestClient returns SQL Server client used in tests.
|
|
func MakeTestClient(ctx context.Context, config common.TestClientConfig) (*mssql.Conn, error) {
|
|
host, port, err := net.SplitHostPort(config.Address)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
portI, err := strconv.ParseUint(port, 10, 64)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
connector := mssql.NewConnectorConfig(msdsn.Config{
|
|
Host: host,
|
|
Port: portI,
|
|
User: config.RouteToDatabase.Username,
|
|
Database: config.RouteToDatabase.Database,
|
|
Encryption: msdsn.EncryptionDisabled,
|
|
Protocols: []string{"tcp"},
|
|
}, nil)
|
|
|
|
conn, err := connector.Connect(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
mssqlConn, ok := conn.(*mssql.Conn)
|
|
if !ok {
|
|
return nil, trace.BadParameter("expected *mssql.Conn, got: %T", conn)
|
|
}
|
|
|
|
return mssqlConn, nil
|
|
}
|
|
|
|
// TestConnector is used in tests to mock connections to SQL Server.
|
|
type TestConnector struct{}
|
|
|
|
// Connect simulates successful connection to a SQL Server.
|
|
func (c *TestConnector) Connect(ctx context.Context, sessionCtx *common.Session, loginPacket *protocol.Login7Packet) (io.ReadWriteCloser, []mssql.Token, error) {
|
|
host, port, err := net.SplitHostPort(sessionCtx.Database.GetURI())
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
|
|
portI, err := strconv.ParseUint(port, 10, 64)
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
|
|
// Pass all login options from the client to the server.
|
|
options := msdsn.LoginOptions{
|
|
OptionFlags1: loginPacket.OptionFlags1(),
|
|
OptionFlags2: loginPacket.OptionFlags2(),
|
|
TypeFlags: loginPacket.TypeFlags(),
|
|
}
|
|
|
|
connector := mssql.NewConnectorConfig(msdsn.Config{
|
|
Host: host,
|
|
Port: portI,
|
|
LoginOptions: options,
|
|
Protocols: []string{"tcp"},
|
|
}, nil)
|
|
|
|
conn, err := connector.Connect(ctx)
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
|
|
mssqlConn, ok := conn.(*mssql.Conn)
|
|
if !ok {
|
|
return nil, nil, trace.BadParameter("expected *mssql.Conn, got: %T", conn)
|
|
}
|
|
|
|
return mssqlConn.GetUnderlyingConn(), mssqlConn.GetLoginFlags(), nil
|
|
}
|
|
|
|
// TestServer is a test MSServer server used in functional database
|
|
// access tests.
|
|
type TestServer struct {
|
|
cfg common.TestServerConfig
|
|
listener net.Listener
|
|
port string
|
|
log logrus.FieldLogger
|
|
}
|
|
|
|
// NewTestServer returns a new instance of a test MSServer.
|
|
func NewTestServer(config common.TestServerConfig) (svr *TestServer, err error) {
|
|
err = config.CheckAndSetDefaults()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
defer config.CloseOnError(&err)
|
|
|
|
port, err := config.Port()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
log := logrus.WithFields(logrus.Fields{
|
|
trace.Component: defaults.ProtocolSQLServer,
|
|
"name": config.Name,
|
|
})
|
|
server := &TestServer{
|
|
cfg: config,
|
|
listener: config.Listener,
|
|
port: port,
|
|
log: log,
|
|
}
|
|
return server, nil
|
|
}
|
|
|
|
// Port returns SQL Server port.
|
|
func (s *TestServer) Port() string {
|
|
return s.port
|
|
}
|
|
|
|
// Serve starts serving client connections.
|
|
func (s *TestServer) Serve() error {
|
|
s.log.Debugf("Starting test MSServer server on %v.", s.listener.Addr())
|
|
defer s.log.Debug("Test MSServer server stopped.")
|
|
for {
|
|
conn, err := s.listener.Accept()
|
|
if err != nil {
|
|
if utils.IsOKNetworkError(err) {
|
|
return nil
|
|
}
|
|
s.log.WithError(err).Error("Failed to accept connection.")
|
|
continue
|
|
}
|
|
s.log.Debug("Accepted connection.")
|
|
go func() {
|
|
defer s.log.Debug("Connection done.")
|
|
defer conn.Close()
|
|
err = s.handleConnection(conn)
|
|
if err != nil {
|
|
s.log.Errorf("Failed to handle connection: %v.",
|
|
trace.DebugReport(err))
|
|
}
|
|
}()
|
|
}
|
|
}
|
|
|
|
func (s *TestServer) handleConnection(conn net.Conn) error {
|
|
if err := handleLogin(conn); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
for {
|
|
basicPacket, err := protocol.ReadPacket(conn)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
packet, err := protocol.ToSQLPacket(basicPacket)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
switch packet.(type) {
|
|
case *protocol.SQLBatch:
|
|
if _, err = conn.Write(mockSQLBatchServerResp); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func handleLogin(conn net.Conn) error {
|
|
if _, err := protocol.ReadPreLoginPacket(conn); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
if err := protocol.WritePreLoginResponse(conn); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
if _, err := protocol.ReadLogin7Packet(conn); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
if _, err := conn.Write(mockLoginServerResp); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Close closes the server listener.
|
|
func (s *TestServer) Close() error {
|
|
return s.listener.Close()
|
|
}
|
|
|
|
var (
|
|
mockLoginServerResp = []byte{
|
|
0x04, 0x01, 0x00, 0xa7, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x1b, 0x00, 0x01, 0x06, 0x6d, 0x00, 0x61,
|
|
0x00, 0x73, 0x00, 0x74, 0x00, 0x65, 0x00, 0x72, 0x00, 0x06, 0x6d, 0x00, 0x61, 0x00, 0x73, 0x00,
|
|
0x74, 0x00, 0x65, 0x00, 0x72, 0x00, 0xe3, 0x08, 0x00, 0x07, 0x05, 0x09, 0x04, 0xd0, 0x00, 0x34,
|
|
0x00, 0xe3, 0x17, 0x00, 0x02, 0x0a, 0x75, 0x00, 0x73, 0x00, 0x5f, 0x00, 0x65, 0x00, 0x6e, 0x00,
|
|
0x67, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x73, 0x00, 0x68, 0x00, 0x00, 0xad, 0x36, 0x00, 0x01, 0x74,
|
|
0x00, 0x00, 0x04, 0x16, 0x4d, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00,
|
|
0x6f, 0x00, 0x66, 0x00, 0x74, 0x00, 0x20, 0x00, 0x53, 0x00, 0x51, 0x00, 0x4c, 0x00, 0x20, 0x00,
|
|
0x53, 0x00, 0x65, 0x00, 0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x0f, 0x00, 0x0f, 0xe9, 0xe3, 0x13, 0x00, 0x04, 0x04, 0x34, 0x00, 0x30, 0x00, 0x39, 0x00, 0x36,
|
|
0x00, 0x04, 0x34, 0x00, 0x30, 0x00, 0x39, 0x00, 0x36, 0x00, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
}
|
|
mockSQLBatchServerResp = []byte{
|
|
0x04, 0x01, 0x00, 0x2b, 0x00, 0x4a, 0x01, 0x00, 0xe3, 0x03, 0x00, 0x12, 0x00, 0x00, 0x81, 0x01,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x38, 0x00, 0xd1, 0x01, 0x00, 0x00, 0x00, 0xfd, 0x10,
|
|
0x00, 0xc1, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
}
|
|
)
|