Skip to content

Commit

Permalink
moved all STDIN code into a utility function. added SSH Agent code to…
Browse files Browse the repository at this point in the history
… add pem (possibly encrypted) to ssh-agent before opening a tunnel.
  • Loading branch information
AnalogJ committed Apr 20, 2018
1 parent 87e7696 commit 4be5f72
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 19 deletions.
7 changes: 1 addition & 6 deletions cmd/drawbridge/drawbridge.go
Expand Up @@ -5,14 +5,12 @@ import (
"os"
"time"

"bufio"
"drawbridge/pkg/config"
"drawbridge/pkg/actions"
"drawbridge/pkg/utils"
"drawbridge/pkg/version"
"gopkg.in/urfave/cli.v2"
"strconv"
"strings"
"log"
)

Expand Down Expand Up @@ -104,10 +102,7 @@ func main() {
listEngine := actions.ListAction{Config: config}
listEngine.Start()

reader := bufio.NewReader(os.Stdin)
text, _ := reader.ReadString('\n')
fmt.Println(text)
text = strings.TrimSpace(text)
text := utils.StdinQuery("Enter number of ssh config you would like to connect to:")
i, err := strconv.Atoi(text)
if err != nil {
return err
Expand Down
1 change: 1 addition & 0 deletions glide.yaml
Expand Up @@ -13,6 +13,7 @@ import:
- package: github.com/mitchellh/go-homedir
- package: github.com/Jeffail/gabs
version: ^1.0.0
- package: golang.org/x/crypto
testImport:
- package: github.com/matryer/moq
- package: github.com/golang/mock
Expand Down
70 changes: 66 additions & 4 deletions pkg/actions/connect.go
Expand Up @@ -7,6 +7,14 @@ import (
"syscall"
"os"
"os/exec"
"drawbridge/pkg/errors"
"golang.org/x/crypto/ssh/agent"
"net"
"golang.org/x/crypto/ssh"
"encoding/pem"
"fmt"
"io/ioutil"
"crypto/x509"
)

type ConnectAction struct {
Expand Down Expand Up @@ -43,19 +51,73 @@ func (e *ConnectAction) Start(answerData map[string]interface{}) error {

//TODO: Check that the bastion host is accessible.

//TODO: add the ssh/pem key to the ssh-agent (if its running).

e.SshAgentAddPemKey(tmplPemFilepath)

//https://gobyexample.com/execing-processes
//https://groob.io/posts/golang-execve/


binary, lookErr := exec.LookPath("ssh")
sshBin, lookErr := exec.LookPath("ssh")
if lookErr != nil {
panic(lookErr)
return errors.DependencyMissingError("ssh is missing")
}

args := []string{"ssh", "bastion", "-F", tmplConfigFilepath}

return syscall.Exec(binary, args, os.Environ())
return syscall.Exec(sshBin, args, os.Environ())
}


func (e *ConnectAction) SshAgentAddPemKey(pemFilepath string) error {
//first lets ensure that the pemFilepath exists
if !utils.FileExists(pemFilepath) {
return errors.PemKeyMissingError(fmt.Sprintf("No pem file exists at %v", pemFilepath))
}

//ensure that the ssh-agent is available on this machine.
_, err := exec.LookPath("ssh-agent")
if err != nil {
return errors.DependencyMissingError("ssh-agent is missing")
}

//read the pem file data
keyData, err := ioutil.ReadFile(pemFilepath)
if err != nil {
return err
}

//TODO: check if this pemfile is already added to the ssh-agent

//decode the ssh pem key (and handle encypted/passphrase protected keys)
//https://stackoverflow.com/questions/42105432/how-to-use-an-encrypted-private-key-with-golang-ssh
block, _ := pem.Decode(keyData)

//https://github.com/golang/crypto/blob/master/ssh/keys.go

var privateKeyData interface{}
if x509.IsEncryptedPEMBlock(block) {
//inform the user that the key is encrypted.
passphrase := utils.StdinQuery(fmt.Sprintf("The key at %v is encrypted and requires a passphrase. Please enter it below:", pemFilepath))
privateKeyData, err = ssh.ParseRawPrivateKeyWithPassphrase(block, []byte(passphrase))
} else {
privateKeyData, err = ssh.ParseRawPrivateKey(block)
}

// register the privatekey with ssh-agent

socket := os.Getenv("SSH_AUTH_SOCK")
conn, err := net.Dial("unix", socket)
if err != nil {
return err
}
agentClient := agent.NewClient(conn)

err = agentClient.Add(agent.AddedKey{
PrivateKey: privateKeyData,
Comment: fmt.Sprintf("drawbridge - %v", pemFilepath),
//LifetimeSecs: TODO: for safety we should limit this key's use for 1h
})

return err
}
10 changes: 2 additions & 8 deletions pkg/actions/create.go
@@ -1,17 +1,14 @@
package actions

import (
"bufio"
"drawbridge/pkg/config"
"drawbridge/pkg/errors"
"drawbridge/pkg/utils"
"fmt"
"gopkg.in/yaml.v2"
"log"
"os"
"path"
"strconv"
"strings"
)

type CreateAction struct {
Expand Down Expand Up @@ -122,11 +119,8 @@ func (e *CreateAction) queryResponse(questionKey string, question config.Questio

for true {
//this question is not answered, and it is required. We should ask the user.
stdReader := bufio.NewReader(os.Stdin)
s := fmt.Sprintf("Please enter a value for `%s` [%s] - %s:", questionKey, question.GetType(), question.Description)
fmt.Println(s)
answer, _ := stdReader.ReadString('\n')
answer = strings.Trim(answer, "\n")
answer := utils.StdinQuery(fmt.Sprintf("Please enter a value for `%s` [%s] - %s:", questionKey, question.GetType(), question.Description))

answerTyped, err := convertAnswerType(answer, question.GetType())
if err != nil {
fmt.Printf("%v\n", err)
Expand Down
21 changes: 20 additions & 1 deletion pkg/errors/errors.go
Expand Up @@ -18,13 +18,32 @@ func (str ConfigValidationError) Error() string {
return fmt.Sprintf("ConfigValidationError: %q", string(str))
}

// Raised when the
// Raised when a dependency (like ssh or ssh-agent) is missing
type DependencyMissingError string

func (str DependencyMissingError) Error() string {
return fmt.Sprintf("DependencyMissingError: %q", string(str))
}

// Raised when a SSH/pem key is not present.
type PemKeyMissingError string
func (str PemKeyMissingError) Error() string {
return fmt.Sprintf("PemKeyMissingError: %q", string(str))
}

// Raised when the file to write already exists
type TemplateFileExistsError string

func (str TemplateFileExistsError) Error() string {
return fmt.Sprintf("TemplateFileExistsError: %q", string(str))
}







// Raised when Question does not exist
type QuestionKeyInvalidError string

Expand Down
2 changes: 2 additions & 0 deletions pkg/errors/errors_test.go
Expand Up @@ -34,4 +34,6 @@ func TestErrors(t *testing.T) {
require.Implements(t, (*error)(nil), errors.QuestionKeyInvalidError("test"), "should implement the error interface")
require.Implements(t, (*error)(nil), errors.QuestionValidationError("test"), "should implement the error interface")
require.Implements(t, (*error)(nil), errors.AnswerFormatError("test"), "should implement the error interface")
require.Implements(t, (*error)(nil), errors.DependencyMissingError("test"), "should implement the error interface")
require.Implements(t, (*error)(nil), errors.PemKeyMissingError("test"), "should implement the error interface")
}
18 changes: 18 additions & 0 deletions pkg/utils/stdin.go
@@ -0,0 +1,18 @@
package utils

import (
"bufio"
"os"
"fmt"
"strings"
)

func StdinQuery(question string) string {

fmt.Println(question)
reader := bufio.NewReader(os.Stdin)
text, _ := reader.ReadString('\n')
fmt.Println(text)
text = strings.TrimSpace(text)
return text
}

0 comments on commit 4be5f72

Please sign in to comment.