Skip to content

Commit c475e6a

Browse files
committed
ING-1363: Ensure client cert auth fails when disabled
1 parent 59963b5 commit c475e6a

10 files changed

Lines changed: 142 additions & 117 deletions

File tree

cmd/gateway/main.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -446,19 +446,13 @@ func startGateway() {
446446

447447
var selfSignedCert *tls.Certificate
448448
if config.selfSign {
449-
generatedCert, generatedKey, err := selfsignedcert.GenerateCertificate()
449+
generatedCert, err := selfsignedcert.GenerateCertificate()
450450
if err != nil {
451451
logger.Error("failed to generate a self-signed certificate")
452452
os.Exit(1)
453453
}
454454

455-
tlsCert, err := selfsignedcert.ConstructTlsCert(generatedCert, generatedKey)
456-
if err != nil {
457-
logger.Error("failed to generate a self-signed certificate")
458-
os.Exit(1)
459-
}
460-
461-
selfSignedCert = tlsCert
455+
selfSignedCert = generatedCert
462456
}
463457

464458
var grpcCertificate tls.Certificate

gateway/auth/cbauthauthenticator.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
var (
1515
ErrInvalidCredentials = errors.New("invalid credentials")
1616
ErrInvalidCertificate = errors.New("invalid certificate")
17+
ErrCertAuthDisabled = errors.New("client cert auth disabled")
1718
)
1819

1920
type CbAuthAuthenticator struct {
@@ -101,6 +102,8 @@ func (a *CbAuthAuthenticator) ValidateConnStateForObo(ctx context.Context, connS
101102
if err != nil {
102103
if errors.Is(err, cbauthx.ErrInvalidAuth) {
103104
return "", "", ErrInvalidCertificate
105+
} else if errors.Is(err, cbauthx.ErrCertAuthDisabled) {
106+
return "", "", ErrCertAuthDisabled
104107
}
105108

106109
return "", "", fmt.Errorf("failed to check certificate with cbauth: %s", err.Error())

gateway/dataimpl/server_v1/authhandler.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ func (a AuthHandler) MaybeGetOboUserFromContext(ctx context.Context) (string, st
8888
if err != nil {
8989
if errors.Is(err, auth.ErrInvalidCertificate) {
9090
return "", "", a.ErrorHandler.NewInvalidCertificateStatus()
91+
} else if errors.Is(err, auth.ErrCertAuthDisabled) {
92+
return "", "", a.ErrorHandler.NewCertAuthDisabledStatus()
9193
}
9294

9395
a.Logger.Error("received an unexpected cert authentication error", zap.Error(err))

gateway/dataimpl/server_v1/errorhandler.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,16 @@ func (e ErrorHandler) NewInvalidCertificateStatus() *status.Status {
937937
return st
938938
}
939939

940+
func (e ErrorHandler) NewCertAuthDisabledStatus() *status.Status {
941+
st := status.New(codes.Unauthenticated, "Client cert auth disabled on the cluster.")
942+
st = e.tryAttachStatusDetails(st, &epb.ResourceInfo{
943+
ResourceType: "user",
944+
ResourceName: "",
945+
Description: "",
946+
})
947+
return st
948+
}
949+
940950
func (e ErrorHandler) NewUnexpectedAuthTypeStatus() *status.Status {
941951
st := status.New(codes.InvalidArgument, "Unexpected auth type.")
942952
return st

gateway/test/dapi_graceful_shutdown_test.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,11 @@ import (
1919
func (s *GatewayOpsTestSuite) TestGracefulShutdown() {
2020
s.T().Logf("setting up new instance of stellar gateway...")
2121

22-
cert, key, err := selfsignedcert.GenerateCertificate()
22+
gwCert, err := selfsignedcert.GenerateCertificate()
2323
if err != nil {
2424
s.T().Fatalf("failed to create testing certificate: %s", err)
2525
}
2626

27-
gwCert, err := selfsignedcert.ConstructTlsCert(cert, key)
28-
if err != nil {
29-
s.T().Fatalf("failed to construct testing certificate: %s", err)
30-
}
31-
3227
logger, err := zap.NewDevelopment()
3328
if err != nil {
3429
s.T().Fatalf("failed to initialize test logging: %s", err)

gateway/test/mtls_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ package test
33
import (
44
"context"
55
"crypto/tls"
6+
"net/http"
67
"time"
78

9+
"github.com/couchbase/gocbcorex/cbhttpx"
10+
"github.com/couchbase/gocbcorex/cbmgmtx"
811
"github.com/couchbase/goprotostellar/genproto/kv_v1"
912
"github.com/couchbase/stellar-gateway/testutils"
1013
"github.com/stretchr/testify/assert"
@@ -17,6 +20,8 @@ func (s *GatewayOpsTestSuite) TestClientCertAuth() {
1720
testutils.SkipIfNoDinoCluster(s.T())
1821

1922
s.Run("KvService", s.KvService)
23+
24+
s.Run("ClientCertAuthDisabled", s.ClientCertConfiguration)
2025
}
2126

2227
func (s *GatewayOpsTestSuite) KvService() {
@@ -96,6 +101,107 @@ func (s *GatewayOpsTestSuite) KvService() {
96101
})
97102
}
98103

104+
func (s *GatewayOpsTestSuite) ClientCertConfiguration() {
105+
dino := testutils.StartDinoTesting(s.T(), false)
106+
username := "certConfig"
107+
conn := s.newClientCertConn(dino, username)
108+
kvClient := kv_v1.NewKvServiceClient(conn)
109+
110+
getFn := func() (*kv_v1.GetResponse, error) {
111+
return kvClient.Get(context.Background(), &kv_v1.GetRequest{
112+
BucketName: s.bucketName,
113+
ScopeName: s.scopeName,
114+
CollectionName: s.collectionName,
115+
Key: s.testDocId(),
116+
})
117+
}
118+
119+
enableReq := &cbmgmtx.ConfigureClientCertAuthRequest{
120+
State: "enable",
121+
Prefixes: []cbmgmtx.Prefix{
122+
{
123+
Path: "san.email",
124+
Prefix: "",
125+
Delimiter: "@",
126+
},
127+
},
128+
}
129+
130+
dino.AddWriteUser(username)
131+
time.Sleep(time.Second * 5)
132+
133+
// Check that client cert auth is working as expected.
134+
s.Run("InitialSuccess", func() {
135+
resp, err := getFn()
136+
requireRpcSuccess(s.T(), resp, err)
137+
})
138+
139+
testConfig := testutils.GetTestConfig(s.T())
140+
mgmt := cbmgmtx.Management{
141+
Transport: http.DefaultTransport,
142+
UserAgent: "useragent",
143+
Endpoint: "http://" + testConfig.CbConnStr + ":8091",
144+
Auth: &cbhttpx.BasicAuth{
145+
Username: testConfig.CbUser,
146+
Password: testConfig.CbPass,
147+
},
148+
}
149+
150+
// Change the path that cbauth will try and get the name from and check
151+
// that the old cert fails
152+
err := mgmt.ConfigureClientCertAuth(context.Background(), &cbmgmtx.ConfigureClientCertAuthRequest{
153+
State: "enable",
154+
Prefixes: []cbmgmtx.Prefix{
155+
{
156+
Path: "subject.cn",
157+
Prefix: "",
158+
Delimiter: "",
159+
},
160+
},
161+
})
162+
time.Sleep(time.Second * 5)
163+
164+
// Check that client cert auth is working as expected.
165+
s.Run("IncorrectUsernamePath", func() {
166+
_, err := getFn()
167+
assertRpcStatus(s.T(), err, codes.PermissionDenied)
168+
assert.Contains(s.T(), err.Error(), "Your certificate is invalid")
169+
})
170+
171+
// Restore intial settings and check that the original cert works again.
172+
err = mgmt.ConfigureClientCertAuth(context.Background(), enableReq)
173+
assert.NoError(s.T(), err)
174+
time.Sleep(time.Second * 5)
175+
176+
s.Run("SuccessAfterSettingsReset", func() {
177+
resp, err := getFn()
178+
requireRpcSuccess(s.T(), resp, err)
179+
})
180+
181+
// Disable client cert auth on the cluster and make sure op fails.
182+
err = mgmt.ConfigureClientCertAuth(context.Background(), &cbmgmtx.ConfigureClientCertAuthRequest{
183+
State: "disable",
184+
Prefixes: []cbmgmtx.Prefix{
185+
{
186+
Path: "san.email",
187+
Prefix: "",
188+
Delimiter: "@",
189+
},
190+
},
191+
})
192+
assert.NoError(s.T(), err)
193+
time.Sleep(time.Second * 5)
194+
195+
s.Run("CertAuthDisabled", func() {
196+
_, err := getFn()
197+
assertRpcStatus(s.T(), err, codes.Unauthenticated)
198+
assert.Contains(s.T(), err.Error(), "Client cert auth disabled on the cluster")
199+
})
200+
201+
err = mgmt.ConfigureClientCertAuth(context.Background(), enableReq)
202+
assert.NoError(s.T(), err)
203+
}
204+
99205
func (s *GatewayOpsTestSuite) newClientCertConn(dino *testutils.DinoController, username string) *grpc.ClientConn {
100206
res := dino.GetClientCert(username)
101207

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-20251111022038-67fa47c27da2
13+
github.com/couchbase/gocbcorex v0.0.0-20251118081240-3f26da0f471e
1414
github.com/couchbase/goprotostellar v1.0.3-0.20251106001300-b09286f4d53d
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-20251111022038-67fa47c27da2 h1:Cbw1gJs5V2z/SHPmiqVo7nlr7RWtMAusEoxIDpsPm+s=
71-
github.com/couchbase/gocbcorex v0.0.0-20251111022038-67fa47c27da2/go.mod h1:tlLBmDl/J9iUCImvGvzIr1sLhHm8+KWaSfkofmErCMQ=
70+
github.com/couchbase/gocbcorex v0.0.0-20251118081240-3f26da0f471e h1:3H82a+phltYGRicpdKogxt9mvDAe7kD66t9086vXh9k=
71+
github.com/couchbase/gocbcorex v0.0.0-20251118081240-3f26da0f471e/go.mod h1:tlLBmDl/J9iUCImvGvzIr1sLhHm8+KWaSfkofmErCMQ=
7272
github.com/couchbase/goprotostellar v1.0.3-0.20251106001300-b09286f4d53d h1:FZZcOTRLIaK3VGX0eVGmvCqc6teJ+BSuS3VYJMAjejs=
7373
github.com/couchbase/goprotostellar v1.0.3-0.20251106001300-b09286f4d53d/go.mod h1:X58ot5FRqlBTBkwG/oI4klunpu4MApjGktheqeRWQw0=
7474
github.com/couchbaselabs/gocbconnstr v1.0.5 h1:e0JokB5qbcz7rfnxEhNRTKz8q1svoRvDoZihsiwNigA=

utils/certificates/certificates.go

Lines changed: 0 additions & 73 deletions
This file was deleted.

utils/selfsignedcert/selfsignedcert.go

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,21 @@ import (
1515
"github.com/pkg/errors"
1616
)
1717

18-
func GenerateCertificate() (*x509.Certificate, *ecdsa.PrivateKey, error) {
18+
func GenerateCertificate() (*tls.Certificate, error) {
1919
priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
2020
if err != nil {
21-
return nil, nil, errors.New("failed to generate private key")
21+
return nil, errors.Wrap(err, "failed to generate private key")
22+
}
23+
24+
privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
25+
if err != nil {
26+
return nil, errors.Wrap(err, "unable to marshal private key")
2227
}
2328

2429
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
2530
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
2631
if err != nil {
27-
return nil, nil, errors.Wrap(err, "failed to generate serial number")
32+
return nil, errors.Wrap(err, "failed to generate serial number")
2833
}
2934

3035
template := x509.Certificate{
@@ -36,49 +41,32 @@ func GenerateCertificate() (*x509.Certificate, *ecdsa.PrivateKey, error) {
3641
NotBefore: time.Now(),
3742
NotAfter: time.Now().Add(7 * 24 * time.Hour),
3843

39-
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
44+
KeyUsage: x509.KeyUsageDigitalSignature,
45+
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
4046
BasicConstraintsValid: true,
41-
IsCA: true,
4247
}
4348

4449
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
4550
if err != nil {
46-
return nil, nil, errors.Wrap(err, "failed to create certificate")
47-
}
48-
49-
cert, err := x509.ParseCertificate(derBytes)
50-
if err != nil {
51-
return nil, nil, errors.Wrap(err, "failed to parse cert from bytes")
52-
}
53-
54-
return cert, priv, nil
55-
}
56-
57-
func ConstructTlsCert(cert *x509.Certificate, key *ecdsa.PrivateKey) (*tls.Certificate, error) {
58-
keyBytes, err := x509.MarshalPKCS8PrivateKey(key)
59-
if err != nil {
60-
return nil, errors.Wrap(err, "unable to marshal private key")
51+
return nil, errors.Wrap(err, "failed to create certificate")
6152
}
6253

6354
keyBuf := bytes.NewBuffer(nil)
64-
err = pem.Encode(keyBuf, &pem.Block{
65-
Type: "EC PRIVATE KEY",
66-
Bytes: keyBytes,
67-
})
55+
err = pem.Encode(keyBuf, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})
6856
if err != nil {
6957
return nil, errors.Wrap(err, "failed to write key pem data")
7058
}
7159

7260
certBuf := bytes.NewBuffer(nil)
73-
err = pem.Encode(certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
61+
err = pem.Encode(certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
7462
if err != nil {
7563
return nil, errors.Wrap(err, "failed to write cert pem data")
7664
}
7765

78-
tlsCert, err := tls.X509KeyPair(certBuf.Bytes(), keyBuf.Bytes())
66+
cert, err := tls.X509KeyPair(certBuf.Bytes(), keyBuf.Bytes())
7967
if err != nil {
8068
return nil, errors.Wrap(err, "failed to produce tls certificate")
8169
}
8270

83-
return &tlsCert, nil
71+
return &cert, nil
8472
}

0 commit comments

Comments
 (0)