From 926dcbbc6359b7a71ac38bb093a6e628f56586f2 Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Fri, 9 Dec 2022 18:28:16 +0100 Subject: [PATCH] add a CLI command to reset admin passwords Signed-off-by: Nicola Murino --- go.mod | 7 +- go.sum | 12 +-- internal/cmd/resetpwd.go | 120 ++++++++++++++++++++++++++ internal/dataprovider/dataprovider.go | 5 ++ 4 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 internal/cmd/resetpwd.go diff --git a/go.mod b/go.mod index da655061..4cb07717 100644 --- a/go.mod +++ b/go.mod @@ -70,6 +70,7 @@ require ( golang.org/x/net v0.4.0 golang.org/x/oauth2 v0.3.0 golang.org/x/sys v0.3.0 + golang.org/x/term v0.3.0 golang.org/x/time v0.3.0 google.golang.org/api v0.104.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 @@ -105,7 +106,7 @@ require ( github.com/fatih/color v1.13.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/go-test/deep v1.0.8 // indirect + github.com/go-test/deep v1.1.0 // indirect github.com/goccy/go-json v0.10.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect @@ -128,7 +129,7 @@ require ( github.com/lestrrat-go/option v1.0.0 // indirect github.com/lib/pq v1.10.7 // indirect github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c // indirect - github.com/magiconair/properties v1.8.6 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect @@ -142,7 +143,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/common v0.38.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spf13/cast v1.5.0 // indirect diff --git a/go.sum b/go.sum index e5de8ee8..f45f3f80 100644 --- a/go.sum +++ b/go.sum @@ -683,8 +683,8 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= -github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= +github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= @@ -1122,8 +1122,8 @@ github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuz github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -1389,8 +1389,9 @@ github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+ github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.34.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= -github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/common v0.38.0 h1:VTQitp6mXTdUoCmDMugDVOJ1opi6ADftKfp/yeqTR/E= +github.com/prometheus/common v0.38.0/go.mod h1:MBXfmBQZrK5XpbCkjofnXs96LD2QQ7fEq4C0xjC/yec= github.com/prometheus/common/assets v0.1.0/go.mod h1:D17UVUE12bHbim7HzwUvtqm6gwBEaDQ0F+hIGbFbccI= github.com/prometheus/common/assets v0.2.0/go.mod h1:D17UVUE12bHbim7HzwUvtqm6gwBEaDQ0F+hIGbFbccI= github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= @@ -2016,6 +2017,7 @@ golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/cmd/resetpwd.go b/internal/cmd/resetpwd.go new file mode 100644 index 00000000..77f076e0 --- /dev/null +++ b/internal/cmd/resetpwd.go @@ -0,0 +1,120 @@ +// Copyright (C) 2019-2022 Nicola Murino +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package cmd + +import ( + "bytes" + "fmt" + "os" + + "github.com/rs/zerolog" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "golang.org/x/term" + + "github.com/drakkan/sftpgo/v2/internal/config" + "github.com/drakkan/sftpgo/v2/internal/dataprovider" + "github.com/drakkan/sftpgo/v2/internal/logger" + "github.com/drakkan/sftpgo/v2/internal/util" +) + +var ( + resetPwdAdmin string + resetPwdCmd = &cobra.Command{ + Use: "resetpwd", + Short: "Reset the password for the specified administrator", + Long: `This command reads the data provider connection details from the specified +configuration file and resets the password for the specified administrator. +This command is not supported for the memory provider. +For embedded providers like bolt and SQLite you should stop the running SFTPGo +instance to avoid database corruption. + +Please take a look at the usage below to customize the options.`, + Run: func(_ *cobra.Command, _ []string) { + logger.DisableLogger() + logger.EnableConsoleLogger(zerolog.DebugLevel) + configDir = util.CleanDirInput(configDir) + err := config.LoadConfig(configDir, configFile) + if err != nil { + logger.WarnToConsole("Unable to initialize data provider, config load error: %v", err) + os.Exit(1) + } + kmsConfig := config.GetKMSConfig() + err = kmsConfig.Initialize() + if err != nil { + logger.ErrorToConsole("unable to initialize KMS: %v", err) + os.Exit(1) + } + mfaConfig := config.GetMFAConfig() + err = mfaConfig.Initialize() + if err != nil { + logger.ErrorToConsole("Unable to initialize MFA: %v", err) + os.Exit(1) + } + providerConf := config.GetProviderConf() + if providerConf.Driver == dataprovider.MemoryDataProviderName { + logger.ErrorToConsole("memory provider is not supported") + os.Exit(1) + } + // ignore actions + providerConf.Actions.Hook = "" + providerConf.Actions.ExecuteFor = nil + providerConf.Actions.ExecuteOn = nil + logger.InfoToConsole("Initializing provider: %q config file: %q", providerConf.Driver, viper.ConfigFileUsed()) + err = dataprovider.Initialize(providerConf, configDir, false) + if err != nil { + logger.ErrorToConsole("Unable to initialize data provider: %v", err) + os.Exit(1) + } + admin, err := dataprovider.AdminExists(resetPwdAdmin) + if err != nil { + logger.ErrorToConsole("Unable to get admin %q: %v", resetPwdAdmin, err) + os.Exit(1) + } + fmt.Printf("Enter Password: ") + pwd, err := term.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + logger.ErrorToConsole("Unable to read the password: %v", err) + os.Exit(1) + } + fmt.Println("") + fmt.Printf("Confirm Password: ") + confirmPwd, err := term.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + logger.ErrorToConsole("Unable to read the password: %v", err) + os.Exit(1) + } + fmt.Println("") + if !bytes.Equal(pwd, confirmPwd) { + logger.ErrorToConsole("Passwords do not match") + os.Exit(1) + } + admin.Password = string(pwd) + if err := dataprovider.UpdateAdmin(&admin, dataprovider.ActionExecutorSystem, "", ""); err != nil { + logger.ErrorToConsole("Unable to update password: %v", err) + os.Exit(1) + } + logger.InfoToConsole("Password updated for admin %q", resetPwdAdmin) + }, + } +) + +func init() { + addConfigFlags(resetPwdCmd) + resetPwdCmd.Flags().StringVar(&resetPwdAdmin, "admin", "", `Administrator username whose password to reset`) + resetPwdCmd.MarkFlagRequired("admin") //nolint:errcheck + + rootCmd.AddCommand(resetPwdCmd) +} diff --git a/internal/dataprovider/dataprovider.go b/internal/dataprovider/dataprovider.go index c36269ee..2ccc7e1e 100644 --- a/internal/dataprovider/dataprovider.go +++ b/internal/dataprovider/dataprovider.go @@ -1942,6 +1942,11 @@ func AddUser(user *User, executor, ipAddress, role string) error { // UpdateUserPassword updates the user password func UpdateUserPassword(username, plainPwd, executor, ipAddress, role string) error { + if config.PasswordValidation.Users.MinEntropy > 0 { + if err := passwordvalidator.Validate(plainPwd, config.PasswordValidation.Users.MinEntropy); err != nil { + return util.NewValidationError(err.Error()) + } + } hashedPwd, err := hashPlainPassword(plainPwd) if err != nil { return util.NewGenericError(fmt.Sprintf("unable to set the new password: %v", err))