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

Implement CLI setup #4250

Closed
wants to merge 74 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
7df8baa
wip
b1ron Apr 18, 2024
c8f28c1
wip
b1ron Apr 18, 2024
ab6df03
remove PLAIN
b1ron Apr 18, 2024
8819d3f
rename to pwd
b1ron Apr 18, 2024
adb9463
wip
b1ron Apr 19, 2024
f6a7ba0
remove TODO
b1ron Apr 19, 2024
30c8ff0
wip
b1ron Apr 19, 2024
225f3e3
small fixes
b1ron Apr 19, 2024
a09b3ab
fix error
b1ron Apr 19, 2024
4cae917
remove pwdi
b1ron Apr 19, 2024
15ea308
change import declaration for util/password
b1ron Apr 19, 2024
d6471d6
db is dbName
b1ron Apr 19, 2024
0eaf989
Merge branch 'main' into backend-user-creation
b1ron Apr 19, 2024
17e6bed
some comments
b1ron Apr 19, 2024
543b34d
export MakeCredentials and fix MsgUpdateUser
b1ron Apr 19, 2024
61161df
export MakeCredentials this way
b1ron Apr 19, 2024
d23e3d7
handle empty password in makeCredentials
b1ron Apr 19, 2024
e815770
grammar
b1ron Apr 19, 2024
b9f155b
mechanisms field must not be empty
b1ron Apr 19, 2024
3cb51a1
consistent error message
b1ron Apr 19, 2024
19dbc08
swap
b1ron Apr 19, 2024
1d6fc26
wip
b1ron Apr 19, 2024
0fe87f9
comment
b1ron Apr 19, 2024
efe47c5
move under setup check
b1ron Apr 19, 2024
6d59802
BadValue will conflict with ErrorCodeDatabaseDoesNotExist error
b1ron Apr 22, 2024
7d3e832
use handler errors in backends package to not break integration tests
b1ron Apr 22, 2024
3ef9654
avoid escaping quotes
b1ron Apr 22, 2024
c07f239
lint
b1ron Apr 22, 2024
bece4c1
do not import handlererrors
b1ron Apr 22, 2024
a46b7de
fix format
b1ron Apr 22, 2024
f06a1e7
revert changes
b1ron Apr 23, 2024
8562208
handle ErrUserAlreadyExists error properly
b1ron Apr 23, 2024
94032cc
return ErrorCodeBadValue for bad mechanism
b1ron Apr 23, 2024
acf6c9b
handle bad mechanism error
b1ron Apr 23, 2024
963fb2c
internal error method and make ErrorCodeBadValue error
b1ron Apr 23, 2024
e54be90
revert formatting
b1ron Apr 23, 2024
e1fdf31
handle bad mechanism error
b1ron Apr 23, 2024
23ebd49
clean
b1ron Apr 23, 2024
57ecf71
pwdi
b1ron Apr 23, 2024
a2d81f1
ErrorCodeStringProhibited
b1ron Apr 23, 2024
3c65476
handler error
b1ron Apr 23, 2024
f76cf0d
revert changes to error.go
b1ron Apr 24, 2024
dc771b0
revert changes and check if mechanisms.Len is zero
b1ron Apr 24, 2024
25be364
use SCRAM-SHA-1 as default when nil
b1ron Apr 24, 2024
a20b461
revert some error handling
b1ron Apr 24, 2024
a06e1fe
handle unknown auth mechanism error properly
b1ron Apr 24, 2024
f550672
handle unknown auth mechanism error properly
b1ron Apr 24, 2024
ee77458
handler prohibited character
b1ron Apr 24, 2024
0f8392d
use strings.Contains for prohibited character error
b1ron Apr 24, 2024
92f33a9
nolint:goconst test
b1ron Apr 24, 2024
044fad0
revert
b1ron Apr 24, 2024
8d1c89d
lint
b1ron Apr 24, 2024
8f428b3
lint
b1ron Apr 26, 2024
f52f8a9
Merge branch 'main' into backend-user-creation
b1ron Apr 26, 2024
0db37bc
add directive nolint:goconst
b1ron Apr 26, 2024
7ded01c
lint
b1ron Apr 26, 2024
b9e84ee
Merge branch 'main' into backend-user-creation
b1ron Apr 29, 2024
127bb72
Merge branch 'main' into backend-user-creation
b1ron Apr 30, 2024
ca471cd
Merge branch 'main' into backend-user-creation
b1ron May 7, 2024
0398d09
return err and set all mechanisms when nil
b1ron May 7, 2024
804403d
comment change
b1ron May 7, 2024
8857b14
use helper
b1ron May 7, 2024
3c8d599
remove unnecessary logic
b1ron May 7, 2024
21f7cb7
tweaks
b1ron May 8, 2024
b5e6847
Merge branch 'main' into backend-user-creation
b1ron May 8, 2024
ce05e91
Merge branch 'main' into backend-user-creation
AlekSi May 27, 2024
c6edf51
Merge branch 'main' into backend-user-creation
AlekSi May 28, 2024
42d4d7d
Move
AlekSi May 28, 2024
990f1d2
Refactor
AlekSi May 28, 2024
5e31a54
Refactor
AlekSi May 28, 2024
43c2d6d
Merge branch 'backend-user-creation' into setup-cli
AlekSi May 28, 2024
d24a4df
Remove stubs
AlekSi May 28, 2024
3df4bb5
Initial code
AlekSi May 28, 2024
69cfc52
WIP
AlekSi May 28, 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
53 changes: 36 additions & 17 deletions cmd/ferretdb/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import (
"github.com/FerretDB/FerretDB/internal/util/debugbuild"
"github.com/FerretDB/FerretDB/internal/util/logging"
"github.com/FerretDB/FerretDB/internal/util/must"
"github.com/FerretDB/FerretDB/internal/util/password"
"github.com/FerretDB/FerretDB/internal/util/state"
"github.com/FerretDB/FerretDB/internal/util/telemetry"
)
Expand All @@ -58,12 +59,6 @@ var cli struct {
StateDir string `default:"." help:"Process state directory."`
ReplSetName string `default:"" help:"Replica set name."`

Setup struct {
Username string `default:"" help:"Username for setup."`
Password string `default:"" help:"Password for setup."`
Timeout time.Duration `default:"30s" help:"Timeout for setup."`
} `embed:"" prefix:"setup-"`

Listen struct {
Addr string `default:"127.0.0.1:27017" help:"Listen TCP address."`
Unix string `default:"" help:"Listen Unix domain socket path."`
Expand All @@ -85,6 +80,13 @@ var cli struct {
// see setCLIPlugins
kong.Plugins

Setup struct {
Database string `default:"" help:"FIXME"` // FIXME
Username string `default:"" help:"Setup user during backend initialization."`
Password string `default:"" help:"Setup user's password."`
Timeout time.Duration `default:"30s" help:"Setup timeout."`
} `embed:"" prefix:"setup-"`

Log struct {
Level string `default:"${default_log_level}" help:"${help_log_level}"`
Format string `default:"console" help:"${help_log_format}" enum:"${enum_log_format}"`
Expand All @@ -106,9 +108,10 @@ var cli struct {
Percentage uint8 `default:"10" help:"Experimental: percentage of documents to cleanup."`
} `embed:"" prefix:"capped-cleanup-"`

EnableNewAuth bool `default:"false" help:"Experimental: enable new authentication."`
BatchSize int `default:"100" help:"Experimental: maximum insertion batch size."`
MaxBsonObjectSizeMiB int `default:"16" help:"Experimental: maximum BSON object size in MiB."`
EnableNewAuth bool `default:"false" help:"Experimental: enable new authentication."`

BatchSize int `default:"100" help:"Experimental: maximum insertion batch size."`
MaxBsonObjectSizeMiB int `default:"16" help:"Experimental: maximum BSON object size in MiB."`

Telemetry struct {
URL string `default:"https://beacon.ferretdb.com/" help:"Telemetry: reporting URL."`
Expand Down Expand Up @@ -292,6 +295,23 @@ func setupLogger(stateProvider *state.Provider, format string) *zap.Logger {
return l
}

// checkFlags checks that CLI flags are not self-contradictory.
func checkFlags(logger *zap.Logger) {
l := logger.Sugar()

if (cli.Setup.Database == "") != (cli.Setup.Username == "") {
l.Fatal("--setup-database should be used together with --setup-username")
}

if cli.Setup.Database != "" && !cli.Test.EnableNewAuth {
l.Fatal("--setup-database requires --test-enable-new-auth")
}

if cli.Test.DisablePushdown && cli.Test.EnableNestedPushdown {
l.Fatal("--test-disable-pushdown and --test-enable-nested-pushdown should not be set at the same time")
}
}

// runTelemetryReporter runs telemetry reporter until ctx is canceled.
func runTelemetryReporter(ctx context.Context, opts *telemetry.NewReporterOpts) {
r, err := telemetry.NewReporter(opts)
Expand Down Expand Up @@ -347,6 +367,8 @@ func run() {

logger := setupLogger(stateProvider, cli.Log.Format)

checkFlags(logger)

if _, err := maxprocs.Set(maxprocs.Logger(logger.Sugar().Debugf)); err != nil {
logger.Sugar().Warnf("Failed to set GOMAXPROCS: %s.", err)
}
Expand All @@ -361,14 +383,6 @@ func run() {

var wg sync.WaitGroup

if cli.Setup.Username != "" && !cli.Test.EnableNewAuth {
logger.Sugar().Fatal("--setup-username requires --test-enable-new-auth")
}

if cli.Test.DisablePushdown && cli.Test.EnableNestedPushdown {
logger.Sugar().Fatal("--test-disable-pushdown and --test-enable-nested-pushdown should not be set at the same time")
}

if cli.DebugAddr != "" && cli.DebugAddr != "-" {
wg.Add(1)

Expand Down Expand Up @@ -408,6 +422,11 @@ func run() {
TCPHost: cli.Listen.Addr,
ReplSetName: cli.ReplSetName,

SetupDatabase: cli.Setup.Database,
SetupUsername: cli.Setup.Username,
SetupPassword: password.WrapPassword(cli.Setup.Password),
SetupTimeout: cli.Setup.Timeout,

PostgreSQLURL: postgreSQLFlags.PostgreSQLURL,

SQLiteURL: sqliteFlags.SQLiteURL,
Expand Down
118 changes: 118 additions & 0 deletions internal/backends/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright 2021 FerretDB Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package backends

import (
"context"
"errors"

"github.com/google/uuid"

"github.com/FerretDB/FerretDB/internal/types"
"github.com/FerretDB/FerretDB/internal/util/iterator"
"github.com/FerretDB/FerretDB/internal/util/lazyerrors"
"github.com/FerretDB/FerretDB/internal/util/must"
"github.com/FerretDB/FerretDB/internal/util/password"
)

// CreateUserParams represents the parameters of CreateUser function.
//
//nolint:vet // for readability
type CreateUserParams struct {
Database string
Username string
Password password.Password
Mechanisms *types.Array
}

// CreateUser stores a new user in the given backend.
func CreateUser(ctx context.Context, b Backend, params *CreateUserParams) error {
must.NotBeZero(params)

credentials, err := MakeCredentials(params.Username, params.Password, params.Mechanisms)
if err != nil {
return err
}

id := uuid.New()
saved := must.NotFail(types.NewDocument(
"_id", params.Database+"."+params.Username,
"credentials", credentials,
"user", params.Username,
"db", params.Database,
"roles", types.MakeArray(0),
"userId", types.Binary{Subtype: types.BinaryUUID, B: must.NotFail(id.MarshalBinary())},
))

db := must.NotFail(b.Database("admin"))
coll := must.NotFail(db.Collection("system.users"))

_, err = coll.InsertAll(ctx, &InsertAllParams{
Docs: []*types.Document{saved},
})

return err
}

// MakeCredentials creates a document with credentials for the chosen mechanisms.
// The mechanisms array must be validated by the caller.
func MakeCredentials(username string, userPassword password.Password, mechanisms *types.Array) (*types.Document, error) {
credentials := types.MakeDocument(0)

if mechanisms == nil {
mechanisms = must.NotFail(types.NewArray("SCRAM-SHA-1", "SCRAM-SHA-256"))

Check warning on line 75 in internal/backends/user.go

View check run for this annotation

Codecov / codecov/patch

internal/backends/user.go#L75

Added line #L75 was not covered by tests
}

iter := mechanisms.Iterator()
defer iter.Close()

for {
var v any
_, v, err := iter.Next()

if errors.Is(err, iterator.ErrIteratorDone) {
break
}

if err != nil {
return nil, lazyerrors.Error(err)

Check warning on line 90 in internal/backends/user.go

View check run for this annotation

Codecov / codecov/patch

internal/backends/user.go#L90

Added line #L90 was not covered by tests
}

var hash *types.Document

switch v {
case "PLAIN":
credentials.Set("PLAIN", must.NotFail(password.PlainHash(userPassword.Password())))
case "SCRAM-SHA-1":
hash, err = password.SCRAMSHA1Hash(username, userPassword.Password())
if err != nil {
return nil, err

Check warning on line 101 in internal/backends/user.go

View check run for this annotation

Codecov / codecov/patch

internal/backends/user.go#L101

Added line #L101 was not covered by tests
}

credentials.Set("SCRAM-SHA-1", hash)
case "SCRAM-SHA-256":
hash, err = password.SCRAMSHA256Hash(userPassword.Password())
if err != nil {
return nil, err
}

credentials.Set("SCRAM-SHA-256", hash)
default:
panic("unknown mechanism")

Check warning on line 113 in internal/backends/user.go

View check run for this annotation

Codecov / codecov/patch

internal/backends/user.go#L112-L113

Added lines #L112 - L113 were not covered by tests
}
}

return credentials, nil
}
1 change: 0 additions & 1 deletion internal/clientconn/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,6 @@ func acceptLoop(ctx context.Context, listener net.Listener, wg *sync.WaitGroup,
retry++
ctxutil.SleepWithJitter(ctx, time.Second, retry)
}
continue
}

wg.Add(1)
Expand Down
2 changes: 1 addition & 1 deletion internal/handler/authenticate.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (h *Handler) authenticate(ctx context.Context) error {
username, userPassword, mechanism := conninfo.Get(ctx).Auth()

switch mechanism {
case "SCRAM-SHA-256", "SCRAM-SHA-1":
case "SCRAM-SHA-256", "SCRAM-SHA-1": //nolint:goconst // we don't need a constant for this
// SCRAM calls back scramCredentialLookup each time Step is called,
// and that checks the authentication.
return nil
Expand Down
52 changes: 52 additions & 0 deletions internal/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@
"github.com/FerretDB/FerretDB/internal/clientconn/connmetrics"
"github.com/FerretDB/FerretDB/internal/clientconn/cursor"
"github.com/FerretDB/FerretDB/internal/types"
"github.com/FerretDB/FerretDB/internal/util/ctxutil"
"github.com/FerretDB/FerretDB/internal/util/iterator"
"github.com/FerretDB/FerretDB/internal/util/lazyerrors"
"github.com/FerretDB/FerretDB/internal/util/must"
"github.com/FerretDB/FerretDB/internal/util/password"
"github.com/FerretDB/FerretDB/internal/util/state"
)

Expand Down Expand Up @@ -71,6 +73,11 @@
TCPHost string
ReplSetName string

SetupDatabase string
SetupUsername string
SetupPassword password.Password
SetupTimeout time.Duration

L *zap.Logger
ConnMetrics *connmetrics.ConnMetrics
StateProvider *state.Provider
Expand Down Expand Up @@ -126,6 +133,11 @@
),
}

if err := h.setupFIXME(); err != nil {
h.Close()
return nil, err

Check warning on line 138 in internal/handler/handler.go

View check run for this annotation

Codecov / codecov/patch

internal/handler/handler.go#L137-L138

Added lines #L137 - L138 were not covered by tests
}

h.initCommands()

h.wg.Add(1)
Expand All @@ -139,6 +151,46 @@
return h, nil
}

// FIXME

Check failure on line 154 in internal/handler/handler.go

View workflow job for this annotation

GitHub Actions / golangci-lint

Comment should end in a period (godot)
func (h *Handler) setupFIXME() error {
if h.SetupDatabase == "" {
return nil
}

ctx, cancel := context.WithTimeout(context.TODO(), h.SetupTimeout)
defer cancel()

Check warning on line 161 in internal/handler/handler.go

View check run for this annotation

Codecov / codecov/patch

internal/handler/handler.go#L160-L161

Added lines #L160 - L161 were not covered by tests

info := conninfo.New()
info.SetBypassBackendAuth()

Check warning on line 164 in internal/handler/handler.go

View check run for this annotation

Codecov / codecov/patch

internal/handler/handler.go#L163-L164

Added lines #L163 - L164 were not covered by tests

ctx = conninfo.Ctx(ctx, info)

Check warning on line 166 in internal/handler/handler.go

View check run for this annotation

Codecov / codecov/patch

internal/handler/handler.go#L166

Added line #L166 was not covered by tests

var retry int64
for ctx.Err() == nil {
if _, err := h.b.Status(ctx, nil); err == nil {
break

Check warning on line 171 in internal/handler/handler.go

View check run for this annotation

Codecov / codecov/patch

internal/handler/handler.go#L168-L171

Added lines #L168 - L171 were not covered by tests
}

retry++
ctxutil.SleepWithJitter(ctx, time.Second, retry)

Check warning on line 175 in internal/handler/handler.go

View check run for this annotation

Codecov / codecov/patch

internal/handler/handler.go#L174-L175

Added lines #L174 - L175 were not covered by tests
}

res, err := h.b.ListDatabases(ctx, &backends.ListDatabasesParams{Name: h.SetupDatabase})
if err != nil {
return err

Check warning on line 180 in internal/handler/handler.go

View check run for this annotation

Codecov / codecov/patch

internal/handler/handler.go#L178-L180

Added lines #L178 - L180 were not covered by tests
}

if len(res.Databases) != 0 {
return nil

Check warning on line 184 in internal/handler/handler.go

View check run for this annotation

Codecov / codecov/patch

internal/handler/handler.go#L183-L184

Added lines #L183 - L184 were not covered by tests
}

return backends.CreateUser(ctx, h.b, &backends.CreateUserParams{
Database: h.SetupDatabase,
Username: h.SetupUsername,
Password: h.SetupPassword,
})

Check warning on line 191 in internal/handler/handler.go

View check run for this annotation

Codecov / codecov/patch

internal/handler/handler.go#L187-L191

Added lines #L187 - L191 were not covered by tests
}

// runCappedCleanup calls capped collections cleanup function according to the given interval.
func (h *Handler) runCappedCleanup() {
if h.CappedCleanupInterval <= 0 {
Expand Down