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

Use authentication enabled docker for integration test #4160

Merged
merged 46 commits into from Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
8f0483c
wip
chilagrow Mar 6, 2024
1ebdda2
implement local host exception
chilagrow Mar 7, 2024
95c5d4e
fix logout
chilagrow Mar 7, 2024
ec0bc37
Add helper for checking local peers
AlekSi Mar 7, 2024
ba2da29
lint
chilagrow Mar 7, 2024
64376be
Merge branch 'main' into localhost-exception
chilagrow Mar 7, 2024
68d5cc0
update logout test
chilagrow Mar 7, 2024
2c24034
Merge branch 'local' into localhost-exception
chilagrow Mar 7, 2024
4e1388f
update local host exception
chilagrow Mar 7, 2024
baf646c
fmt
chilagrow Mar 7, 2024
a503adc
update
chilagrow Mar 7, 2024
7197ad1
make connectionStatus same as proxy
chilagrow Mar 7, 2024
5e838f7
add re-authenticate test and cleanup tests
chilagrow Mar 8, 2024
7c7c2e4
use authenticated user for supported mechanisms test
chilagrow Mar 8, 2024
205d7d0
fix test
chilagrow Mar 8, 2024
681855f
revert setup back
chilagrow Mar 8, 2024
dd2d240
add ipv6 test
chilagrow Mar 8, 2024
f3bbd12
remove mongodb specific condition on setup user
chilagrow Mar 8, 2024
e7a10f8
cleanup user
chilagrow Mar 8, 2024
b71bada
user is created per test and authenticated user is used as client
chilagrow Mar 11, 2024
f594641
check error
chilagrow Mar 11, 2024
0aa5c97
Merge branch 'main' into localhost-exception
AlekSi Mar 11, 2024
ae8c54d
merge conflict
chilagrow Mar 11, 2024
27c402c
handle empty mechanism for local host exception
chilagrow Mar 12, 2024
d84d716
use command error in case other driver can reach this path
chilagrow Mar 12, 2024
17ffa48
revert change on connectionStatus
chilagrow Mar 12, 2024
e1c6014
Merge branch 'localhost-exception' into cleanup-setupuser
chilagrow Mar 12, 2024
fcf8445
just check user
chilagrow Mar 12, 2024
8f662c7
Merge branch 'localhost-exception' into cleanup-setupuser
chilagrow Mar 12, 2024
004abe6
remove confusing error message
chilagrow Mar 12, 2024
8807bcb
Merge branch 'localhost-exception' into cleanup-setupuser
chilagrow Mar 12, 2024
2822e6c
use authenticated mongodb for test-integration-mongodb
chilagrow Mar 12, 2024
80767f7
merge conflict
chilagrow Mar 13, 2024
30cacc5
use absolute path for mongodb uri
chilagrow Mar 13, 2024
6904c53
assign authorization roles for mongodb users
chilagrow Mar 13, 2024
0603c84
fix changed error code upon authentication
chilagrow Mar 13, 2024
8865890
tablable session and getMore returns authorization error
chilagrow Mar 13, 2024
6b09aad
test cleanup
chilagrow Mar 13, 2024
41dcb5c
readability
chilagrow Mar 13, 2024
09a41f0
update finding certificates
chilagrow Mar 13, 2024
bb434c0
lint
chilagrow Mar 13, 2024
6ef0705
avoid conflict with other PR
chilagrow Mar 13, 2024
ee7a42d
idiomacy
chilagrow Mar 13, 2024
719728a
Merge branch 'main' into cleanup-setupuser
AlekSi Mar 13, 2024
90d2505
Merge branch 'main' into cleanup-setupuser
AlekSi Mar 14, 2024
b063d16
Merge branch 'main' into cleanup-setupuser
AlekSi Mar 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Expand Up @@ -183,7 +183,8 @@ you can run those with `task test-unit` after starting the environment as descri

We also have a set of "integration" tests in the `integration` directory.
They use the Go MongoDB driver like a regular user application.
They could test any MongoDB-compatible database (such as FerretDB or MongoDB itself) via a regular TCP or TLS port or Unix socket.
They could test any MongoDB-compatible database (such as FerretDB or MongoDB itself) via a regular TCP or TLS port
or Unix domain socket.
They also could test in-process FerretDB instances
(meaning that integration tests start and stop them themselves) with a given backend.
Finally, some integration tests (so-called compatibility or "compat" tests) connect to two systems
Expand Down
2 changes: 1 addition & 1 deletion ferretdb/ferretdb.go
Expand Up @@ -214,7 +214,7 @@ func (f *FerretDB) MongoDBURI() string {
Path: "/",
}
case f.config.Listener.Unix != "":
// MongoDB really wants Unix socket path in the host part of the URI
// MongoDB really wants Unix domain socket path in the host part of the URI
u = &url.URL{
Scheme: "mongodb",
Host: f.l.UnixAddr().String(),
Expand Down
120 changes: 77 additions & 43 deletions integration/commands_authentication_test.go
Expand Up @@ -17,23 +17,36 @@ package integration
import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"

"github.com/FerretDB/FerretDB/integration/setup"
"github.com/FerretDB/FerretDB/internal/types"
"github.com/FerretDB/FerretDB/internal/util/must"
"github.com/FerretDB/FerretDB/internal/util/testutil"
)

func TestCommandsAuthenticationLogout(t *testing.T) {
t.Parallel()

ctx, collection := setup.Setup(t)
db := collection.Database()
s := setup.SetupWithOpts(t, nil)
ctx, db := s.Ctx, s.Collection.Database()

opts := options.Client().ApplyURI(s.MongoDBURI)
client, err := mongo.Connect(ctx, opts)
require.NoError(t, err, "cannot connect to MongoDB")

t.Cleanup(func() {
require.NoError(t, client.Disconnect(ctx))
})

db = client.Database(db.Name())

// the test user logs out
var res bson.D
err := db.RunCommand(ctx, bson.D{{"logout", 1}}).Decode(&res)
assert.NoError(t, err)
err = db.RunCommand(ctx, bson.D{{"logout", 1}}).Decode(&res)
require.NoError(t, err)

actual := ConvertDocument(t, res)
actual.Remove("$clusterTime")
Expand All @@ -44,7 +57,7 @@ func TestCommandsAuthenticationLogout(t *testing.T) {

// the test user logs out again, it has no effect
err = db.RunCommand(ctx, bson.D{{"logout", 1}}).Decode(&res)
assert.NoError(t, err)
require.NoError(t, err)

actual = ConvertDocument(t, res)
actual.Remove("$clusterTime")
Expand All @@ -53,47 +66,68 @@ func TestCommandsAuthenticationLogout(t *testing.T) {
testutil.AssertEqual(t, expected, actual)
}

func TestCommandsAuthenticationLogoutTLS(t *testing.T) {
setup.SkipForMongoDB(t, "tls is not enabled for mongodb backend")

func TestCommandsAuthenticationLogoutAuthenticatedUser(t *testing.T) {
t.Parallel()

ctx, collection := setup.Setup(t)
db := collection.Database()

// the test user is authenticated
expectedAuthenticated := bson.D{
{
"authInfo", bson.D{
{"authenticatedUsers", bson.A{bson.D{{"user", "username"}}}},
{"authenticatedUserRoles", bson.A{}},
{"authenticatedUserPrivileges", bson.A{}},
},
},
{"ok", float64(1)},
s := setup.SetupWithOpts(t, &setup.SetupOpts{SetupUser: true})
ctx, db := s.Ctx, s.Collection.Database()
username, password, mechanism := "testuser", "testpass", "SCRAM-SHA-256"

err := db.RunCommand(ctx, bson.D{
{"createUser", username},
{"roles", bson.A{}},
{"pwd", password},
{"mechanisms", bson.A{mechanism}},
}).Err()
require.NoError(t, err, "cannot create user")

credential := options.Credential{
AuthMechanism: mechanism,
AuthSource: db.Name(),
Username: username,
Password: password,
}

opts := options.Client().ApplyURI(s.MongoDBURI).SetAuth(credential)
client, err := mongo.Connect(ctx, opts)
require.NoError(t, err, "cannot connect to MongoDB")

t.Cleanup(func() {
require.NoError(t, client.Disconnect(ctx))
})

db = client.Database(db.Name())

var res bson.D
err := db.RunCommand(ctx, bson.D{{"connectionStatus", 1}}).Decode(&res)
assert.NoError(t, err)
assert.Equal(t, expectedAuthenticated, res)
err = db.RunCommand(ctx, bson.D{{"connectionStatus", 1}}).Decode(&res)
require.NoError(t, err)

actualAuth, err := ConvertDocument(t, res).Get("authInfo")
require.NoError(t, err)

actualUsersV, err := actualAuth.(*types.Document).Get("authenticatedUsers")
require.NoError(t, err)

expectedUsers := must.NotFail(types.NewArray(must.NotFail(types.NewDocument("user", username, "db", db.Name()))))
require.Equal(t, expectedUsers, actualUsersV.(*types.Array))

// the test user logs out
err = db.RunCommand(ctx, bson.D{{"logout", 1}}).Decode(&res)
assert.NoError(t, err)
assert.Equal(t, bson.D{{"ok", float64(1)}}, res)

// the test user is no longer authenticated
expectedUnauthenticated := bson.D{
{
"authInfo", bson.D{
{"authenticatedUsers", bson.A{}},
{"authenticatedUserRoles", bson.A{}},
{"authenticatedUserPrivileges", bson.A{}},
},
},
{"ok", float64(1)},
}
require.NoError(t, err)

actual := ConvertDocument(t, res)
actual.Remove("$clusterTime")
actual.Remove("operationTime")

expected := ConvertDocument(t, bson.D{{"ok", float64(1)}})
testutil.AssertEqual(t, expected, actual)

err = db.RunCommand(ctx, bson.D{{"connectionStatus", 1}}).Decode(&res)
assert.NoError(t, err)
assert.Equal(t, expectedUnauthenticated, res)
require.NoError(t, err)

actualAuth, err = ConvertDocument(t, res).Get("authInfo")
require.NoError(t, err)

actualUsersV, err = actualAuth.(*types.Document).Get("authenticatedUsers")
require.NoError(t, err)
require.Empty(t, actualUsersV)
}
2 changes: 1 addition & 1 deletion integration/commands_diagnostic_test.go
Expand Up @@ -403,7 +403,7 @@ func TestCommandsDiagnosticWhatsMyURI(t *testing.T) {
databaseName := s.Collection.Database().Name()
collectionName := s.Collection.Name()

// only check port number on TCP connection, no need to check on Unix socket
// only check port number on TCP connection, no need to check on Unix domain socket
isUnix := s.IsUnixSocket(t)

// setup second client connection to check that `whatsmyuri` returns different ports
Expand Down
7 changes: 5 additions & 2 deletions integration/hello_command_test.go
Expand Up @@ -61,8 +61,11 @@ func TestHello(t *testing.T) {
func TestHelloWithSupportedMechs(t *testing.T) {
t.Parallel()

ctx, collection := setup.Setup(t, shareddata.Scalars, shareddata.Composites)
db := collection.Database()
s := setup.SetupWithOpts(t, &setup.SetupOpts{
SetupUser: true,
Providers: []shareddata.Provider{shareddata.Scalars, shareddata.Composites},
})
ctx, db := s.Ctx, s.Collection.Database()

usersPayload := []bson.D{
{
Expand Down
66 changes: 48 additions & 18 deletions integration/setup/setup.go
Expand Up @@ -17,6 +17,7 @@

import (
"context"
"errors"
"flag"
"fmt"
"net/url"
Expand All @@ -32,6 +33,7 @@
"go.uber.org/zap"

"github.com/FerretDB/FerretDB/integration/shareddata"
"github.com/FerretDB/FerretDB/internal/handler/handlererrors"
"github.com/FerretDB/FerretDB/internal/util/iterator"
"github.com/FerretDB/FerretDB/internal/util/observability"
"github.com/FerretDB/FerretDB/internal/util/testutil"
Expand All @@ -45,7 +47,7 @@

targetProxyAddrF = flag.String("target-proxy-addr", "", "in-process FerretDB: use given proxy")
targetTLSF = flag.Bool("target-tls", false, "in-process FerretDB: use TLS")
targetUnixSocketF = flag.Bool("target-unix-socket", false, "in-process FerretDB: use Unix socket")
targetUnixSocketF = flag.Bool("target-unix-socket", false, "in-process FerretDB: use Unix domain socket")

postgreSQLURLF = flag.String("postgresql-url", "", "in-process FerretDB: PostgreSQL URL for 'postgresql' handler.")
sqliteURLF = flag.String("sqlite-url", "", "in-process FerretDB: SQLite URI for 'sqlite' handler.")
Expand Down Expand Up @@ -88,6 +90,9 @@

// ExtraOptions sets the options in MongoDB URI, when the option exists it overwrites that option.
ExtraOptions url.Values

// SetupUser true creates a user and returns authenticated client.
SetupUser bool
}

// SetupResult represents setup results.
Expand All @@ -97,12 +102,12 @@
MongoDBURI string
}

// IsUnixSocket returns true if MongoDB URI is a Unix socket.
// IsUnixSocket returns true if MongoDB URI is a Unix domain socket.
func (s *SetupResult) IsUnixSocket(tb testtb.TB) bool {
tb.Helper()

// we can't use a regular url.Parse because
// MongoDB really wants Unix socket path in the host part of the URI
// MongoDB really wants Unix domain socket path in the host part of the URI
opts := options.Client().ApplyURI(s.MongoDBURI)
res := slices.ContainsFunc(opts.Hosts, func(host string) bool {
return strings.Contains(host, "/")
Expand Down Expand Up @@ -161,9 +166,12 @@
// register cleanup function after setupListener registers its own to preserve full logs
tb.Cleanup(cancel)

collection := setupCollection(tb, setupCtx, client, opts)
if opts.SetupUser {
// user is created before the collection so that user can run collection cleanup
AlekSi marked this conversation as resolved.
Show resolved Hide resolved
client = setupUser(tb, ctx, client, uri)
}

setupUser(tb, setupCtx, client)
collection := setupCollection(tb, setupCtx, client, opts)

level.SetLevel(*logLevelF)

Expand Down Expand Up @@ -325,26 +333,48 @@
return
}

// setupUser creates a user in admin database with supported mechanisms.
// The user uses username/password credential which is the same as the database
// credentials. This is done to avoid the need to reconnect as different credential.
// setupUser creates a user in admin database with supported mechanisms. It returns authenticated client.
//
// Without this, once the first user is created, the authentication fails
// as username/password does not exist in admin.system.users collection.
func setupUser(tb testtb.TB, ctx context.Context, client *mongo.Client) {
// Without this, once the first user is created, the authentication fails as local exception no longer applies.
func setupUser(tb testtb.TB, ctx context.Context, client *mongo.Client, uri string) *mongo.Client {
tb.Helper()

if IsMongoDB(tb) {
return
}
// username is unique per test so the user is deleted after the test
username, password := "username"+tb.Name(), "password"
err := client.Database("admin").RunCommand(ctx, bson.D{
{"dropUser", username},
}).Err()

username, password := "username", "password"
var ce mongo.CommandError
if errors.As(err, &ce) && ce.Code != int32(handlererrors.ErrUserNotFound) {
require.NoError(tb, err, "cannot drop user")

Check warning on line 350 in integration/setup/setup.go

View check run for this annotation

Codecov / codecov/patch

integration/setup/setup.go#L350

Added line #L350 was not covered by tests
}

err := client.Database("admin").RunCommand(ctx, bson.D{
err = client.Database("admin").RunCommand(ctx, bson.D{
{"createUser", username},
{"roles", bson.A{}},
{"pwd", password},
{"mechanisms", bson.A{"PLAIN", "SCRAM-SHA-1", "SCRAM-SHA-256"}},
{"mechanisms", bson.A{"SCRAM-SHA-1", "SCRAM-SHA-256"}},
}).Err()
require.NoErrorf(tb, err, "cannot create user")
require.NoError(tb, err, "cannot create user")

credential := options.Credential{
AuthMechanism: "SCRAM-SHA-256",
AuthSource: "admin",
Username: username,
Password: password,
}

opts := options.Client().ApplyURI(uri).SetAuth(credential)
authenticatedClient, err := mongo.Connect(ctx, opts)
require.NoError(tb, err, "cannot connect to MongoDB")

tb.Cleanup(func() {
err = authenticatedClient.Database("admin").RunCommand(ctx, bson.D{
{"dropUser", username},
}).Err()
require.NoError(tb, err, "cannot drop user")
})

return authenticatedClient
}