Skip to content

Commit

Permalink
Enable SQL migrated CLI commands to authenticate
Browse files Browse the repository at this point in the history
Enable SQL migrated commands authentication in the following ways:

* New Global Flag --password
* DOLT_CLI_PASSWORD environment variable
* Ask for a password with a prompt.
* Automatic authentication to a server using the secret in the sql-server.lock file

One significant change in behavior is that previously if a user presented a non-sense username/password, we'd accept it as a super user identity. If a real user was specified, we would promote that user to a super user - regardless of if the password was correct.

Now, if a --user flag is presented, the user must present a password by flag, env var or prompt. If the user/pwd combination is not a known user, the command will fail. This applies to both local and remote mode.

Important to call out that this isn't about security. If you want to be a super user when using your local instance, you can just not provide a --user. This behavior is to enable consistent behavior of client applications where they need to test permissions.

Related: #3922
  • Loading branch information
macneale4 authored Jun 27, 2023
2 parents 167f778 + a3250f2 commit a9159c2
Show file tree
Hide file tree
Showing 21 changed files with 652 additions and 197 deletions.
34 changes: 34 additions & 0 deletions go/Godeps/LICENSES

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions go/Godeps/update.sh

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions go/Godeps/verify.sh

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 3 additions & 49 deletions go/cmd/dolt/cli/arg_parser_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,52 +75,6 @@ func ParseAuthor(authorStr string) (string, string, error) {
return name, email, nil
}

const (
AllowEmptyFlag = "allow-empty"
SkipEmptyFlag = "skip-empty"
DateParam = "date"
MessageArg = "message"
AuthorParam = "author"
ForceFlag = "force"
DryRunFlag = "dry-run"
SetUpstreamFlag = "set-upstream"
AllFlag = "all"
UpperCaseAllFlag = "ALL"
HardResetParam = "hard"
SoftResetParam = "soft"
CheckoutCoBranch = "b"
NoFFParam = "no-ff"
SquashParam = "squash"
AbortParam = "abort"
CopyFlag = "copy"
MoveFlag = "move"
DeleteFlag = "delete"
DeleteForceFlag = "D"
OutputOnlyFlag = "output-only"
RemoteParam = "remote"
BranchParam = "branch"
TrackFlag = "track"
AmendFlag = "amend"
CommitFlag = "commit"
NoCommitFlag = "no-commit"
NoEditFlag = "no-edit"
OursFlag = "ours"
TheirsFlag = "theirs"
NumberFlag = "number"
NotFlag = "not"
MergesFlag = "merges"
ParentsFlag = "parents"
MinParentsFlag = "min-parents"
DecorateFlag = "decorate"
OneLineFlag = "oneline"
ShallowFlag = "shallow"
CachedFlag = "cached"
ListFlag = "list"
UserParam = "user"
NoPrettyFlag = "no-pretty"
ShowIgnoredFlag = "ignored"
)

const (
SyncBackupId = "sync"
SyncBackupUrlId = "sync-url"
Expand Down Expand Up @@ -202,7 +156,7 @@ func CreateCloneArgParser() *argparser.ArgParser {
ap.SupportsString(dbfactory.AWSCredsProfile, "", "profile", "AWS profile to use.")
ap.SupportsString(dbfactory.OSSCredsFileParam, "", "file", "OSS credentials file.")
ap.SupportsString(dbfactory.OSSCredsProfile, "", "profile", "OSS profile to use.")
ap.SupportsString(UserParam, "u", "user", "User name to use when authenticating with the remote. Gets password from the environment variable {{.EmphasisLeft}}DOLT_REMOTE_PASSWORD{{.EmphasisRight}}.")
ap.SupportsString(UserFlag, "u", "user", "User name to use when authenticating with the remote. Gets password from the environment variable {{.EmphasisLeft}}DOLT_REMOTE_PASSWORD{{.EmphasisRight}}.")
return ap
}

Expand Down Expand Up @@ -247,7 +201,7 @@ func CreateCherryPickArgParser() *argparser.ArgParser {

func CreateFetchArgParser() *argparser.ArgParser {
ap := argparser.NewArgParserWithVariableArgs("fetch")
ap.SupportsString(UserParam, "u", "user", "User name to use when authenticating with the remote. Gets password from the environment variable {{.EmphasisLeft}}DOLT_REMOTE_PASSWORD{{.EmphasisRight}}.")
ap.SupportsString(UserFlag, "u", "user", "User name to use when authenticating with the remote. Gets password from the environment variable {{.EmphasisLeft}}DOLT_REMOTE_PASSWORD{{.EmphasisRight}}.")
return ap
}

Expand All @@ -270,7 +224,7 @@ func CreatePullArgParser() *argparser.ArgParser {
ap.SupportsFlag(CommitFlag, "", "Perform the merge and commit the result. This is the default option, but can be overridden with the --no-commit flag. Note that this option does not affect fast-forward merges, which don't create a new merge commit, and if any merge conflicts or constraint violations are detected, no commit will be attempted.")
ap.SupportsFlag(NoCommitFlag, "", "Perform the merge and stop just before creating a merge commit. Note this will not prevent a fast-forward merge; use the --no-ff arg together with the --no-commit arg to prevent both fast-forwards and merge commits.")
ap.SupportsFlag(NoEditFlag, "", "Use an auto-generated commit message when creating a merge commit. The default for interactive CLI sessions is to open an editor.")
ap.SupportsString(UserParam, "u", "user", "User name to use when authenticating with the remote. Gets password from the environment variable {{.EmphasisLeft}}DOLT_REMOTE_PASSWORD{{.EmphasisRight}}.")
ap.SupportsString(UserFlag, "u", "user", "User name to use when authenticating with the remote. Gets password from the environment variable {{.EmphasisLeft}}DOLT_REMOTE_PASSWORD{{.EmphasisRight}}.")
return ap
}

Expand Down
86 changes: 86 additions & 0 deletions go/cmd/dolt/cli/credentials.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2023 Dolthub, 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 cli

import (
"fmt"
"os"

"golang.org/x/crypto/ssh/terminal"

"github.com/dolthub/dolt/go/libraries/utils/argparser"
)

type UserPassword struct {
Username string
Password string
Specified bool // If true, the user and password were provided by the user.
}

const DOLT_ENV_PWD = "DOLT_CLI_PASSWORD"
const DOLT_SILENCE_USER_REQ_FOR_TESTING = "DOLT_SILENCE_USER_REQ_FOR_TESTING"

// BuildUserPasswordPrompt builds a UserPassword struct from the parsed args. The user is prompted for a password if one
// is not provided. If a username is not provided, the default is "root" (which will not be allowed is a password is
// provided). A new instances of ArgParseResults is returned which does not contain the user or password flags.
func BuildUserPasswordPrompt(parsedArgs *argparser.ArgParseResults) (newParsedArgs *argparser.ArgParseResults, credentials *UserPassword, err error) {
userId, hasUserId := parsedArgs.GetValue(UserFlag)
password, hasPassword := parsedArgs.GetValue(PasswordFlag)

if !hasPassword {
envPassword, hasEnvPassword := os.LookupEnv(DOLT_ENV_PWD)
if hasEnvPassword {
password = envPassword
hasPassword = true
}
}

newParsedArgs = parsedArgs.DropValue(UserFlag)
newParsedArgs = newParsedArgs.DropValue(PasswordFlag)

if !hasUserId && !hasPassword {
// Common "out of box" behavior.
return newParsedArgs, &UserPassword{Username: "root", Password: "", Specified: false}, nil
}

if hasUserId && hasPassword {
return newParsedArgs, &UserPassword{Username: userId, Password: password, Specified: true}, nil
}

if hasUserId && !hasPassword {
password = ""
val, hasVal := os.LookupEnv(DOLT_ENV_PWD)
if hasVal {
password = val
} else {
Printf("Enter password: ")
passwordBytes, err := terminal.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return nil, nil, err
}
password = string(passwordBytes) // Assuming UTF-8 for time being. This may not work forever.
}
return newParsedArgs, &UserPassword{Username: userId, Password: password, Specified: true}, nil
}

testOverride, hasTestOverride := os.LookupEnv(DOLT_SILENCE_USER_REQ_FOR_TESTING)
if hasTestOverride && testOverride == "Y" {
// Used for BATS testing only. Typical usage will not hit this path, but we have many legacy tests which
// do not provide a user, and the DOLT_ENV_PWD is set to avoid the prompt.
return newParsedArgs, &UserPassword{Specified: false}, nil
}

return nil, nil, fmt.Errorf("When a password is provided, a user must also be provided. Use the --user flag to provide a username")
}
64 changes: 64 additions & 0 deletions go/cmd/dolt/cli/flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright 2023 Dolthub, 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 cli

// Constants for command line flags names. These tend to be used in multiple places, so defining
// them low in the package dependency tree makes sense.
const (
AbortParam = "abort"
AllFlag = "all"
AllowEmptyFlag = "allow-empty"
AmendFlag = "amend"
AuthorParam = "author"
BranchParam = "branch"
CachedFlag = "cached"
CheckoutCoBranch = "b"
CommitFlag = "commit"
CopyFlag = "copy"
DateParam = "date"
DecorateFlag = "decorate"
DeleteFlag = "delete"
DeleteForceFlag = "D"
DryRunFlag = "dry-run"
ForceFlag = "force"
HardResetParam = "hard"
ListFlag = "list"
MergesFlag = "merges"
MessageArg = "message"
MinParentsFlag = "min-parents"
MoveFlag = "move"
NoCommitFlag = "no-commit"
NoEditFlag = "no-edit"
NoFFParam = "no-ff"
NoPrettyFlag = "no-pretty"
NotFlag = "not"
NumberFlag = "number"
OneLineFlag = "oneline"
OursFlag = "ours"
OutputOnlyFlag = "output-only"
ParentsFlag = "parents"
PasswordFlag = "password"
RemoteParam = "remote"
SetUpstreamFlag = "set-upstream"
ShallowFlag = "shallow"
ShowIgnoredFlag = "ignored"
SkipEmptyFlag = "skip-empty"
SoftResetParam = "soft"
SquashParam = "squash"
TheirsFlag = "theirs"
TrackFlag = "track"
UpperCaseAllFlag = "ALL"
UserFlag = "user"
)
4 changes: 2 additions & 2 deletions go/cmd/dolt/commands/clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,15 +237,15 @@ func validateAndParseDolthubUrl(urlStr string) (string, bool) {
}

func getRemoteUserAndPassConfig(apr *argparser.ArgParseResults) (*creds.DoltCredsForPass, errhand.VerboseError) {
if !apr.Contains(cli.UserParam) {
if !apr.Contains(cli.UserFlag) {
return nil, nil
}
pass, found := os.LookupEnv("DOLT_REMOTE_PASSWORD")
if !found {
return nil, errhand.BuildDError("error: must set DOLT_REMOTE_PASSWORD environment variable to use --user param").Build()
}
return &creds.DoltCredsForPass{
Username: apr.GetValueOrDefault(cli.UserParam, ""),
Username: apr.GetValueOrDefault(cli.UserFlag, ""),
Password: pass,
}, nil
}
16 changes: 16 additions & 0 deletions go/cmd/dolt/commands/sqlserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"net"
"net/http"
"os"
"runtime"
"strconv"
"time"
Expand All @@ -45,6 +46,10 @@ import (
"github.com/dolthub/dolt/go/libraries/doltcore/sqlserver"
)

const (
LocalConnectionUser = "__dolt_local_user__"
)

// Serve starts a MySQL-compatible server. Returns any errors that were encountered.
func Serve(
ctx context.Context,
Expand Down Expand Up @@ -219,6 +224,12 @@ func Serve(
lck := env.NewDBLock(serverConfig.Port())
sqlserver.SetRunningServer(mySQLServer, &lck)

localConnectionHost := "localhost"
if isDoltTestEnvSet() {
localConnectionHost = "%"
}
sqlEngine.GetUnderlyingEngine().Analyzer.Catalog.MySQLDb.AddSuperUser(LocalConnectionUser, localConnectionHost, lck.Secret)

var metSrv *http.Server
if serverConfig.MetricsHost() != "" && serverConfig.MetricsPort() > 0 {
mux := http.NewServeMux()
Expand Down Expand Up @@ -508,3 +519,8 @@ func checkForUnixSocket(config ServerConfig) (string, bool, error) {

return "", false, nil
}

// isDoltTestEnvSet Temporary function to enable __dolt_local_user__ to be used until https://github.com/dolthub/dolt/issues/6239 is resolved
func isDoltTestEnvSet() bool {
return os.Getenv("DOLT_ENABLE_LOCAL_USER_FOR_ALL_HOSTS") != ""
}
6 changes: 3 additions & 3 deletions go/cmd/dolt/commands/sqlserver/sqlclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -475,13 +475,13 @@ func (c ConnectionQueryist) Query(ctx *sql.Context, query string) (sql.Schema, s

// BuildConnectionStringQueryist returns a Queryist that connects to the server specified by the given server config. Presence in this
// module isn't ideal, but it's the only way to get the server config into the queryist.
func BuildConnectionStringQueryist(ctx context.Context, cwdFS filesys.Filesys, apr *argparser.ArgParseResults, port int, database string) (cli.LateBindQueryist, error) {
serverConfig, err := GetServerConfig(cwdFS, apr)
func BuildConnectionStringQueryist(ctx context.Context, cwdFS filesys.Filesys, creds *cli.UserPassword, apr *argparser.ArgParseResults, port int, database string) (cli.LateBindQueryist, error) {
clientConfig, err := GetClientConfig(cwdFS, creds, apr)
if err != nil {
return nil, err
}

parsedMySQLConfig, err := mysqlDriver.ParseDSN(ConnectionString(serverConfig, database))
parsedMySQLConfig, err := mysqlDriver.ParseDSN(ConnectionString(clientConfig, database))
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit a9159c2

Please sign in to comment.