Skip to content

Commit

Permalink
feat: format output
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexanderGrooff committed Jul 27, 2023
1 parent c9d8993 commit be275a4
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 72 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ This is most commonly used for retrieving ad-hoc information without too much fu
## Usage

```bash
boxes run -t theta,testalex.h -c hostname
$ boxes run -t theta,testalex.h -c hostname
INFO[0000] theta: theta
INFO[0000] testalex.h: j6yt29-testalex-magweb-do.nodes.hypernode.io

# Format output with Go template syntax
$ boxes run -t theta,testalex.h -c hostname --format "{{.Target}} -> {{.Stdout}}"
INFO[0000] theta -> theta
INFO[0000] testalex.h -> j6yt29-testalex-magweb-do.nodes.hypernode.io
```
28 changes: 28 additions & 0 deletions cmd/logging.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cmd

import (
"bytes"
"text/template"
)

// Type for results
type Result struct {
Target string
Hostname string
Stdout string
Stderr string
Error error
}

func (result *Result) toString(format string) string {
tmpl, err := template.New("result").Parse(format)
if err != nil {
panic(err)
}
var buf bytes.Buffer
err = tmpl.Execute(&buf, result)
if err != nil {
panic(err)
}
return buf.String()
}
5 changes: 4 additions & 1 deletion cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ var runCmd = &cobra.Command{
targets, _ := cmd.Flags().GetStringSlice("target")
commands, _ := cmd.Flags().GetStringSlice("command")
outputFile, _ := cmd.Flags().GetString("output")
format, _ := cmd.Flags().GetString("format")

// Execute the commands
executeCommands(targets, commands, outputFile)
executeCommands(targets, commands, outputFile, format)
},
}

Expand All @@ -28,5 +29,7 @@ func init() {

runCmd.Flags().StringSliceP("target", "t", []string{}, "Target host")
runCmd.Flags().StringSliceP("command", "c", []string{}, "Command to run")
runCmd.Flags().StringP("format", "f", "{{.Target}}: {{.Stdout}}",
"Output format in Go template syntax. Available fields: Target, Hostname, Stdout, Stderr, Error")
runCmd.Flags().StringP("output", "o", "", "Output file")
}
83 changes: 13 additions & 70 deletions cmd/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@ package cmd
import (
"fmt"
"os"
"strings"
"sync"

"github.com/appleboy/easyssh-proxy"
"github.com/kevinburke/ssh_config"
log "github.com/sirupsen/logrus"
)

func executeCommands(targets []string, commands []string, outputFile string) {
func executeCommands(targets []string, commands []string, outputFile string, format string) {
// Open output file in append mode
var file *os.File
var err error
Expand All @@ -32,87 +30,32 @@ func executeCommands(targets []string, commands []string, outputFile string) {
ssh := getConfigForHost(target)
// TODO: Use same SSH connection for all commands
for _, command := range commands {
stdout, stderr, _, err := ssh.Run(command)
result := RunAndParse(target, command, ssh)
if err != nil {
log.Warn("Error running command on ", target, ": ", err)
continue
}
// Identify host with output
log.Info(target, ": ", stdout)
if stderr != "" {
log.Info(target, ": ", stderr)
}
output := result.toString(format)
log.Info(output)
// Write output to file if given
if file != nil {
if _, err := file.WriteString(fmt.Sprintf("%s: %s\n%s\n", target, command, stdout)); err != nil {
if _, err := file.WriteString(fmt.Sprintf("%s\n", output)); err != nil {
log.Warn("Error writing to file: ", err)
}
if stderr != "" {
if _, err := file.WriteString(fmt.Sprintf("%s: %s\n%s\n", target, command, stderr)); err != nil {
log.Warn("Error writing to file: ", err)
}
}
}
}
}(target)
}
wg.Wait()
}

func getConfigForHost(target string) *easyssh.MakeConfig {
proxy := ssh_config.Get(target, "ProxyJump")
user := ssh_config.Get(target, "User")
if user == "" {
user = os.Getenv("USER")
}
port := ssh_config.Get(target, "Port")
if port == "" {
port = "22"
}
hostname := fillSSHConfigHostname(target, ssh_config.Get(target, "HostName"))
if hostname == "" {
hostname = target
}

log.Debug("Creating SSH config for target ", target, " with hostname ", hostname, " and proxy ", proxy)

if proxy != "" {
log.Debug("Using proxy ", proxy, " for target ", target)
return &easyssh.MakeConfig{
User: user,
Server: hostname,
Port: port,
Proxy: *makeConfigToDefaultConfig(getConfigForHost(proxy)),
}
} else {
log.Debug("No proxy found for target ", target)
return &easyssh.MakeConfig{
User: user,
Server: hostname,
Port: port,
}
}
}

func fillSSHConfigHostname(target string, configHostName string) string {
// Check if "%h" is in the config name
if strings.Contains(configHostName, "%h") {
// Replace %h with the target
return strings.ReplaceAll(configHostName, "%h", target)
}

// Nothing to replace
return configHostName
}

// Note: this is necessary for generating proxy configs
func makeConfigToDefaultConfig(makeConfig *easyssh.MakeConfig) *easyssh.DefaultConfig {
return &easyssh.DefaultConfig{
User: makeConfig.User,
Server: makeConfig.Server,
Port: makeConfig.Port,
Password: makeConfig.Password,
KeyPath: makeConfig.KeyPath,
Timeout: makeConfig.Timeout,
func RunAndParse(target string, command string, ssh *easyssh.MakeConfig) Result {
stdout, stderr, _, err := ssh.Run(command)
return Result{
Target: target,
Hostname: ssh.Server,
Stdout: stdout,
Stderr: stderr,
Error: err,
}
}
68 changes: 68 additions & 0 deletions cmd/ssh_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package cmd

import (
"os"
"strings"

"github.com/appleboy/easyssh-proxy"
"github.com/kevinburke/ssh_config"
log "github.com/sirupsen/logrus"
)

func getConfigForHost(target string) *easyssh.MakeConfig {
proxy := ssh_config.Get(target, "ProxyJump")
user := ssh_config.Get(target, "User")
if user == "" {
user = os.Getenv("USER")
}
port := ssh_config.Get(target, "Port")
if port == "" {
port = "22"
}
hostname := fillSSHConfigHostname(target, ssh_config.Get(target, "HostName"))
if hostname == "" {
hostname = target
}

log.Debug("Creating SSH config for target ", target, " with hostname ", hostname, " and proxy ", proxy)

if proxy != "" {
log.Debug("Using proxy ", proxy, " for target ", target)
return &easyssh.MakeConfig{
User: user,
Server: hostname,
Port: port,
Proxy: *makeConfigToDefaultConfig(getConfigForHost(proxy)),
}
} else {
log.Debug("No proxy found for target ", target)
return &easyssh.MakeConfig{
User: user,
Server: hostname,
Port: port,
}
}
}

func fillSSHConfigHostname(target string, configHostName string) string {
// Check if "%h" is in the config name
if strings.Contains(configHostName, "%h") {
// Replace %h with the target
return strings.ReplaceAll(configHostName, "%h", target)
}

// Nothing to replace
return configHostName
}

// Note: this is necessary for generating proxy configs
func makeConfigToDefaultConfig(makeConfig *easyssh.MakeConfig) *easyssh.DefaultConfig {
return &easyssh.DefaultConfig{
User: makeConfig.User,
Server: makeConfig.Server,
Port: makeConfig.Port,
Password: makeConfig.Password,
KeyPath: makeConfig.KeyPath,
Timeout: makeConfig.Timeout,
}
}

0 comments on commit be275a4

Please sign in to comment.