Skip to content

Commit e3c5c03

Browse files
committed
GOCBC-1769: Change to Authenticator interface
Motivation ---------- We need a way to do mtls rotation and also to support JWT auth. To achieve this we need to refactor how auth is done. This is technically a breaking change but we are not aware of any users other than gocb. Changes ------- Remove username, password, and certificate. Create Authenticator interface and certificate and basic authenticators. Expose a ReconfigureAuthenticator function on routing client.
1 parent 8f7ff2e commit e3c5c03

File tree

4 files changed

+109
-43
lines changed

4 files changed

+109
-43
lines changed

auth.go

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,69 @@ package gocbcoreps
22

33
import (
44
"context"
5+
"crypto/tls"
56
"encoding/base64"
6-
7-
"google.golang.org/grpc/credentials"
7+
"sync/atomic"
88
)
99

10-
type GrpcBasicAuth struct {
11-
EncodedData string
10+
type Authenticator interface {
11+
isAuthenticator()
12+
}
13+
14+
type BasicAuthenticator struct {
15+
encodedData atomic.Pointer[string]
1216
}
1317

14-
// NewJWTAccessFromKey creates PerRPCCredentials from the given jsonKey.
15-
func NewGrpcBasicAuth(username, password string) (credentials.PerRPCCredentials, error) {
18+
// NewBasicAuthenticator creates PerRPCCredentials from the given username and password.
19+
func NewBasicAuthenticator(username, password string) *BasicAuthenticator {
1620
basicAuth := username + ":" + password
1721
authValue := base64.StdEncoding.EncodeToString([]byte(basicAuth))
18-
return GrpcBasicAuth{authValue}, nil
22+
23+
auth := &BasicAuthenticator{}
24+
25+
auth.encodedData.Store(&authValue)
26+
27+
return auth
1928
}
2029

21-
func (j GrpcBasicAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
30+
func (j *BasicAuthenticator) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
31+
encodedData := j.encodedData.Load()
2232
return map[string]string{
23-
"authorization": "Basic " + j.EncodedData,
33+
"authorization": "Basic " + *encodedData,
2434
}, nil
2535
}
2636

27-
func (j GrpcBasicAuth) RequireTransportSecurity() bool {
37+
func (j *BasicAuthenticator) RequireTransportSecurity() bool {
2838
return false
2939
}
40+
41+
func (j *BasicAuthenticator) UpdateCredentials(username, password string) {
42+
basicAuth := username + ":" + password
43+
authValue := base64.StdEncoding.EncodeToString([]byte(basicAuth))
44+
45+
j.encodedData.Store(&authValue)
46+
}
47+
48+
func (j *BasicAuthenticator) isAuthenticator() {}
49+
50+
type CertificateAuthenticator struct {
51+
certificate atomic.Pointer[tls.Certificate]
52+
}
53+
54+
func NewCertificateAuthenticator(cert *tls.Certificate) *CertificateAuthenticator {
55+
auth := &CertificateAuthenticator{}
56+
auth.certificate.Store(cert)
57+
return auth
58+
}
59+
60+
func (j *CertificateAuthenticator) GetClientCertificate(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
61+
cert := j.certificate.Load()
62+
63+
return cert, nil
64+
}
65+
66+
func (j *CertificateAuthenticator) UpdateCertificate(cert *tls.Certificate) {
67+
j.certificate.Store(cert)
68+
}
69+
70+
func (j *CertificateAuthenticator) isAuthenticator() {}

error.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package gocbcoreps
2+
3+
import "errors"
4+
5+
var (
6+
// ErrAuthenticatorMismatch is returned when there is a mismatch between authenticators.
7+
ErrAuthenticatorMismatch = errors.New("authenticator mismatch")
8+
9+
// ErrAuthenticatorUnsupported is returned when a unsupported authenticator is specified.
10+
ErrAuthenticatorUnsupported = errors.New("authenticator unsupported")
11+
)

routingclient.go

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package gocbcoreps
22

33
import (
44
"context"
5-
"crypto/tls"
65
"crypto/x509"
76
"net"
87
"sync"
@@ -39,16 +38,15 @@ type RoutingClient struct {
3938
lock sync.Mutex
4039
buckets map[string]*routingClient_Bucket
4140
logger *zap.Logger
41+
auth Authenticator
4242
}
4343

4444
// Verify that RoutingClient implements Conn
4545
var _ Conn = (*RoutingClient)(nil)
4646

4747
type DialOptions struct {
4848
RootCAs *x509.CertPool
49-
Certificate *tls.Certificate
50-
Username string
51-
Password string
49+
Authenticator Authenticator
5250
Logger *zap.Logger
5351
InsecureSkipVerify bool
5452
PoolSize uint32
@@ -88,9 +86,7 @@ func DialContext(ctx context.Context, target string, opts *DialOptions) (*Routin
8886
for i := uint32(0); i < poolSize; i++ {
8987
conn, err := dialRoutingConn(ctx, target, &routingConnOptions{
9088
RootCAs: opts.RootCAs,
91-
Certificate: opts.Certificate,
92-
Username: opts.Username,
93-
Password: opts.Password,
89+
Authenticator: opts.Authenticator,
9490
InsecureSkipVerify: opts.InsecureSkipVerify,
9591
TracerProvider: opts.TracerProvider,
9692
MeterProvider: opts.MeterProvider,
@@ -111,6 +107,7 @@ func DialContext(ctx context.Context, target string, opts *DialOptions) (*Routin
111107
routing: routing,
112108
buckets: make(map[string]*routingClient_Bucket),
113109
logger: logger,
110+
auth: opts.Authenticator,
114111
}, nil
115112
}
116113

@@ -166,6 +163,39 @@ func (c *RoutingClient) CloseBucket(bucketName string) {
166163
c.lock.Unlock()
167164
}
168165

166+
type ReconfigureAuthenticatorOptions struct {
167+
Authenticator Authenticator
168+
}
169+
170+
func (c *RoutingClient) ReconfigureAuthenticator(opts ReconfigureAuthenticatorOptions) error {
171+
auth := opts.Authenticator
172+
c.lock.Lock()
173+
defer c.lock.Unlock()
174+
175+
switch a := c.auth.(type) {
176+
case *BasicAuthenticator:
177+
switch na := auth.(type) {
178+
case *BasicAuthenticator:
179+
data := na.encodedData.Load()
180+
a.encodedData.Store(data)
181+
default:
182+
return ErrAuthenticatorMismatch
183+
}
184+
case *CertificateAuthenticator:
185+
switch na := auth.(type) {
186+
case *CertificateAuthenticator:
187+
cert := na.certificate.Load()
188+
a.certificate.Store(cert)
189+
default:
190+
return ErrAuthenticatorMismatch
191+
}
192+
default:
193+
return ErrAuthenticatorUnsupported
194+
}
195+
196+
return nil
197+
}
198+
169199
func (c *RoutingClient) ConnectionState() ConnState {
170200
r := c.routing.Load()
171201

routingconn.go

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import (
44
"context"
55
"crypto/tls"
66
"crypto/x509"
7-
"errors"
8-
97
"go.opentelemetry.io/otel/metric"
108
"go.opentelemetry.io/otel/propagation"
119
"go.opentelemetry.io/otel/trace"
@@ -34,9 +32,7 @@ import (
3432
type routingConnOptions struct {
3533
InsecureSkipVerify bool // used for enabling TLS, but skipping verification
3634
RootCAs *x509.CertPool
37-
Certificate *tls.Certificate
38-
Username string
39-
Password string
35+
Authenticator Authenticator
4036
TracerProvider trace.TracerProvider
4137
MeterProvider metric.MeterProvider
4238
}
@@ -62,25 +58,13 @@ const maxMsgSize = 26214400 // 25MiB
6258

6359
func dialRoutingConn(ctx context.Context, address string, opts *routingConnOptions) (*routingConn, error) {
6460
var perRpcDialOpt grpc.DialOption
61+
var getClientCertificate func(info *tls.CertificateRequestInfo) (*tls.Certificate, error)
6562

66-
// setup basic auth.
67-
if opts.Username != "" && opts.Password != "" {
68-
basicAuthCreds, err := NewGrpcBasicAuth(opts.Username, opts.Password)
69-
if err != nil {
70-
return nil, err
71-
}
72-
perRpcDialOpt = grpc.WithPerRPCCredentials(basicAuthCreds)
73-
} else {
74-
perRpcDialOpt = nil
75-
}
76-
77-
var certificates []tls.Certificate
78-
if opts.Certificate != nil {
79-
if perRpcDialOpt != nil {
80-
return nil, errors.New("cannot use basic credentials and client cert auth at the same time")
81-
}
82-
83-
certificates = append(certificates, *opts.Certificate)
63+
switch a := opts.Authenticator.(type) {
64+
case *BasicAuthenticator:
65+
perRpcDialOpt = grpc.WithPerRPCCredentials(a)
66+
case *CertificateAuthenticator:
67+
getClientCertificate = a.GetClientCertificate
8468
}
8569

8670
pool, err := x509.SystemCertPool()
@@ -94,9 +78,9 @@ func dialRoutingConn(ctx context.Context, address string, opts *routingConnOptio
9478

9579
dialOpts := []grpc.DialOption{grpc.WithTransportCredentials(credentials.NewTLS(
9680
&tls.Config{
97-
InsecureSkipVerify: opts.InsecureSkipVerify,
98-
RootCAs: pool,
99-
Certificates: certificates,
81+
InsecureSkipVerify: opts.InsecureSkipVerify,
82+
RootCAs: pool,
83+
GetClientCertificate: getClientCertificate,
10084
},
10185
))}
10286
if perRpcDialOpt != nil {

0 commit comments

Comments
 (0)