From e7cd9a3f04ef48e9c0e20a10e2c1b4f4a18fed4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Bergelt?= Date: Fri, 5 May 2017 13:06:33 +0200 Subject: [PATCH 1/2] Added support for different credential providers --- cred/classic.go | 35 +++++++++++++++++++++ cred/cred.go | 26 ++++++++++++++++ cred/interactive.go | 68 +++++++++++++++++++++++++++++++++++++++++ cred/pwdfile.go | 44 ++++++++++++++++++++++++++ cred/wincred_other.go | 16 ++++++++++ cred/wincred_windows.go | 35 +++++++++++++++++++++ deploy/ftp.go | 44 ++++++++++++++++++-------- deploy/sftp.go | 40 +++++++++++++++++------- 8 files changed, 284 insertions(+), 24 deletions(-) create mode 100644 cred/classic.go create mode 100644 cred/cred.go create mode 100644 cred/interactive.go create mode 100644 cred/pwdfile.go create mode 100644 cred/wincred_other.go create mode 100644 cred/wincred_windows.go diff --git a/cred/classic.go b/cred/classic.go new file mode 100644 index 0000000..35301ea --- /dev/null +++ b/cred/classic.go @@ -0,0 +1,35 @@ +package cred + +import ( + "errors" + "github.com/spf13/viper" +) + +/** + CredentialProvider which reads the credentials directly from the config file + (old behavior) +**/ +type ClassicCredProvider struct { +} + +func (cstore ClassicCredProvider) GetCredentials(connectionType string) (credentials *Credentials, err error) { + + uid := viper.GetString(connectionType + ".user") + pwd := viper.GetString(connectionType + ".pwd") + + serr := "" + + if uid == "" { + serr = serr + "UID not found. Define " + connectionType + ".user in config file. " + } + if uid == "" { + serr = serr + "PWD not found. Define " + connectionType + ".pwd in config file. " + } + + if (serr != "") { + return nil, errors.New(serr) + } + + return &Credentials{ UID: uid, PWD: pwd}, nil +} + diff --git a/cred/cred.go b/cred/cred.go new file mode 100644 index 0000000..2b4dd2e --- /dev/null +++ b/cred/cred.go @@ -0,0 +1,26 @@ +package cred + +type Credentials struct { + UID string + PWD string +} + +type CredentialProvider interface { + // connectionType e.g. ftp, sftp + GetCredentials(connectionType string) (*Credentials, error) +} + +func GetCredentialProvider(identifier string) CredentialProvider { + switch identifier { + case "classic": + return ClassicCredProvider{} + case "interactive": + return InteractiveCredProvider{} + case "pwdfile": + return PwdFileCredProvider{} + case "wincred": + return WinCredProvider{} + default: + return nil; + } +} diff --git a/cred/interactive.go b/cred/interactive.go new file mode 100644 index 0000000..ecb08d1 --- /dev/null +++ b/cred/interactive.go @@ -0,0 +1,68 @@ +package cred + +import ( + "fmt" + "os" + "bufio" + "github.com/eiannone/keyboard" + "github.com/spf13/viper" +) + +/** + CredentialProvider which asks the user to input their credentials on stdin + once they are required +**/ +type InteractiveCredProvider struct { +} + +func readPwd() (pwd string, err error) { + s := "" + + for { + char, key, err := keyboard.GetSingleKey() + + if (err != nil) { + return "", err + } + + if (key == keyboard.KeyEnter) { + break + } + + s += string(char) + } + + return s, err +} + +func (cstore InteractiveCredProvider) GetCredentials(connectionType string) (credentials *Credentials, err error) { + reader := bufio.NewReader(os.Stdin) + + fmt.Println("Please enter credentials for connection type " + connectionType); + + // is the username preset? + uname := viper.GetString(connectionType + ".user") + + if (uname != "") { + fmt.Println("Username: " + uname); + } else { + // user has to enter username + fmt.Print("Enter username: "); + unameBy, _, err := reader.ReadLine() + uname = string(unameBy) + + if (err != nil) { + return nil, err + } + } + + fmt.Print("Enter password (input will be hidden): "); + pwd, err := readPwd(); + + if (err != nil) { + return nil, err + } + + return &Credentials{ UID: uname, PWD: pwd }, nil +} + diff --git a/cred/pwdfile.go b/cred/pwdfile.go new file mode 100644 index 0000000..45f727b --- /dev/null +++ b/cred/pwdfile.go @@ -0,0 +1,44 @@ +package cred + +import ( + "errors" + "github.com/spf13/viper" +) + +/** + CredentialProvider which reads the credentials from a separate file +**/ +type PwdFileCredProvider struct { +} + +func (cstore PwdFileCredProvider) GetCredentials(connectionType string) (credentials *Credentials, err error) { + + pwdfile := viper.GetString(connectionType + ".pwdfile") + + pwdConfig := viper.New() + pwdConfig.SetConfigName(pwdfile) + pwdConfig.AddConfigPath(".") + err = pwdConfig.ReadInConfig() + if err != nil { + return nil, err + } + + uid := pwdConfig.GetString(connectionType + ".user") + pwd := pwdConfig.GetString(connectionType + ".pwd") + + serr := "" + + if uid == "" { + serr = serr + "UID not found. Define " + connectionType + ".user in credential config file. " + } + if uid == "" { + serr = serr + "PWD not found. Define " + connectionType + ".pwd in credential config file. " + } + + if (serr != "") { + return nil, errors.New(serr) + } + + return &Credentials{ UID: uid, PWD: pwd}, nil +} + diff --git a/cred/wincred_other.go b/cred/wincred_other.go new file mode 100644 index 0000000..799e4a2 --- /dev/null +++ b/cred/wincred_other.go @@ -0,0 +1,16 @@ +// +build !windows + +package cred + +import "errors" + +/** + Credential Provider which retrieves the credentials from the Windows Credential Manager + (Windows only, naturally) +**/ +type WinCredProvider struct { +} + +func (cstore WinCredProvider) GetCredentials(connectionType string) (credentials *Credentials, err error) { + return nil, errors.New("The wincred credential provider is only supported on Windows"); +} \ No newline at end of file diff --git a/cred/wincred_windows.go b/cred/wincred_windows.go new file mode 100644 index 0000000..fab48e7 --- /dev/null +++ b/cred/wincred_windows.go @@ -0,0 +1,35 @@ +package cred + +import ( + "errors" + "strings" + "github.com/danieljoos/wincred" + "github.com/spf13/viper" +) + +/** + Credential Provider which retrieves the credentials from the Windows Credential Manager + (Windows only, naturally) +**/ +type WinCredProvider struct { +} + +func (cstore WinCredProvider) GetCredentials(connectionType string) (credentials *Credentials, err error) { + identifier := viper.GetString(connectionType + ".wincred-identifier") + + if (identifier == "") { + return nil, errors.New("Wincred identifier not found. Define " + connectionType +".wincred-identifier in config file.") + } + + cred, err := wincred.GetGenericCredential(identifier) + if err != nil { + return nil, err + } + + // the returned password contains null bytes between + // the characters for some reason -> remove these + pwd := string(cred.CredentialBlob) + pwd = strings.Replace(pwd, string(0), "", -1) + + return &Credentials{ UID: cred.UserName, PWD: pwd }, nil +} \ No newline at end of file diff --git a/deploy/ftp.go b/deploy/ftp.go index 0f28908..05e24a5 100644 --- a/deploy/ftp.go +++ b/deploy/ftp.go @@ -21,8 +21,10 @@ import ( "path" "strings" "os" - + "github.com/dutchcoders/goftp" + "github.com/mindok/hugodeploy/cred" + jww "github.com/spf13/jwalterweatherman" "github.com/spf13/viper" ) @@ -47,8 +49,32 @@ func (f *FTPDeployer) Initialise() error { // Gather together settings f.HostID = viper.GetString("ftp.host") f.Port = viper.GetString("ftp.port") - f.UID = viper.GetString("ftp.user") - f.PWD = viper.GetString("ftp.pwd") + + // get credentials + credProviderName := viper.GetString("ftp.credentialProvider") + if (credProviderName == "") { + // when no credental provider is given + // fall back to old style credentials + credProviderName = "classic" + } + + jww.DEBUG.Println("CredentialProvider is ", credProviderName) + + credProvider := cred.GetCredentialProvider(credProviderName) + if (credProvider == nil) { + jww.ERROR.Println("Unknown or unsupported credential provider: ", credProviderName) + return errors.New ( "Unknown or unsupported credential provider: " + credProviderName ); + } + + credentials, err := credProvider.GetCredentials("ftp") + if (err != nil) { + jww.ERROR.Println("Unable to retrieve credentials: ", err) + return err; + } + + f.UID = credentials.UID + f.PWD = credentials.PWD + f.RootDir = viper.GetString("ftp.rootdir") f.DisableTLS = false if viper.IsSet("ftp.disabletls") { @@ -62,13 +88,7 @@ func (f *FTPDeployer) Initialise() error { } if f.Port == "" { serr = serr + "Port not found. Define ftp.port in config file. " - } - if f.UID == "" { - serr = serr + "UID not found. Define ftp.user in config file. " - } - if f.PWD == "" { - serr = serr + "PWD not found. Define ftp.pwd in config file. " - } + } if f.RootDir == "" { f.RootDir = "/" jww.WARN.Println("FTP: Website root directory not found (ftp: rootdir in config). Defaulting to '/'") @@ -77,9 +97,7 @@ func (f *FTPDeployer) Initialise() error { if serr != "" { jww.ERROR.Println("Error initialising FTP Deployer: ", serr) panic(errors.New("Error initialising FTP Deployer. " + serr)) - } - - var err error + } jww.FEEDBACK.Println("Creating FTP connection... ") //Create initial connection diff --git a/deploy/sftp.go b/deploy/sftp.go index c86ad57..9aff686 100644 --- a/deploy/sftp.go +++ b/deploy/sftp.go @@ -20,6 +20,7 @@ import ( jww "github.com/spf13/jwalterweatherman" "github.com/spf13/viper" "golang.org/x/crypto/ssh" + "github.com/mindok/hugodeploy/cred" ) /* NOTE: INCOMPLETE, UNTESTED CODE */ @@ -43,8 +44,32 @@ func (s *SFTPDeployer) Initialise() error { // Gather together settings s.HostID = viper.GetString("sftp.host") s.Port = viper.GetString("sftp.port") - s.UID = viper.GetString("sftp.user") - s.PWD = viper.GetString("sftp.pwd") + + // get credentials + credProviderName := viper.GetString("ftp.credentialProvider") + if (credProviderName == "") { + // when no credental provider is given + // fall back to old style credentials + credProviderName = "classic" + } + + jww.DEBUG.Println("CredentialProvider is ", credProviderName) + + credProvider := cred.GetCredentialProvider(credProviderName) + if (credProvider == nil) { + jww.ERROR.Println("Unknown or unsupported credential provider: ", credProviderName) + return errors.New ( "Unknown or unsupported credential provider: " + credProviderName ); + } + + credentials, err := credProvider.GetCredentials("sftp") + if (err != nil) { + jww.ERROR.Println("Unable to retrieve credentials: ", err) + return err; + } + + s.UID = credentials.UID + s.PWD = credentials.PWD + jww.INFO.Println("Got SFTP settings: ", s.HostID, s.Port, s.UID) if s.HostID == "" { @@ -52,19 +77,12 @@ func (s *SFTPDeployer) Initialise() error { } if s.Port == "" { serr = serr + "Port not found. Define sftp.port in config file. " - } - if s.UID == "" { - serr = serr + "UID not found. Define sftp.user in config file. " - } - if s.PWD == "" { - serr = serr + "PWD not found. Define sftp.pwd in config file. " - } - + } if serr != "" { return errors.New("Error initialising SFTP Deployer. " + serr) } - err := errors.New("") //Must be away to avoid this, but double function returns below barf + err = errors.New("") //Must be away to avoid this, but double function returns below barf //Attempt to connect. First create the SSH client: config := &ssh.ClientConfig{ From 82f2845be704c2f2607ce1e836a2028b38740813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Bergelt?= Date: Fri, 5 May 2017 13:10:18 +0200 Subject: [PATCH 2/2] Fixed config file template (replaced tab with 2 spaces) Added "credentialProvider: classic" to default config file --- cmd/init.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/init.go b/cmd/init.go index cd484fb..2361e9e 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -184,11 +184,12 @@ func createConfigFile() { # Connection settings for deployment target (FTP only) ftp: host: - port: + port: + rootdir: + disabletls: false + credentialProvider: classic user: pwd: - rootdir: - disabletls: false # Connection settings for deployment target (SFTP only) sftp: