Skip to content

Commit a60bd2e

Browse files
committed
ING-662: Adds support for client cert authentication
1 parent 7cd2cb2 commit a60bd2e

File tree

7 files changed

+90
-5
lines changed

7 files changed

+90
-5
lines changed

gateway/auth/authenticator.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package auth
22

3-
import "context"
3+
import (
4+
"context"
5+
"crypto/tls"
6+
)
47

58
type Authenticator interface {
69
ValidateUserForObo(ctx context.Context, user, pass string) (string, string, error)
10+
ValidateConnStateForObo(ctx context.Context, connState *tls.ConnectionState) (string, string, error)
711
}

gateway/auth/cbauthauthenticator.go

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

33
import (
44
"context"
5+
"crypto/tls"
56
"errors"
67
"fmt"
78
"time"
@@ -12,6 +13,7 @@ import (
1213

1314
var (
1415
ErrInvalidCredentials = errors.New("invalid credentials")
16+
ErrInvalidCertificate = errors.New("invalid certificate")
1517
)
1618

1719
type CbAuthAuthenticator struct {
@@ -94,6 +96,19 @@ func (a *CbAuthAuthenticator) ValidateUserForObo(ctx context.Context, user, pass
9496
return user, info.Domain, nil
9597
}
9698

99+
func (a *CbAuthAuthenticator) ValidateConnStateForObo(ctx context.Context, connState *tls.ConnectionState) (string, string, error) {
100+
info, err := a.Authenticator.CheckCertificate(ctx, connState)
101+
if err != nil {
102+
if errors.Is(err, cbauthx.ErrInvalidAuth) {
103+
return "", "", ErrInvalidCertificate
104+
}
105+
106+
return "", "", fmt.Errorf("failed to check certificate with cbauth: %s", err.Error())
107+
}
108+
109+
return info.User, info.Domain, nil
110+
}
111+
97112
func (a *CbAuthAuthenticator) Close() error {
98113
return a.Authenticator.Close()
99114
}

gateway/dataimpl/server_v1/authhandler.go

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package server_v1
22

33
import (
44
"context"
5+
"crypto/tls"
56
"errors"
67

78
"github.com/couchbase/gocbcorex"
@@ -10,7 +11,9 @@ import (
1011
"github.com/couchbase/stellar-gateway/gateway/auth"
1112
"github.com/couchbase/stellar-gateway/utils/authhdr"
1213
"go.uber.org/zap"
14+
"google.golang.org/grpc/credentials"
1315
"google.golang.org/grpc/metadata"
16+
"google.golang.org/grpc/peer"
1417
"google.golang.org/grpc/status"
1518
)
1619

@@ -45,16 +48,57 @@ func (a AuthHandler) MaybeGetUserPassFromContext(ctx context.Context) (string, s
4548
return username, password, nil
4649
}
4750

51+
func (a AuthHandler) MaybeGetConnStateFromContext(ctx context.Context) (*tls.ConnectionState, *status.Status) {
52+
p, ok := peer.FromContext(ctx)
53+
if !ok {
54+
return nil, nil
55+
}
56+
57+
tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo)
58+
if !ok {
59+
a.Logger.Debug("unexpected auth type", zap.String("authType", p.AuthInfo.AuthType()))
60+
return nil, a.ErrorHandler.NewUnexpectedAuthTypeStatus()
61+
}
62+
63+
return &tlsInfo.State, nil
64+
}
65+
4866
func (a AuthHandler) MaybeGetOboUserFromContext(ctx context.Context) (string, string, *status.Status) {
4967
username, password, errSt := a.MaybeGetUserPassFromContext(ctx)
5068
if errSt != nil {
5169
return "", "", errSt
5270
}
5371

54-
if username == "" && password == "" {
72+
connState, errSt := a.MaybeGetConnStateFromContext(ctx)
73+
if errSt != nil {
74+
return "", "", errSt
75+
}
76+
77+
credsFound := username != "" && password != ""
78+
certFound := connState != nil && len(connState.PeerCertificates) != 0
79+
80+
if credsFound && certFound {
81+
return "", "", a.ErrorHandler.NewCredentialsAndCertStatus()
82+
}
83+
84+
if !credsFound && !certFound {
5585
return "", "", nil
5686
}
5787

88+
if certFound {
89+
oboUser, oboDomain, err := a.Authenticator.ValidateConnStateForObo(ctx, connState)
90+
if err != nil {
91+
if errors.Is(err, auth.ErrInvalidCertificate) {
92+
return "", "", a.ErrorHandler.NewInvalidCertificateStatus()
93+
}
94+
95+
a.Logger.Error("received an unexpected cert authentication error", zap.Error(err))
96+
return "", "", a.ErrorHandler.NewInternalStatus()
97+
}
98+
99+
return oboUser, oboDomain, nil
100+
}
101+
58102
oboUser, oboDomain, err := a.Authenticator.ValidateUserForObo(ctx, username, password)
59103
if err != nil {
60104
if errors.Is(err, auth.ErrInvalidCredentials) {

gateway/dataimpl/server_v1/errorhandler.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -869,6 +869,26 @@ func (e ErrorHandler) NewInvalidCredentialsStatus() *status.Status {
869869
return st
870870
}
871871

872+
func (e ErrorHandler) NewInvalidCertificateStatus() *status.Status {
873+
st := status.New(codes.PermissionDenied, "Your certificate is invalid.")
874+
st = e.tryAttachStatusDetails(st, &epb.ResourceInfo{
875+
ResourceType: "user",
876+
ResourceName: "",
877+
Description: "",
878+
})
879+
return st
880+
}
881+
882+
func (e ErrorHandler) NewUnexpectedAuthTypeStatus() *status.Status {
883+
st := status.New(codes.InvalidArgument, "Unexpected auth type.")
884+
return st
885+
}
886+
887+
func (e ErrorHandler) NewCredentialsAndCertStatus() *status.Status {
888+
st := status.New(codes.InvalidArgument, "Authorization header and client certificate provided.")
889+
return st
890+
}
891+
872892
func (e ErrorHandler) NewInvalidQueryStatus(baseErr error, queryErrStr string) *status.Status {
873893
st := status.New(codes.InvalidArgument,
874894
fmt.Sprintf("Query parsing failed: %s", queryErrStr))

gateway/gateway.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,8 @@ func (g *Gateway) Run(ctx context.Context) error {
410410
Metrics: metrics.GetSnMetrics(),
411411
RateLimiter: rateLimiter,
412412
GrpcTlsConfig: &tls.Config{
413+
ClientCAs: config.ClusterCaCert,
414+
ClientAuth: tls.VerifyClientCertIfGiven,
413415
GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) {
414416
return g.atomicGrpcCert.Load(), nil
415417
},

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ require (
1010
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.12.0
1111
github.com/aws/aws-sdk-go-v2/config v1.28.8
1212
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.9
13-
github.com/couchbase/gocbcorex v0.0.0-20251016083313-1d5a0c9e5beb
13+
github.com/couchbase/gocbcorex v0.0.0-20251022095750-491da08faa75
1414
github.com/couchbase/goprotostellar v1.0.3-0.20250811181441-159acea69746
1515
github.com/couchbaselabs/gocbconnstr v1.0.5
1616
github.com/couchbaselabs/gocbconnstr/v2 v2.0.0-20240607131231-fb385523de28

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
6767
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
6868
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
6969
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
70-
github.com/couchbase/gocbcorex v0.0.0-20251016083313-1d5a0c9e5beb h1:pHlpj241FbkJqrSuPI9gvBAhQ8g906Rfbm+zT/j6rb8=
71-
github.com/couchbase/gocbcorex v0.0.0-20251016083313-1d5a0c9e5beb/go.mod h1:6dno13KSKXpYALSx1KkYNHCwIQiuZdfL1xc3dDcYl1o=
70+
github.com/couchbase/gocbcorex v0.0.0-20251022095750-491da08faa75 h1:H53znXKq5qgA0SLMgF5XuV0sHGQQ0wrmjJ1Cz7vxgEU=
71+
github.com/couchbase/gocbcorex v0.0.0-20251022095750-491da08faa75/go.mod h1:6dno13KSKXpYALSx1KkYNHCwIQiuZdfL1xc3dDcYl1o=
7272
github.com/couchbase/goprotostellar v1.0.3-0.20250811181441-159acea69746 h1:kibnwIhBQ50egj2wCX3cd+MtwPXKre4DTDnXuoXQCGQ=
7373
github.com/couchbase/goprotostellar v1.0.3-0.20250811181441-159acea69746/go.mod h1:x/he3raYF/EspPXAfuVsBdLFNEYUxgFhlicJTRduPZ4=
7474
github.com/couchbaselabs/gocbconnstr v1.0.5 h1:e0JokB5qbcz7rfnxEhNRTKz8q1svoRvDoZihsiwNigA=

0 commit comments

Comments
 (0)