Skip to content

Commit

Permalink
Merge 201443e into 6fd3cbf
Browse files Browse the repository at this point in the history
  • Loading branch information
AnalogJ committed Apr 21, 2018
2 parents 6fd3cbf + 201443e commit 15e46bf
Show file tree
Hide file tree
Showing 19 changed files with 290 additions and 132 deletions.
11 changes: 10 additions & 1 deletion README.md
Expand Up @@ -83,4 +83,13 @@
- https://stackoverflow.com/questions/12484398/global-template-data
- https://stackoverflow.com/questions/35612456/how-to-use-golang-template-missingkey-option
- https://medium.com/@dgryski/consistent-hashing-algorithmic-tradeoffs-ef6b8e2fcae8
- https://github.com/mitchellh/go-homedir
- https://github.com/mitchellh/go-homedir
- https://gobyexample.com/execing-processes
- https://groob.io/posts/golang-execve/
- https://www.digitalocean.com/community/tutorials/how-to-configure-custom-connection-options-for-your-ssh-client

- https://github.com/zalando/go-keyring
- https://github.com/tmc/keyring
- https://github.com/jaraco/keyring
- https://github.com/99designs/keyring
- https://unix.stackexchange.com/questions/64795/must-i-store-a-private-key-in-a-file
19 changes: 6 additions & 13 deletions cmd/drawbridge/drawbridge.go
Expand Up @@ -5,16 +5,12 @@ import (
"os"
"time"

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

Expand Down Expand Up @@ -81,7 +77,7 @@ func main() {
return err
}

createEngine := create.CreateEngine{Config: config}
createEngine := actions.CreateAction{Config: config}
return createEngine.Start(cliAnswers)
},

Expand All @@ -92,7 +88,7 @@ func main() {
Usage: "List all drawbridge managed ssh configs",
Action: func(c *cli.Context) error {

listEngine := list.ListEngine{Config: config}
listEngine := actions.ListAction{Config: config}
return listEngine.Start()
},

Expand All @@ -103,20 +99,17 @@ func main() {
Usage: "Connect to a drawbridge managed ssh config",
Action: func(c *cli.Context) error {

listEngine := list.ListEngine{Config: config}
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
}
answerIndex := i - 1

connectEngine := connect.ConnectEngine{Config: config}
connectEngine := actions.ConnectAction{Config: config}
return connectEngine.Start(listEngine.OrderedAnswers[answerIndex].(map[string]interface{}))
},

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
123 changes: 123 additions & 0 deletions pkg/actions/connect.go
@@ -0,0 +1,123 @@
package actions

import (
"drawbridge/pkg/config"
"drawbridge/pkg/utils"
"path/filepath"
"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 {
Config config.Interface
}

func (e *ConnectAction) Start(answerData map[string]interface{}) error {

//"-c", "command1; command2; command3; ..."

tmplData, err := e.Config.GetActiveConfigTemplate()
if err != nil {
return nil
}
tmplConfigFilepath, err := utils.PopulateTemplate(tmplData.FilePath, answerData)
if err != nil {
return nil
}
tmplConfigFilepath, err = utils.ExpandPath(filepath.Join(e.Config.GetString("options.config_dir"), tmplConfigFilepath))
if err != nil {
return nil
}

tmplPemFilepath, err := utils.PopulateTemplate(tmplData.PemFilePath, answerData)
if err != nil {
return nil
}
tmplPemFilepath, err = utils.ExpandPath(filepath.Join(e.Config.GetString("options.pem_dir"), tmplPemFilepath))
if err != nil {
return nil
}

//TODO: Print the lines we're running.

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


e.SshAgentAddPemKey(tmplPemFilepath)

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


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

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

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.Bytes, []byte(passphrase))
} else {
privateKeyData, err = ssh.ParseRawPrivateKey(block.Bytes)
}

// 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
}
22 changes: 8 additions & 14 deletions pkg/create/create.go → pkg/actions/create.go
@@ -1,24 +1,21 @@
package create
package actions

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

type CreateEngine struct {
type CreateAction struct {
Config config.Interface
}

func (e *CreateEngine) Start(cliAnswerData map[string]interface{}) error {
func (e *CreateAction) Start(cliAnswerData map[string]interface{}) error {

// prepare answer data with config.options
answerData := map[string]interface{}{}
Expand Down Expand Up @@ -66,7 +63,7 @@ func (e *CreateEngine) Start(cliAnswerData map[string]interface{}) error {
return err
}

err = activeConfigTemplate.WriteConfigTemplate(answerData, e.Config.GetString("options.config_dir"))
err = activeConfigTemplate.WriteTemplate(answerData)
if err != nil {
return err
}
Expand Down Expand Up @@ -103,7 +100,7 @@ func (e *CreateEngine) Start(cliAnswerData map[string]interface{}) error {
return nil
}

func (e *CreateEngine) Query(questions map[string]config.Question, answerData map[string]interface{}) (map[string]interface{}, error) {
func (e *CreateAction) Query(questions map[string]config.Question, answerData map[string]interface{}) (map[string]interface{}, error) {
for questionKey, questionData := range questions {

val, ok := questionData.Schema["required"]
Expand All @@ -118,15 +115,12 @@ func (e *CreateEngine) Query(questions map[string]config.Question, answerData ma
return answerData, nil
}

func (e *CreateEngine) queryResponse(questionKey string, question config.Question) interface{} {
func (e *CreateAction) queryResponse(questionKey string, question config.Question) interface{} {

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
1 change: 1 addition & 0 deletions pkg/actions/delete.go
@@ -0,0 +1 @@
package actions
1 change: 1 addition & 0 deletions pkg/actions/download.go
@@ -0,0 +1 @@
package actions
8 changes: 4 additions & 4 deletions pkg/list/list.go → pkg/actions/list.go
@@ -1,4 +1,4 @@
package list
package actions

import (
"drawbridge/pkg/config"
Expand All @@ -16,13 +16,13 @@ import (
"strings"
)

type ListEngine struct {
type ListAction struct {
Config config.Interface
GroupedAnswers *gabs.Container
OrderedAnswers []interface{}
}

func (e *ListEngine) Start() error {
func (e *ListAction) Start() error {

configDir := e.Config.GetString("options.config_dir")
configDir, err := utils.ExpandPath(configDir)
Expand Down Expand Up @@ -115,7 +115,7 @@ func (e *ListEngine) Start() error {
return nil
}

func (e *ListEngine) PrintUI(level int, groups []string, groupedAnswers *gabs.Container) error {
func (e *ListAction) PrintUI(level int, groups []string, groupedAnswers *gabs.Container) error {
children, _ := groupedAnswers.ChildrenMap()
for groupKey, child := range children {
nextGroups := []string{}
Expand Down

0 comments on commit 15e46bf

Please sign in to comment.