Skip to content

Commit

Permalink
[3.1.3 backport] CBG-3660 check Origin header on websocket (#6612)
Browse files Browse the repository at this point in the history
backports CBG-3652 check Origin header on websocket (#6610)
  • Loading branch information
torcolvin authored Dec 12, 2023
1 parent 1842fa5 commit 52b979c
Show file tree
Hide file tree
Showing 12 changed files with 313 additions and 40 deletions.
3 changes: 2 additions & 1 deletion db/active_replicator.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,8 @@ func (ar *ActiveReplicator) GetStatus(ctx context.Context) *ReplicationStatus {
func connect(arc *activeReplicatorCommon, idSuffix string) (blipSender *blip.Sender, bsc *BlipSyncContext, err error) {
arc.replicationStats.NumConnectAttempts.Add(1)

blipContext, err := NewSGBlipContext(arc.ctx, arc.config.ID+idSuffix)
var originPatterns []string // no origin headers for ISGR
blipContext, err := NewSGBlipContext(arc.ctx, arc.config.ID+idSuffix, originPatterns)
if err != nil {
return nil, nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion db/active_replicator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func TestBlipSyncErrorUserinfo(t *testing.T) {
srvURL.Path = "/db1"
t.Logf("srvURL: %v", srvURL.String())

blipContext, err := NewSGBlipContext(base.TestCtx(t), t.Name())
blipContext, err := NewSGBlipContext(base.TestCtx(t), t.Name(), nil)
require.NoError(t, err)

_, err = blipSync(*srvURL, blipContext, false)
Expand Down
14 changes: 9 additions & 5 deletions db/blip.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,22 @@ var (
)

// NewSGBlipContext returns a go-blip context with the given ID, initialized for use in Sync Gateway.
func NewSGBlipContext(ctx context.Context, id string) (bc *blip.Context, err error) {
func NewSGBlipContext(ctx context.Context, id string, origin []string) (bc *blip.Context, err error) {
// V3 is first here as it is the preferred communication method
// In the host case this means SGW can accept both V3 and V2 clients
// In the client case this means we prefer V3 but can fallback to V2
return NewSGBlipContextWithProtocols(ctx, id, BlipCBMobileReplicationV3, BlipCBMobileReplicationV2)
return NewSGBlipContextWithProtocols(ctx, id, origin, []string{BlipCBMobileReplicationV3, BlipCBMobileReplicationV2})
}

func NewSGBlipContextWithProtocols(ctx context.Context, id string, protocol ...string) (bc *blip.Context, err error) {
func NewSGBlipContextWithProtocols(ctx context.Context, id string, origin []string, protocols []string) (bc *blip.Context, err error) {
opts := blip.ContextOptions{
Origin: origin,
ProtocolIds: protocols,
}
if id == "" {
bc, err = blip.NewContext(protocol...)
bc, err = blip.NewContext(opts)
} else {
bc, err = blip.NewContextCustomID(id, protocol...)
bc, err = blip.NewContextCustomID(id, opts)
}

bc.LogMessages = base.LogDebugEnabled(base.KeyWebSocket)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/couchbase/cbgt v1.3.9
github.com/couchbase/clog v0.1.0
github.com/couchbase/go-blip v0.0.0-20221021161139-215cbac22bd7
github.com/couchbase/go-blip v0.0.0-20231212221113-a6ee87e0c16f
github.com/couchbase/go-couchbase v0.1.1
github.com/couchbase/gocb/v2 v2.6.5
github.com/couchbase/gocbcore/v10 v10.2.10
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ github.com/couchbase/cbgt v1.3.9 h1:MAT3FwD1ctekxuFe0yau0H1BCTvgLXvh1ipbZ3nZhBE=
github.com/couchbase/cbgt v1.3.9/go.mod h1:MImhtmvk0qjJit5HbmA34tnYThZoNtvgjL7jJH/kCAE=
github.com/couchbase/clog v0.1.0 h1:4Kh/YHkhRjMCbdQuvRVsm39XZh4FtL1d8fAwJsHrEPY=
github.com/couchbase/clog v0.1.0/go.mod h1:7tzUpEOsE+fgU81yfcjy5N1H6XtbVC8SgOz/3mCjmd4=
github.com/couchbase/go-blip v0.0.0-20221021161139-215cbac22bd7 h1:/GTlMVovmGKrFAl5e7u9CXuhjTlR5a4911Ujou18Q4Q=
github.com/couchbase/go-blip v0.0.0-20221021161139-215cbac22bd7/go.mod h1:nSpldGTqAhTOaDDL0Li2dSE0smqbISKagT7fIqYIRec=
github.com/couchbase/go-blip v0.0.0-20231212221113-a6ee87e0c16f h1:vmZxVtUFv5TfEXXrnTAqVikt6m++/gGja891m1ig6PU=
github.com/couchbase/go-blip v0.0.0-20231212221113-a6ee87e0c16f/go.mod h1:nSpldGTqAhTOaDDL0Li2dSE0smqbISKagT7fIqYIRec=
github.com/couchbase/go-couchbase v0.1.1 h1:ClFXELcKj/ojyoTYbsY34QUrrYCBi/1G749sXSCkdhk=
github.com/couchbase/go-couchbase v0.1.1/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A=
github.com/couchbase/gocb/v2 v2.6.5 h1:xaZu29o8UJEV1ZQ3n2s9jcRCUHz/JsQ6+y6JBnVsy5A=
Expand Down
4 changes: 4 additions & 0 deletions rest/blip_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ type BlipTesterClientOpts struct {

// a deltaSrc rev ID for which to reject a delta
rejectDeltasForSrcRev string

// optional Origin header
origin *string
}

// BlipTesterClient is a fully fledged client to emulate CBL behaviour on both push and pull replications through methods on this type.
Expand Down Expand Up @@ -539,6 +542,7 @@ func newBlipTesterReplication(tb testing.TB, id string, btc *BlipTesterClient, s
connectingUserChannelGrants: btc.Channels,
blipProtocols: btc.SupportedBLIPProtocols,
skipCollectionsInitialization: skipCollectionsInitialization,
origin: btc.origin,
}, btc.rt)
if err != nil {
return nil, err
Expand Down
26 changes: 25 additions & 1 deletion rest/blip_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ package rest
import (
"fmt"
"net/http"
"net/url"

"github.com/couchbase/sync_gateway/db"

Expand All @@ -36,8 +37,11 @@ func (h *handler) handleBLIPSync() error {
blip.CompressionLevel = *c
}

// error is checked at the time of database load, and ignored at this time
originPatterns, _ := hostOnlyCORS(h.db.CORS.Origin)

// Create a BLIP context:
blipContext, err := db.NewSGBlipContext(h.ctx(), "")
blipContext, err := db.NewSGBlipContext(h.ctx(), "", originPatterns)
if err != nil {
return err
}
Expand Down Expand Up @@ -71,3 +75,23 @@ func (h *handler) handleBLIPSync() error {

return nil
}

// hostOnlyCORS returns the host portion of the origin URL, suitable for passing to websocket library.
func hostOnlyCORS(originPatterns []string) ([]string, error) {
var origins []string
var multiError *base.MultiError
for _, origin := range originPatterns {
// this is a special pattern for allowing all origins
if origin == "*" {
origins = append(origins, origin)
continue
}
u, err := url.Parse(origin)
if err != nil {
multiError = multiError.Append(fmt.Errorf("%s is not a valid pattern for CORS config", err))
continue
}
origins = append(origins, u.Host)
}
return origins, multiError.ErrorOrNil()
}
72 changes: 72 additions & 0 deletions rest/blip_sync_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright 2023-Present Couchbase, Inc.
//
// Use of this software is governed by the Business Source License included
// in the file licenses/BSL-Couchbase.txt. As of the Change Date specified
// in that file, in accordance with the Business Source License, use of this
// software will be governed by the Apache License, Version 2.0, included in
// the file licenses/APL2.txt.

package rest

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func TestHostOnlyCORS(t *testing.T) {
const unparseableURL = "1http:///example.com"
testsCases := []struct {
input []string
output []string
hasError bool
}{
{
input: []string{"http://example.com"},
output: []string{"example.com"},
},
{
input: []string{"https://example.com", "http://example.com"},
output: []string{"example.com", "example.com"},
},
{
input: []string{"*", "http://example.com"},
output: []string{"*", "example.com"},
},
{
input: []string{"wss://example.com"},
output: []string{"example.com"},
},
{
input: []string{"http://example.com:12345"},
output: []string{"example.com:12345"},
},
{
input: []string{unparseableURL},
output: nil,
hasError: true,
},
{
input: []string{"*", unparseableURL},
output: []string{"*"},
hasError: true,
},
{
input: []string{"*", unparseableURL, "http://example.com"},
output: []string{"*", "example.com"},
hasError: true,
},
}
for _, test := range testsCases {
t.Run(fmt.Sprintf("%v->%v", test.input, test.output), func(t *testing.T) {
output, err := hostOnlyCORS(test.input)
if test.hasError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
assert.Equal(t, test.output, output)
})
}
}
5 changes: 5 additions & 0 deletions rest/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,11 @@ func (dbConfig *DbConfig) validateVersion(ctx context.Context, isEnterpriseEditi
}
}

if dbConfig.CORS != nil {
// these values will likely to be ignored by the CORS handler unless browser sends abornmal Origin headers
_, err := hostOnlyCORS(dbConfig.CORS.Origin)
base.WarnfCtx(ctx, "The cors.origin contains values that may be ignored: %s", err)
}
return multiError.ErrorOrNil()
}

Expand Down
17 changes: 17 additions & 0 deletions rest/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2713,3 +2713,20 @@ func TestDatabaseConfigDropScopes(t *testing.T) {
require.Contains(t, resp.Body.String(), "cannot change scope")

}

func TestBadCORSValuesConfig(t *testing.T) {
if base.UnitTestUrlIsWalrus() {
t.Skip("test only works with CBS/rosmar")
}
rt := NewRestTester(t, &RestTesterConfig{PersistentConfig: true})
defer rt.Close()

// expect database to be created with bad CORS values, but do log a warning
dbConfig := rt.NewDbConfig()
dbConfig.CORS = &auth.CORSConfig{
Origin: []string{"http://example.com", "1http://example.com"},
}
base.AssertLogContains(t, "cors.origin contains values", func() {
rt.CreateDatabase("db", dbConfig)
})
}
Loading

0 comments on commit 52b979c

Please sign in to comment.