* password manager plugin
This commit is contained in:
Pavel Vorobyov
2019-09-24 13:42:09 +03:00
committed by GitHub
parent 9f5347e616
commit d1b9cdc054
8 changed files with 196 additions and 94 deletions

View File

@@ -13,6 +13,7 @@ import (
"github.com/chzyer/readline"
"github.com/viert/xc/config"
"github.com/viert/xc/log"
"github.com/viert/xc/passmgr"
"github.com/viert/xc/remote"
"github.com/viert/xc/store"
"github.com/viert/xc/term"
@@ -44,6 +45,7 @@ type Cli struct {
prependHostnames bool
progressBar bool
debug bool
usePasswordMgr bool
interpreter string
sudoInterpreter string
@@ -114,6 +116,16 @@ func New(cfg *config.XCConfig, backend store.Backend) (*Cli, error) {
cli.outputFileName = ""
cli.outputFile = nil
if cfg.PasswordManagerPath != "" {
term.Warnf("Loading password manager from %s\n", cfg.PasswordManagerPath)
err = passmgr.Load(cfg.PasswordManagerPath)
if err != nil {
term.Errorf("Error initializing password manager: %s\n", err)
} else {
cli.usePasswordMgr = true
}
}
remote.Initialize(cli.sshThreads, cli.user)
remote.SetPrependHostnames(cli.prependHostnames)
remote.SetRemoteTmpdir(cfg.RemoteTmpdir)
@@ -276,9 +288,10 @@ func (c *Cli) confirm(msg string) bool {
}
func (c *Cli) acquirePasswd() {
if c.raiseType == remote.RTNone {
if c.raiseType == remote.RTNone || c.usePasswordMgr {
return
}
if c.raisePasswd == "" {
c.doPasswd("passwd", "")
}
@@ -407,3 +420,23 @@ func (c *Cli) dorunscript(mode execMode, argsLine string) {
r.ErrorHosts = append(r.ErrorHosts, copyError...)
r.Print()
}
func doOnOff(propName string, propRef *bool, args []string) {
if len(args) < 1 {
value := "off"
if *propRef {
value = "on"
}
term.Warnf("%s is %s\n", propName, value)
return
}
switch args[0] {
case "on":
*propRef = true
case "off":
*propRef = false
default:
term.Errorf("Invalid %s vaue. Please use either \"on\" or \"off\"\n", propName)
return
}
}

View File

@@ -40,7 +40,7 @@ func newCompleter(store *store.Store, commands []string) *completer {
x.handlers["c_runscript"] = x.completeDistribute
x.handlers["p_runscript"] = x.completeDistribute
helpTopics := append(commands, "expressions", "config", "rcfiles")
helpTopics := append(commands, "expressions", "config", "rcfiles", "passmgr")
x.handlers["help"] = staticCompleter(helpTopics)
return x
}

View File

@@ -8,6 +8,8 @@ import (
"strconv"
"syscall"
"github.com/viert/xc/passmgr"
"github.com/viert/xc/remote"
"github.com/viert/xc/term"
)
@@ -46,6 +48,7 @@ func (c *Cli) setupCmdHandlers() {
c.handlers["s_runscript"] = c.doSRunScript
c.handlers["c_runscript"] = c.doCRunScript
c.handlers["p_runscript"] = c.doPRunScript
c.handlers["use_password_manager"] = c.doUsePasswordManager
commands := make([]string, len(c.handlers))
i := 0
@@ -277,68 +280,29 @@ func (c *Cli) doDelay(name string, argsLine string, args ...string) {
}
func (c *Cli) doDebug(name string, argsLine string, args ...string) {
if len(args) < 1 {
value := "off"
if c.debug {
value = "on"
}
term.Warnf("Debug is %s\n", value)
return
}
switch args[0] {
case "on":
c.debug = true
case "off":
c.debug = false
default:
term.Errorf("Invalid debug value. Please use either \"on\" or \"off\"\n")
return
}
doOnOff("debug", &c.debug, args)
remote.SetDebug(c.debug)
}
func (c *Cli) doProgressBar(name string, argsLine string, args ...string) {
if len(args) < 1 {
value := "off"
if c.progressBar {
value = "on"
}
term.Warnf("Progressbar is %s\n", value)
return
}
switch args[0] {
case "on":
c.progressBar = true
case "off":
c.progressBar = false
default:
term.Errorf("Invalid progressbar value. Please use either \"on\" or \"off\"\n")
return
}
doOnOff("progressbar", &c.progressBar, args)
remote.SetProgressBar(c.progressBar)
}
func (c *Cli) doPrependHostnames(name string, argsLine string, args ...string) {
if len(args) < 1 {
value := "off"
if c.prependHostnames {
value = "on"
}
term.Warnf("prepend_hostnames is %s\n", value)
return
}
switch args[0] {
case "on":
c.prependHostnames = true
case "off":
c.prependHostnames = false
default:
term.Errorf("Invalid prepend_hostnames value. Please use either \"on\" or \"off\"\n")
return
}
doOnOff("prepend_hostnames", &c.prependHostnames, args)
remote.SetPrependHostnames(c.prependHostnames)
}
func (c *Cli) doUsePasswordManager(name string, argsLine string, args ...string) {
doOnOff("use_password_manager", &c.usePasswordMgr, args)
if c.usePasswordMgr && !passmgr.Ready() {
term.Errorf("Password manager is not ready\n")
c.usePasswordMgr = false
}
remote.SetUsePasswordManager(c.usePasswordMgr)
}
func (c *Cli) doReload(name string, argsLine string, args ...string) {
err := c.store.BackendReload()
if err != nil {
@@ -359,7 +323,7 @@ func (c *Cli) doInterpreter(name string, argsLine string, args ...string) {
func (c *Cli) doConnectTimeout(name string, argsLine string, args ...string) {
if len(args) < 1 {
term.Warnf("connect_timeout = %s\n", c.connectTimeout)
term.Warnf("connect_timeout = %d\n", c.connectTimeout)
return
}
ct, err := strconv.ParseInt(args[0], 10, 64)

View File

@@ -161,6 +161,16 @@ It may be useful for configuring aliases (as they are dropped when xc exits) and
Rcfile is just a number of xc commands in a text file.`,
},
"passmgr": &helpItem{
isTopic: true,
help: `Password manager is a golang plugin which must have two exported functions:
func Init() error, which is called on xc start
func GetPass(host string) (password string), which is kinda self-explanatory
For more info on how to write golang plugins, please refer to golang documentation or this article:
https://medium.com/learning-the-go-programming-language/writing-modular-go-programs-with-plugins-ec46381ee1a9`,
},
"debug": &helpItem{
usage: "<on/off>",
help: `An internal debug. May cause unexpected output. One shouldn't use it unless she knows what she's doing.`,
@@ -318,6 +328,12 @@ xc moves on to the next server.`,
without arguments, prints the current value.`,
},
"use_password_manager": &helpItem{
usage: "[<on/off>]",
help: `Sets the password manager on/off. If no value is given, prints the current value.
If password manager is not ready, setting this value to "on" will print an error.`,
},
"user": &helpItem{
usage: "<username>",
help: `Sets the username for all the execution commands. This is used to get access to hosts via ssh/scp.`,
@@ -370,7 +386,8 @@ List of commands:
reload reloads hosts and groups data from inventoree
runscript runs a local script on a number of remote hosts
serial shortcut for "mode serial"
ssh starts ssh session to a number of hosts sequentally
user sets current user
`)
ssh starts ssh session to a number of hosts sequentally
use_password_manager turns password manager on/off
user sets current user`)
fmt.Println()
}

View File

@@ -39,6 +39,9 @@ interpreter_su = su -
type = conductor
url = http://c.inventoree.ru
work_groups =
[passmgr]
path =
`
// BackendType is a backend type enum
@@ -61,28 +64,29 @@ type BackendConfig struct {
// XCConfig represents a configuration struct for XC
type XCConfig struct {
Readline *readline.Config
BackendCfg *BackendConfig
User string
SSHThreads int
SSHConnectTimeout int
PingCount int
RemoteTmpdir string
Mode string
RaiseType string
Delay int
RCfile string
CacheDir string
CacheTTL time.Duration
Debug bool
ProgressBar bool
PrependHostnames bool
LogFile string
ExitConfirm bool
ExecConfirm bool
SudoInterpreter string
SuInterpreter string
Interpreter string
Readline *readline.Config
BackendCfg *BackendConfig
User string
SSHThreads int
SSHConnectTimeout int
PingCount int
RemoteTmpdir string
Mode string
RaiseType string
Delay int
RCfile string
CacheDir string
CacheTTL time.Duration
Debug bool
ProgressBar bool
PrependHostnames bool
LogFile string
ExitConfirm bool
ExecConfirm bool
SudoInterpreter string
SuInterpreter string
Interpreter string
PasswordManagerPath string
}
const (
@@ -310,5 +314,7 @@ func read(filename string, secondPass bool) (*XCConfig, error) {
return nil, fmt.Errorf("Error configuring backend: backend type is not defined")
}
return cfg, err
cfg.PasswordManagerPath, _ = props.GetString("passmgr.path")
return cfg, nil
}

62
passmgr/passmgr.go Normal file
View File

@@ -0,0 +1,62 @@
package passmgr
import (
"fmt"
"plugin"
)
type initFunc func() error
type acquireFunc func(string) string
const (
initFuncName = "Init"
acquireFuncName = "GetPass"
)
var (
p *plugin.Plugin
initialized bool
pluginInit initFunc
pluginAcquire acquireFunc
)
// Load loads a password manager library
func Load(filename string) error {
var err error
p, err = plugin.Open(filename)
if err != nil {
return err
}
init, err := p.Lookup(initFuncName)
if err != nil {
return err
}
pluginInit, initialized = init.(initFunc)
if !initialized {
return fmt.Errorf("invalid plugin `%s() error` function signature", initFuncName)
}
acq, err := p.Lookup(acquireFuncName)
if err != nil {
return err
}
pluginAcquire, initialized = acq.(acquireFunc)
if !initialized {
return fmt.Errorf("invalid plugin `%s(string) string` function signature", acquireFuncName)
}
return nil
}
// GetPass returns password for a given host from password manager
func GetPass(hostname string) string {
if !initialized {
return ""
}
return pluginAcquire(hostname)
}
// Ready returns a bool value indicating if the passmgr is initialized and ready to use
func Ready() bool {
return initialized
}

View File

@@ -10,15 +10,16 @@ import (
)
var (
pool *Pool
currentUser string
currentPassword string
currentRaise RaiseType
currentProgressBar bool
currentPrependHostnames bool
currentRemoteTmpdir string
currentDebug bool
outputFile *os.File
pool *Pool
currentUser string
currentPassword string
currentRaise RaiseType
currentUsePasswordManager bool
currentProgressBar bool
currentPrependHostnames bool
currentRemoteTmpdir string
currentDebug bool
outputFile *os.File
noneInterpreter string
suInterpreter string
@@ -83,6 +84,11 @@ func SetPrependHostnames(prependHostnames bool) {
currentPrependHostnames = prependHostnames
}
// SetUsePasswordManager sets using passmgr on/off
func SetUsePasswordManager(usePasswordMgr bool) {
currentUsePasswordManager = usePasswordMgr
}
// SetConnectTimeout sets the ssh connect timeout in sshOptions
func SetConnectTimeout(timeout int) {
sshOptions["ConnectTimeout"] = fmt.Sprintf("%d", timeout)

View File

@@ -10,14 +10,17 @@ import (
"github.com/kr/pty"
"github.com/npat-efault/poller"
"github.com/viert/xc/log"
"github.com/viert/xc/passmgr"
)
func (w *Worker) runcmd(task *Task) int {
var err error
var n int
var passwordSent bool
var (
err error
n int
password string
passwordSent bool
)
passwordSent = currentRaise == RTNone
cmd := createSSHCmd(task.Hostname, task.Cmd)
cmd.Env = append(os.Environ(), environment...)
@@ -38,6 +41,17 @@ func (w *Worker) runcmd(task *Task) int {
shouldSkipEcho := false
msgCount := 0
if currentRaise != RTNone {
passwordSent = false
if currentUsePasswordManager {
password = passmgr.GetPass(task.Hostname)
} else {
password = currentPassword
}
} else {
passwordSent = true
}
execLoop:
for {
if w.forceStopped() {
@@ -68,7 +82,7 @@ execLoop:
// Trying to find Password prompt in first 5 chunks of data from server
if msgCount < 5 {
if !passwordSent && exPasswdPrompt.Match(chunk) {
ptmx.Write([]byte(currentPassword + "\n"))
ptmx.Write([]byte(password + "\n"))
passwordSent = true
shouldSkipEcho = true
continue