cyberark/secretless-broker

View on GitHub
test/connector/tcp/mssql/mssql_client_request.go

Summary

Maintainability
A
0 mins
Test Coverage
package mssqltest

import (
    "fmt"
    "net"
    "strings"

    "github.com/cyberark/secretless-broker/test/connector/tcp/mssql/client"
)

// localListenerOnPort creates a net.Listener at 127.0.0.1 on the given port. Note that
// passing in a port of "0" will result in a random port being used.
func localListenerOnPort(port string) (net.Listener, error) {
    return net.Listen("tcp", "127.0.0.1:"+port)
}

// clientRequest is the request of an MSSQL client making a connection to a database via
// the Secretless proxyService. The fields on the struct are some of the values that the client has
// control over. Credentials are not included in this struct because those will be
// injected
type clientRequest struct {
    database string
    readOnly bool
    query    string
}

// cloneCredentials creates an independent clone of a credentials map. The resulting
// clone will not be affected by any mutations to the original, and vice-versa. The clone
// is useful for passing to a proxyService service, to avoid zeroization of the original.
func cloneCredentials(original map[string][]byte) map[string][]byte {
    credsClone := make(map[string][]byte)

    for key, value := range original {
        // Clone the value
        valueClone := make([]byte, len(value))
        copy(valueClone, value)

        // Set the key, value pair on the credentials clone
        credsClone[key] = valueClone
    }

    return credsClone
}

// proxyViaSecretless issues a client request using a the 'runQuery' argument to a
// Secretless proxy service configured using the 'credentials' argument.
// proxyViaSecretless uses newInProcessProxyService to creating the in-process proxy
// service. The proxy service exists only for the lifetime of this method call.
func (clientReq clientRequest) proxyViaSecretless(
    runQuery client.RunQuery,
    credentials map[string][]byte,
) (string, string, error) {
    // Create in-process proxy service
    proxyService, err := newInProcessProxyService(credentials)
    if err != nil {
        return "", "", err
    }

    // Ensure the proxy service is stopped
    defer proxyService.Stop()
    // Start the proxyService service
    proxyService.Start()

    // Make the client request to the proxy service
    clientResChan := runQuery.ConcurrentCall(
        client.Config{
            Host:     proxyService.host,
            Port:     proxyService.port,
            Username: "dummy",
            Password: "dummy",
            Database: clientReq.database,
            ReadOnly: clientReq.readOnly,
        },
        clientReq.query,
    )

    // Block and wait for the client response
    clientRes := <-clientResChan

    return clientRes.Out, proxyService.port, clientRes.Err
}

// proxyToCreatedMock issues a client request using a the 'runQuery' argument to a Secretless
// proxy service configured using the 'credentials' argument.
//
// NOTE: proxyToCreatedMock proxies the request to a mock server that terminates the request after the handshake
// This can have unintended effects. gomssql in particular does some weird retry, when a query is prepared!
// TODO: find out this weird gomssqldb behavior.
func (clientReq clientRequest) proxyToCreatedMock(
    runQuery client.RunQuery,
    credentials map[string][]byte,
) (*mockTargetCapture, string, error) {
    // Create mock target
    mt, err := newMockTarget("0")
    if err != nil {
        return nil, "", err
    }
    defer mt.close()

    return clientReq.proxyToMock(runQuery, credentials, mt)
}

func (clientReq clientRequest) proxyToMock(
    runQuery client.RunQuery,
    credentials map[string][]byte,
    mt *mockTarget,
) (*mockTargetCapture, string, error) {
    // Gather credentials
    baseCredentials := map[string][]byte{
        "host": []byte(mt.host),
        "port": []byte(mt.port),
    }
    for key, value := range credentials {
        baseCredentials[key] = value
    }

    // Accept on mock target
    mtResChan := mt.singleAcceptAndHandle()

    // We don't expect anything useful to come back from the client request.
    // This is a fire and forget.
    _, secretlessPort, err := clientReq.proxyViaSecretless(
        runQuery,
        baseCredentials,
    )

    mtRes := <-mtResChan

    var errStrings []string
    if mtRes.err != nil {
        // We only care about err (from the client request) if there was an error in
        // handling the request on the mock server. If this is not the case then it's
        // likely that err is because the mock server closed the connection.
        if err != nil {
            errStrings = append(errStrings, err.Error())
        }
        errStrings = append(errStrings, mtRes.err.Error())
    }

    var combinedErr error
    if len(errStrings) != 0 {
        combinedErr = fmt.Errorf("%s", strings.Join(errStrings, " AND "))
    }

    return mtRes.capture, secretlessPort, combinedErr
}