From 9b7e1bc9010c64172d58ca284e82ab9ca22ef496 Mon Sep 17 00:00:00 2001 From: Doug Wettlaufer Date: Wed, 1 Sep 2021 14:56:03 -0500 Subject: [PATCH 1/2] Begin adding proxyAuthenticators to handle auth between client and proxy --- proxy.go | 13 ++++++-- proxy/proxy.go | 9 +++++- proxycore/proxyauth.go | 60 +++++++++++++++++++++++++++++++++++++ proxycore/proxyauth_test.go | 28 +++++++++++++++++ 4 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 proxycore/proxyauth.go create mode 100644 proxycore/proxyauth_test.go diff --git a/proxy.go b/proxy.go index 1715498..0e98236 100644 --- a/proxy.go +++ b/proxy.go @@ -20,10 +20,11 @@ import ( "net/http" _ "net/http/pprof" - "github.com/alecthomas/kong" "cql-proxy/astra" "cql-proxy/proxy" "cql-proxy/proxycore" + + "github.com/alecthomas/kong" "github.com/datastax/go-cassandra-native-protocol/primitive" "go.uber.org/zap" ) @@ -36,6 +37,7 @@ var cli struct { Bind string `help:"Address to use to bind serve" short:"a"` Debug bool `help:"Show debug logging"` Profiling bool `help:"Enable profiling"` + FakeAuth bool `help:"Enables an authenticator which will imitate authentication between the client and proxy but accepts any credentials provided."` } func main() { @@ -74,13 +76,20 @@ func main() { auth = proxycore.NewPasswordAuth(cli.Username, cli.Password) } + proxyAuth := proxycore.NewNoopProxyAuth() + + if cli.FakeAuth { + proxyAuth = proxycore.NewFakeProxyAuth() + } + p := proxy.NewProxy(ctx, proxy.Config{ Version: primitive.ProtocolVersion4, + Auth: auth, Resolver: resolver, ReconnectPolicy: proxycore.NewReconnectPolicy(), NumConns: 1, - Auth: auth, Logger: logger, + ProxyAuth: proxyAuth, }) bind, _, err := net.SplitHostPort(cli.Bind) diff --git a/proxy/proxy.go b/proxy/proxy.go index 9225b4d..9c43109 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -27,6 +27,7 @@ import ( "cql-proxy/parser" "cql-proxy/proxycore" + "github.com/datastax/go-cassandra-native-protocol/datatype" "github.com/datastax/go-cassandra-native-protocol/frame" "github.com/datastax/go-cassandra-native-protocol/message" @@ -46,6 +47,7 @@ type Config struct { ReconnectPolicy proxycore.ReconnectPolicy NumConns int Logger *zap.Logger + ProxyAuth proxycore.ProxyAuthenticator } type Proxy struct { @@ -237,6 +239,8 @@ type client struct { preparedIdempotence map[[16]byte]bool } +// Receive handles a request from a client to the proxy by decoding the frame and delegating to the correct function that is +// able to handle the particular message type. func (c *client) Receive(reader io.Reader) error { raw, err := codec.DecodeRawFrame(reader) if err != nil { @@ -261,7 +265,10 @@ func (c *client) Receive(reader io.Reader) error { case *message.Options: c.send(raw.Header, &message.Supported{Options: map[string][]string{"CQL_VERSION": {"3.0.0"}, "COMPRESSION": {}}}) case *message.Startup: - c.send(raw.Header, &message.Ready{}) + c.send(raw.Header, c.proxy.config.ProxyAuth.MessageForStartup()) + case *message.AuthResponse: + resp := body.Message.(*message.AuthResponse) + c.send(raw.Header, c.proxy.config.ProxyAuth.HandleAuthResponse(resp.Token)) case *message.Register: for _, t := range msg.EventTypes { if t == primitive.EventTypeSchemaChange { diff --git a/proxycore/proxyauth.go b/proxycore/proxyauth.go new file mode 100644 index 0000000..5206142 --- /dev/null +++ b/proxycore/proxyauth.go @@ -0,0 +1,60 @@ +// Copyright (c) DataStax, 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 proxycore + +import ( + "github.com/datastax/go-cassandra-native-protocol/message" +) + +// ProxyAuthenticator is responsible for processing STARTUP from the client and preceding message/response during the auth +// handshake. +type ProxyAuthenticator interface { + // MessageForStartup will return the proper message in response to the STARTUP request. + MessageForStartup() message.Message + // HandleAuthResponse will return the proper message based on implementation and the token provided by the client. + HandleAuthResponse(token []byte) message.Message +} + +// noopProxyAuth returns a READY message to the initial STARTUP request and thus will never need to handle AUTH_RESPONSE +type noopProxyAuth struct {} + +func (n *noopProxyAuth) MessageForStartup() message.Message { + return &message.Ready{} +} + +func (n *noopProxyAuth) HandleAuthResponse(token []byte) message.Message { + return nil +} + +func NewNoopProxyAuth() ProxyAuthenticator { + return &noopProxyAuth{} +} + +// fakeProxyAuth imitates auth against org.apache.cassandra.auth.PasswordAuthenticator for clients that will break if they +// don't receive an AUTHENTICATE message when they expect it. Regardless of token provided will always reply with an AUTH_SUCCESS +// message. +type fakeProxyAuth struct {} + +func (n *fakeProxyAuth) MessageForStartup() message.Message { + return &message.Authenticate{Authenticator: "org.apache.cassandra.auth.PasswordAuthenticator"} +} + +func (n *fakeProxyAuth) HandleAuthResponse(token []byte) message.Message { + return &message.AuthSuccess{} +} + +func NewFakeProxyAuth() ProxyAuthenticator { + return &fakeProxyAuth{} +} \ No newline at end of file diff --git a/proxycore/proxyauth_test.go b/proxycore/proxyauth_test.go new file mode 100644 index 0000000..34653b0 --- /dev/null +++ b/proxycore/proxyauth_test.go @@ -0,0 +1,28 @@ +package proxycore + +import ( + "testing" + + "github.com/datastax/go-cassandra-native-protocol/message" + "github.com/stretchr/testify/assert" +) + +func Test_noopProxyAuth_HandleAuthResponse(t *testing.T) { + proxyAuth := NewNoopProxyAuth() + assert.Equal(t, nil, proxyAuth.HandleAuthResponse(nil)) +} + +func Test_noopProxyAuth_MessageForStartup(t *testing.T) { + proxyAuth := NewNoopProxyAuth() + assert.Equal(t, &message.Ready{}, proxyAuth.MessageForStartup()) +} + +func Test_fakeProxyAuth_HandleAuthResponse(t *testing.T) { + proxyAuth := NewFakeProxyAuth() + assert.Equal(t, &message.AuthSuccess{}, proxyAuth.HandleAuthResponse(nil)) +} + +func Test_fakeProxyAuth_MessageForStartup(t *testing.T) { + proxyAuth := NewFakeProxyAuth() + assert.Equal(t, &message.Authenticate{Authenticator: "org.apache.cassandra.auth.PasswordAuthenticator"}, proxyAuth.MessageForStartup()) +} \ No newline at end of file From b7f508ced253e03c573b66d6b61e727c158c02ba Mon Sep 17 00:00:00 2001 From: Doug Wettlaufer Date: Wed, 1 Sep 2021 15:51:47 -0500 Subject: [PATCH 2/2] Fix test --- proxy/proxy_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proxy/proxy_test.go b/proxy/proxy_test.go index 150869e..109e3e3 100644 --- a/proxy/proxy_test.go +++ b/proxy/proxy_test.go @@ -23,6 +23,7 @@ import ( "time" "cql-proxy/proxycore" + "github.com/datastax/go-cassandra-native-protocol/datatype" "github.com/datastax/go-cassandra-native-protocol/frame" "github.com/datastax/go-cassandra-native-protocol/message" @@ -85,6 +86,7 @@ func TestProxy_ListenAndServe(t *testing.T) { Resolver: proxycore.NewResolverWithDefaultPort([]string{clusterContactPoint}, clusterPort), ReconnectPolicy: proxycore.NewReconnectPolicyWithDelays(200*time.Millisecond, time.Second), NumConns: 2, + ProxyAuth: proxycore.NewNoopProxyAuth(), }) err = proxy.Listen(proxyContactPoint)