Skip to content

Commit

Permalink
Release 2.1.2 (#143)
Browse files Browse the repository at this point in the history
* Add explicit privilege prompt to improve sudo UX (#138)

* Explicitly prompt for privilege escallation

* Remove password prompt part of privilege message

* Expand sudo detection.

* Tidy up timing issues.

* Consolidate messaging and avoid newline in verbose.

* Cleanup ToString, sudo contains, cover more exec methods.

* Lint does not catch all of fmt.

* Remove unnecessary password prompt from networking cleanup.

* Remove color reset and cat /dev/null to clear route text.

* trying a different approach to requesting for admin privs (#144)
  • Loading branch information
febbraro authored Feb 8, 2018
1 parent 85ba91b commit 09ff3c7
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 14 deletions.
5 changes: 2 additions & 3 deletions commands/stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package commands
import (
"fmt"

"github.com/fatih/color"
"github.com/phase2/rig/util"
"github.com/urfave/cli"
)
Expand Down Expand Up @@ -58,15 +57,15 @@ func (cmd *Stop) StopOutrigger() error {
}
cmd.out.Info("Stopped machine '%s'", cmd.machine.Name)

cmd.out.Spin("Cleaning up local networking (may require your admin password)")
cmd.out.Spin("Cleaning up local networking...")
if util.IsWindows() {
util.Command("runas", "/noprofile", "/user:Administrator", "route", "DELETE", "172.17.0.0").Run()
util.Command("runas", "/noprofile", "/user:Administrator", "route", "DELETE", "172.17.42.1").Run()
} else {
util.EscalatePrivilege()
util.Command("sudo", "route", "-n", "delete", "-net", "172.17.0.0").Run()
util.Command("sudo", "route", "-n", "delete", "-net", "172.17.42.1").Run()
}
color.Unset()
cmd.out.Info("Networking cleanup completed")

return cmd.Success(fmt.Sprintf("Machine '%s' stopped", cmd.machine.Name))
Expand Down
39 changes: 31 additions & 8 deletions util/logger.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package util

import (
"fmt"
"io/ioutil"
"log"
"os"

"fmt"
"github.com/fatih/color"
spun "github.com/slok/gospinner"
)
Expand All @@ -24,10 +24,11 @@ type logChannels struct {

// RigLogger is the global logger object
type RigLogger struct {
Channel logChannels
Progress *RigSpinner
IsVerbose bool
Spinning bool
Channel logChannels
Progress *RigSpinner
IsVerbose bool
Spinning bool
Privileged bool
}

// RigSpinner object wrapper to facilitate our spinner service
Expand All @@ -51,9 +52,10 @@ func LoggerInit(verbose bool) {
Error: log.New(os.Stderr, color.RedString("[ERROR] "), 0),
Verbose: log.New(verboseWriter, "[VERBOSE] ", 0),
},
IsVerbose: verbose,
Progress: &RigSpinner{s},
Spinning: false,
IsVerbose: verbose,
Progress: &RigSpinner{s},
Spinning: false,
Privileged: false,
}
}

Expand Down Expand Up @@ -125,3 +127,24 @@ func (log *RigLogger) Verbose(format string, a ...interface{}) {
func (log *RigLogger) Note(format string, a ...interface{}) {
log.Channel.Info.Println(fmt.Sprintf(format, a...))
}

// PrivilegeEscallationPrompt interrupts a running spinner to ensure clear
// prompting to the user for sudo password entry. It is up to the caller to know
// that privilege is needed. This prompt is only displayed on the first privilege
// escallation of a given rig process.
func (log *RigLogger) PrivilegeEscallationPrompt() {
defer func() { log.Privileged = true }()

if log.Privileged {
return
}

// This newline ensures the last status before escallation is preserved
// on-screen. It creates extraneous space in verbose mode.
if !log.IsVerbose {
fmt.Println()
}
message := "Administrative privileges needed..."
log.Spin(message)
log.Warning(message)
}
39 changes: 36 additions & 3 deletions util/shell_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ func Convert(cmd *exec.Cmd) Executor {
return Executor{cmd}
}

// EscalatePrivilege attempts to gain administrative privilege
// @todo identify administrative escallation on Windows.
// E.g., "runas", "/noprofile", "/user:Administrator
func EscalatePrivilege() error {
return Command("sudo", "-v").Run()
}

// PassthruCommand is similar to ForceStreamCommand in that it will issue all output
// regardless of verbose mode. Further, this version of the command captures the
// exit status of any executed command. This function is intended to simulate
Expand Down Expand Up @@ -92,36 +99,53 @@ func (x Executor) Execute(forceOutput bool) error {
// CombinedOutput runs a command via exec.CombinedOutput() without modification or output of the underlying command.
func (x Executor) CombinedOutput() ([]byte, error) {
x.Log("Executing")
if out := Logger(); out != nil && x.IsPrivileged() {
out.PrivilegeEscallationPrompt()
defer out.Spin("Resuming operation...")
}
return x.cmd.CombinedOutput()
}

// Run runs a command via exec.Run() without modification or output of the underlying command.
func (x Executor) Run() error {
x.Log("Executing")
if out := Logger(); out != nil && x.IsPrivileged() {
out.PrivilegeEscallationPrompt()
defer out.Spin("Resuming operation...")
}
return x.cmd.Run()
}

// Output runs a command via exec.Output() without modification or output of the underlying command.
func (x Executor) Output() ([]byte, error) {
x.Log("Executing")
if out := Logger(); out != nil && x.IsPrivileged() {
out.PrivilegeEscallationPrompt()
defer out.Spin("Resuming operation...")
}
return x.cmd.Output()
}

// Start runs a command via exec.Start() without modification or output of the underlying command.
func (x Executor) Start() error {
x.Log("Executing")
if out := Logger(); out != nil && x.IsPrivileged() {
out.PrivilegeEscallationPrompt()
defer out.Spin("Resuming operation...")
}
return x.cmd.Start()
}

// Log verbosely logs the command.
func (x Executor) Log(tag string) {
color.Set(color.FgMagenta)
Logger().Verbose("%s: %s", tag, x.ToString())
Logger().Verbose("%s: %s", tag, x)
color.Unset()
}

// ToString converts a Command to a human-readable string with key context details.
func (x Executor) ToString() string {
// String converts a Command to a human-readable string with key context details.
// It is automatically applied in contexts such as fmt functions.
func (x Executor) String() string {
context := ""
if x.cmd.Dir != "" {
context = fmt.Sprintf("(WD: %s", x.cmd.Dir)
Expand All @@ -137,3 +161,12 @@ func (x Executor) ToString() string {

return fmt.Sprintf("%s %s %s", x.cmd.Path, strings.Join(x.cmd.Args[1:], " "), context)
}

// IsPrivileged evaluates the command to determine if administrative privilege
// is required.
// @todo identify administrative escallation on Windows.
// E.g., "runas", "/noprofile", "/user:Administrator
func (x Executor) IsPrivileged() bool {
_, privileged := IndexOfSubstring(x.cmd.Args, "sudo")
return privileged
}
30 changes: 30 additions & 0 deletions util/slices.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package util

import (
"strings"
)

// IndexOfString is a general utility function that can find the index of a value
// present in a string slice. The second value is true if the item is found.
func IndexOfString(slice []string, search string) (int, bool) {
for index, elem := range slice {
if elem == search {
return index, true
}
}

return 0, false
}

// IndexOfSubstring is a variation on IndexOfString which checks to see if a
// given slice value matches our search string, or if that search string is
// a substring of the element. The second value is true if the item is found.
func IndexOfSubstring(slice []string, search string) (int, bool) {
for index, elem := range slice {
if strings.Contains(elem, search) {
return index, true
}
}

return 0, false
}

0 comments on commit 09ff3c7

Please sign in to comment.