Skip to content

Commit

Permalink
Using human readable name rather than hostname for UI
Browse files Browse the repository at this point in the history
  • Loading branch information
dimakogan committed Oct 13, 2017
1 parent afc781d commit 5fc929e
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 113 deletions.
4 changes: 1 addition & 3 deletions agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,7 @@ func (agent *Agent) HandleConnection(conn net.Conn) error {
if err := ssh.Unmarshal(payload, notice); err != nil {
return fmt.Errorf("Failed to unmarshal AgentForwardingNoticeMsg: %s", err)
}
scope.ClientHostname = notice.Hostname
scope.ClientPort = notice.Port
scope.ClientUsername = notice.Username
scope.Client = notice.Client
case MsgExecutionRequest:
execReq := new(ExecutionRequestMessage)
if err = ssh.Unmarshal(payload, execReq); err != nil {
Expand Down
24 changes: 13 additions & 11 deletions cmd/sga-guard-bin/sga-guard-bin.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func main() {
var sshOptions []string
parser.UnknownOptionHandler = func(option string, arg flags.SplitArgument, args []string) ([]string, error) {
val, isSet := arg.Value()
sshFlagsWithValues := "bcDEeFIiLmOoQRWw"
sshFlagsWithValues := "bcDEeFIiLmOopQRWw"

if isSet {
sshOptions = append(sshOptions, fmt.Sprintf("-%s %s", option, val))
Expand All @@ -63,6 +63,12 @@ func main() {
}
}

readableName := opts.SSHCommand.UserHost
if parser.FindOptionByShortName('l').IsSet() {
readableName = opts.Username + "@" + readableName
sshOptions = append(sshOptions, "-l", opts.Username)
}

log.SetFlags(log.LstdFlags | log.Lshortfile)
if opts.Debug {
if opts.LogFile == "" {
Expand All @@ -79,9 +85,6 @@ func main() {
log.SetOutput(ioutil.Discard)
}

var host string
host, opts.Port, opts.Username = guardianagent.ResolveRemote(parser, &opts.CommonOptions, opts.SSHCommand.UserHost)

opts.PolicyConfig = os.ExpandEnv(opts.PolicyConfig)
var ag *guardianagent.Agent
if opts.PromptType == "DISPLAY" {
Expand All @@ -100,20 +103,19 @@ func main() {
os.Exit(255)
}
sshFwd := guardianagent.SSHFwd{
SSHProgram: opts.SSHProgram,
SSHArgs: sshOptions,
Host: host,
Port: opts.Port,
Username: opts.Username,
RemoteStubName: opts.RemoteStubName,
SSHProgram: opts.SSHProgram,
SSHArgs: sshOptions,
Host: opts.SSHCommand.UserHost,
RemoteReadableName: readableName,
RemoteStubName: opts.RemoteStubName,
}

if err = sshFwd.SetupForwarding(); err != nil {
fmt.Fprintf(os.Stderr, "%s", err)
os.Exit(255)
}

fmt.Printf("Listening for incoming Guardian Agent requests from %s@%s:%d...\n", opts.Username, host, opts.Port)
fmt.Printf("Listening for incoming Guardian Agent requests from %s...\n", readableName)

var c net.Conn
for {
Expand Down
54 changes: 53 additions & 1 deletion cmd/sga-run/sga-run.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package main

import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"os/user"
"strconv"
"strings"

Expand All @@ -24,6 +28,8 @@ type SSHCommand struct {
type options struct {
guardianagent.CommonOptions

Port int `short:"p" long:"port" description:"Port to connect to on the intermediary host" default:"22"`

StdinNull bool `short:"n" description:"Redirects stdin from /dev/null"`

ForceTTY []bool `short:"t" description:"Forces TTY allocation"`
Expand Down Expand Up @@ -104,7 +110,7 @@ func main() {
}

var host string
host, opts.Port, opts.Username = guardianagent.ResolveRemote(parser, &opts.CommonOptions, opts.SSHCommand.UserHost)
host, opts.Port, opts.Username = resolveRemote(parser, &opts, opts.SSHCommand.UserHost)

var cmd string
if len(opts.SSHCommand.Rest) > 0 {
Expand Down Expand Up @@ -138,3 +144,49 @@ func main() {
os.Exit(255)

}

func resolveRemote(parser *flags.Parser, opts *options, userAndHost string) (host string, port int, username string) {
sshCommandLine := []string{"-G", userAndHost}
if !parser.FindOptionByLongName("port").IsSetDefault() {
sshCommandLine = append(sshCommandLine, fmt.Sprintf("-p %d", opts.Port))
}
if parser.FindOptionByShortName('l').IsSet() {
sshCommandLine = append(sshCommandLine, "-l", opts.Username)
}

sshChild := exec.Command("ssh", sshCommandLine...)
output, err := sshChild.Output()
if err != nil {
log.Printf("Failed to resolve remote using 'ssh %s': %s. Using fallback resolution.", sshCommandLine, err)
return fallbackResolveRemote(opts, userAndHost)
}
lineScanner := bufio.NewScanner(bytes.NewReader(output))
lineScanner.Split(bufio.ScanLines)
for lineScanner.Scan() {
line := lineScanner.Text()
if strings.HasPrefix(strings.ToLower(line), "hostname ") {
host = line[len("hostname "):]
} else if strings.HasPrefix(strings.ToLower(line), "user ") {
username = line[len("user "):]
} else if strings.HasPrefix(strings.ToLower(line), "port ") {
port, _ = strconv.Atoi(line[len("port "):])
}
}
return host, port, username
}

func fallbackResolveRemote(opts *options, userAndHost string) (host string, port int, username string) {
userHost := strings.Split(userAndHost, "@")
host = userHost[len(userHost)-1]
if opts.Username != "" {
username = opts.Username
} else if len(userHost) > 1 {
username = userHost[0]
} else {
curuser, err := user.Current()
if err == nil {
username = curuser.Username
}
}
return host, opts.Port, username
}
60 changes: 1 addition & 59 deletions common.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package guardianagent

import (
"bufio"
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
Expand All @@ -11,12 +9,7 @@ import (
"log"
"net"
"os"
"os/exec"
"os/user"
"strconv"
"strings"

flags "github.com/jessevdk/go-flags"
)

const debugCommon = false
Expand All @@ -41,10 +34,7 @@ type AgentCExtensionMsg struct {
const MsgAgentForwardingNotice = 206

type AgentForwardingNoticeMsg struct {
Hostname string
IP string
Port uint32
Username string
Client string
}

const MsgExecutionRequest = 1
Expand Down Expand Up @@ -180,55 +170,7 @@ func UserRuntimeDir() string {
type CommonOptions struct {
Debug bool `long:"debug" description:"Show debug information"`

Port int `short:"p" long:"port" description:"Port to connect to on the intermediary host" default:"22"`

Username string `short:"l" description:"Specifies the user to log in as on the remote machine"`

LogFile string `long:"log" description:"log file"`
}

func ResolveRemote(parser *flags.Parser, opts *CommonOptions, userAndHost string) (host string, port int, username string) {
sshCommandLine := []string{"-G", userAndHost}
if !parser.FindOptionByLongName("port").IsSetDefault() {
sshCommandLine = append(sshCommandLine, fmt.Sprintf("-p %d", opts.Port))
}
if parser.FindOptionByShortName('l').IsSet() {
sshCommandLine = append(sshCommandLine, "-l", opts.Username)
}

sshChild := exec.Command("ssh", sshCommandLine...)
output, err := sshChild.Output()
if err != nil {
log.Printf("Failed to resolve remote using 'ssh %s': %s. Using fallback resolution.", sshCommandLine, err)
return fallbackResolveRemote(opts, userAndHost)
}
lineScanner := bufio.NewScanner(bytes.NewReader(output))
lineScanner.Split(bufio.ScanLines)
for lineScanner.Scan() {
line := lineScanner.Text()
if strings.HasPrefix(strings.ToLower(line), "hostname ") {
host = line[len("hostname "):]
} else if strings.HasPrefix(strings.ToLower(line), "user ") {
username = line[len("user "):]
} else if strings.HasPrefix(strings.ToLower(line), "port ") {
port, _ = strconv.Atoi(line[len("port "):])
}
}
return host, port, username
}

func fallbackResolveRemote(opts *CommonOptions, userAndHost string) (host string, port int, username string) {
userHost := strings.Split(userAndHost, "@")
host = userHost[len(userHost)-1]
if opts.Username != "" {
username = opts.Username
} else if len(userHost) > 1 {
username = userHost[0]
} else {
curuser, err := user.Current()
if err == nil {
username = curuser.Username
}
}
return host, opts.Port, username
}
50 changes: 28 additions & 22 deletions policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,20 @@ type Policy struct {

func (policy *Policy) RequestApproval(scope Scope, cmd string) error {
if policy.Store.IsAllowed(scope, cmd) {
policy.UI.Inform(fmt.Sprintf("Request by %s@%s:%d to run '%s' on %s@%s auto approved by policy",
scope.ClientUsername, scope.ClientHostname,
scope.ClientPort, cmd, scope.ServiceUsername,
policy.UI.Inform(fmt.Sprintf("Request by %s to run '%s' on %s@%s AUTO-APPROVED by policy",
scope.Client, cmd, scope.ServiceUsername,
scope.ServiceHostname))
return nil
}
question := fmt.Sprintf("Allow %s@%s:%d to run '%s' on %s@%s?",
scope.ClientUsername, scope.ClientHostname,
scope.ClientPort, cmd, scope.ServiceUsername,
scope.ServiceHostname)
question := fmt.Sprintf("Allow %s to run '%s' on %s@%s?",
scope.Client, cmd, scope.ServiceUsername, scope.ServiceHostname)

prompt := Prompt{
Question: question,
Choices: []string{
"Disallow", "Allow once", "Allow forever",
fmt.Sprintf("Allow %s@%s:%d to run any command on %s@%s forever",
scope.ClientUsername, scope.ClientHostname,
scope.ClientPort, scope.ServiceUsername,
scope.ServiceHostname),
fmt.Sprintf("Allow %s to run any command on %s@%s forever",
scope.Client, scope.ServiceUsername, scope.ServiceHostname),
},
}
resp, err := policy.UI.Ask(prompt)
Expand All @@ -39,30 +34,35 @@ func (policy *Policy) RequestApproval(scope Scope, cmd string) error {
}

switch resp {
case 1:
err = errors.New("User rejected client request")
case 2:
policy.UI.Inform(fmt.Sprintf("Request by %s to run '%s' on %s@%s APPROVED by user",
scope.Client, cmd, scope.ServiceUsername, scope.ServiceHostname))
err = nil
case 3:
policy.UI.Inform(fmt.Sprintf("Request by %s to run '%s' on %s@%s PERMANENTLY APPROVED by user",
scope.Client, cmd, scope.ServiceUsername, scope.ServiceHostname))
err = policy.Store.AllowCommand(scope, cmd)
case 4:
policy.UI.Inform(fmt.Sprintf("Request by %s to run ANY COMMAND on %s@%s PERMANENTLY APPROVED by user",
scope.Client, scope.ServiceUsername, scope.ServiceHostname))
err = policy.Store.AllowAll(scope)
default:
policy.UI.Inform(fmt.Sprintf("Request by %s to run '%s' on %s@%s DENIED by user",
scope.Client, cmd, scope.ServiceUsername, scope.ServiceHostname))
err = errors.New("User rejected client request")
}

return err
}

func (policy *Policy) RequestApprovalForAllCommands(scope Scope) error {
if policy.Store.AreAllAllowed(scope) {
policy.UI.Inform(fmt.Sprintf("Request by %s@%s:%d to run any command on %s@%s auto approved by policy",
scope.ClientUsername, scope.ClientHostname,
scope.ClientPort, scope.ServiceUsername, scope.ServiceHostname))
policy.UI.Inform(fmt.Sprintf("Request by %s to run ANY COMMAND on %s@%s AUTO-APPROVED by policy",
scope.Client, scope.ServiceUsername, scope.ServiceHostname))
return nil
}
question := fmt.Sprintf("Can't enforce permission for a single command. Allow %s@%s:%d to run any command on %s@%s?",
scope.ClientUsername, scope.ClientHostname,
scope.ClientPort, scope.ServiceUsername,
scope.ServiceHostname)
question := fmt.Sprintf("Can't enforce permission for a single command. Allow %s to run ANY COMMAND on %s@%s?",
scope.Client, scope.ServiceUsername, scope.ServiceHostname)

prompt := Prompt{
Question: question,
Expand All @@ -71,12 +71,18 @@ func (policy *Policy) RequestApprovalForAllCommands(scope Scope) error {
resp, err := policy.UI.Ask(prompt)

switch resp {
case 1:
err = errors.New("Policy rejected approval escalation")
case 2:
policy.UI.Inform(fmt.Sprintf("Request by %s to run ANY COMMAND on %s@%s APPROVED by user",
scope.Client, scope.ServiceUsername, scope.ServiceHostname))
err = nil
case 3:
policy.UI.Inform(fmt.Sprintf("Request by %s to run ANY COMMAND on %s@%s PERMANENTLY APPROVED by user",
scope.Client, scope.ServiceUsername, scope.ServiceHostname))
err = policy.Store.AllowAll(scope)
default:
policy.UI.Inform(fmt.Sprintf("Request by %s to run ANY COMMAND on %s@%s DENIED by user",
scope.Client, scope.ServiceUsername, scope.ServiceHostname))
err = errors.New("User rejected approval escalation")
}

return err
Expand Down
4 changes: 1 addition & 3 deletions scope.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package guardianagent

type Scope struct {
ClientUsername string `json:"ClientUsername"`
ClientHostname string `json:"ClientHostname"`
ClientPort uint32 `json:"ClientPort"`
Client string `json:"Client"`
ServiceUsername string `json:"ServiceUsername"`
ServiceHostname string `json:"ServiceHostname"`
}
1 change: 1 addition & 0 deletions scripts/sga-guard
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#!/bin/sh

command -v autossh >/dev/null 2>&1 || { echo "autossh is required but is not installed. Aborting." >&2; exit 1; }
exec env AUTOSSH_PATH=sga-guard-bin autossh -M 0 -oServerAliveInterval=30 -oServerAliveCountMax=3 -- $*
Loading

0 comments on commit 5fc929e

Please sign in to comment.