From 6cfffb38f99b1bfec97fd10cd679cdce116ec2e2 Mon Sep 17 00:00:00 2001 From: Nenad Ilic Date: Mon, 25 Sep 2017 18:13:56 +0200 Subject: [PATCH] feat(cli): Allow adding admin password using docker secrets aka file (#1199) (#1214) --- api/cli/cli.go | 20 +++++++++++++------- api/cmd/portainer/main.go | 21 ++++++++++++++++++--- api/file/file.go | 12 ++++++++++++ api/portainer.go | 1 + 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/api/cli/cli.go b/api/cli/cli.go index dfca35923..c4cde81ec 100644 --- a/api/cli/cli.go +++ b/api/cli/cli.go @@ -16,12 +16,13 @@ import ( type Service struct{} const ( - errInvalidEndpointProtocol = portainer.Error("Invalid endpoint protocol: Portainer only supports unix:// or tcp://") - errSocketNotFound = portainer.Error("Unable to locate Unix socket") - errEndpointsFileNotFound = portainer.Error("Unable to locate external endpoints file") - errInvalidSyncInterval = portainer.Error("Invalid synchronization interval") - errEndpointExcludeExternal = portainer.Error("Cannot use the -H flag mutually with --external-endpoints") - errNoAuthExcludeAdminPassword = portainer.Error("Cannot use --no-auth with --admin-password") + errInvalidEndpointProtocol = portainer.Error("Invalid endpoint protocol: Portainer only supports unix:// or tcp://") + errSocketNotFound = portainer.Error("Unable to locate Unix socket") + errEndpointsFileNotFound = portainer.Error("Unable to locate external endpoints file") + errInvalidSyncInterval = portainer.Error("Invalid synchronization interval") + errEndpointExcludeExternal = portainer.Error("Cannot use the -H flag mutually with --external-endpoints") + errNoAuthExcludeAdminPassword = portainer.Error("Cannot use --no-auth with --admin-password or --admin-password-file") + errAdminPassExcludeAdminPassFile = portainer.Error("Cannot use --admin-password with --admin-password-file") ) // ParseFlags parse the CLI flags and return a portainer.Flags struct @@ -45,6 +46,7 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) { SSLCert: kingpin.Flag("sslcert", "Path to the SSL certificate used to secure the Portainer instance").Default(defaultSSLCertPath).String(), SSLKey: kingpin.Flag("sslkey", "Path to the SSL key used to secure the Portainer instance").Default(defaultSSLKeyPath).String(), AdminPassword: kingpin.Flag("admin-password", "Hashed admin password").String(), + AdminPasswordFile: kingpin.Flag("admin-password-file", "Path to the file containing the password for the admin user").String(), // Deprecated flags Labels: pairs(kingpin.Flag("hide-label", "Hide containers with a specific label in the UI").Short('l')), Logo: kingpin.Flag("logo", "URL for the logo displayed in the UI").String(), @@ -77,10 +79,14 @@ func (*Service) ValidateFlags(flags *portainer.CLIFlags) error { return err } - if *flags.NoAuth && (*flags.AdminPassword != "") { + if *flags.NoAuth && (*flags.AdminPassword != "" || *flags.AdminPasswordFile != "") { return errNoAuthExcludeAdminPassword } + if *flags.AdminPassword != "" && *flags.AdminPasswordFile != "" { + return errAdminPassExcludeAdminPassFile + } + displayDeprecationWarnings(*flags.Templates, *flags.Logo, *flags.Labels) return nil diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go index 6f202ec32..0b536c895 100644 --- a/api/cmd/portainer/main.go +++ b/api/cmd/portainer/main.go @@ -212,12 +212,27 @@ func main() { } } - if *flags.AdminPassword != "" { - log.Printf("Creating admin user with password hash %s", *flags.AdminPassword) + adminPasswordHash := "" + if *flags.AdminPasswordFile != "" { + content, err := file.GetStringFromFile(*flags.AdminPasswordFile) + if err != nil { + log.Fatal(err) + } + adminPasswordHash, err = cryptoService.Hash(content) + if err != nil { + log.Fatal(err) + } + } else if *flags.AdminPassword != "" { + adminPasswordHash = *flags.AdminPassword + } + + if adminPasswordHash != "" { + + log.Printf("Creating admin user with password hash %s", adminPasswordHash) user := &portainer.User{ Username: "admin", Role: portainer.AdministratorRole, - Password: *flags.AdminPassword, + Password: adminPasswordHash, } err := store.UserService.CreateUser(user) if err != nil { diff --git a/api/file/file.go b/api/file/file.go index c143fce0b..80e8d7b12 100644 --- a/api/file/file.go +++ b/api/file/file.go @@ -1,6 +1,8 @@ package file import ( + "io/ioutil" + "github.com/portainer/portainer" "io" @@ -162,3 +164,13 @@ func (service *Service) createFileInStore(filePath string, r io.Reader) error { } return nil } + +// GetStringFromFile returns a string content from file. +func GetStringFromFile(filePath string) (string, error) { + content, err := ioutil.ReadFile(filePath) + if err != nil { + return "", err + } + + return string(content), nil +} diff --git a/api/portainer.go b/api/portainer.go index b2a2156a3..539f9745b 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -27,6 +27,7 @@ type ( SSLCert *string SSLKey *string AdminPassword *string + AdminPasswordFile *string // Deprecated fields Logo *string Templates *string