Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Begin adding proxyAuthenticators to handle auth between client and proxy #30

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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() {
Expand Down Expand Up @@ -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)
Expand Down
9 changes: 8 additions & 1 deletion proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -46,6 +47,7 @@ type Config struct {
ReconnectPolicy proxycore.ReconnectPolicy
NumConns int
Logger *zap.Logger
ProxyAuth proxycore.ProxyAuthenticator
}

type Proxy struct {
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions proxy/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
60 changes: 60 additions & 0 deletions proxycore/proxyauth.go
Original file line number Diff line number Diff line change
@@ -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{}
}
28 changes: 28 additions & 0 deletions proxycore/proxyauth_test.go
Original file line number Diff line number Diff line change
@@ -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())
}