mirror of
https://github.com/kemko/xc.git
synced 2026-01-01 15:55:43 +03:00
35
cli/cli.go
35
cli/cli.go
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
23
cli/help.go
23
cli/help.go
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
62
passmgr/passmgr.go
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user