Added: Ssh CLI command and API, config lease impl, sshConnect path to backend, http handler for Ssh connect

This commit is contained in:
Vishal Nayak 2015-06-17 12:39:49 -04:00
parent 08c921c75e
commit 3ed73d98c2
8 changed files with 219 additions and 0 deletions

39
api/sys_ssh.go Normal file
View File

@ -0,0 +1,39 @@
package api
import (
"fmt"
"log"
"net"
"strings"
)
func (c *Sys) Ssh(target string) (*OneTimeKey, error) {
r := c.c.NewRequest("POST", fmt.Sprintf("/v1/ssh/connect"))
input := strings.Split(target, "@")
username := input[0]
ipAddr := input[1]
ip4Addr, err := net.ResolveIPAddr("ip4", ipAddr)
log.Printf("Vishal: ssh.Ssh ipAddr_resolved: %#v\n", ip4Addr.String())
body := map[string]interface{}{
"username": username,
"address": ip4Addr.String(),
}
if err := r.SetJSONBody(body); err != nil {
return nil, err
}
resp, err := c.c.RawRequest(r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result OneTimeKey
err = resp.DecodeJSON(&result)
log.Printf("Vishal: api.Sys.Ssh: result:%#v\n", result)
return &result, err
}
type OneTimeKey struct {
Key string
}

View File

@ -27,6 +27,7 @@ func Backend() *framework.Backend {
pathConfigAddHostKey(&b),
pathConfigRemoveHostKey(&b),
pathRoles(&b),
sshConnect(&b),
},
Secrets: []*framework.Secret{

View File

@ -1,7 +1,9 @@
package ssh
import (
"fmt"
"log"
"time"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
@ -34,9 +36,40 @@ func pathConfigLease(b *backend) *framework.Path {
func (b *backend) pathLeaseWrite(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
log.Printf("Vishal: ssh.pathLeaseWrite\n")
leaseRaw := d.Get("lease").(string)
leaseMaxRaw := d.Get("lease_max").(string)
lease, err := time.ParseDuration(leaseRaw)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf(
"Invalid lease: %s", err)), nil
}
leaseMax, err := time.ParseDuration(leaseMaxRaw)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf(
"Invalid lease: %s", err)), nil
}
// Store it
entry, err := logical.StorageEntryJSON("config/lease", &configLease{
Lease: lease,
LeaseMax: leaseMax,
})
if err != nil {
return nil, err
}
if err := req.Storage.Put(entry); err != nil {
return nil, err
}
return nil, nil
}
type configLease struct {
Lease time.Duration
LeaseMax time.Duration
}
const pathConfigLeaseHelpSyn = `
Configure the default lease information for SSH one time keys.
`

View File

@ -0,0 +1,48 @@
package ssh
import (
"log"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
)
func sshConnect(b *backend) *framework.Path {
log.Printf("Vishal: ssh.sshConnect\n")
return &framework.Path{
Pattern: "connect",
Fields: map[string]*framework.FieldSchema{
"username": &framework.FieldSchema{
Type: framework.TypeString,
Description: "username at SSH host",
},
"address": &framework.FieldSchema{
Type: framework.TypeString,
Description: "IPv4 address of SSH host",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.sshConnectWrite,
},
HelpSynopsis: sshConnectHelpSyn,
HelpDescription: sshConnectHelpDesc,
}
}
func (b *backend) sshConnectWrite(
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
log.Printf("Vishal: ssh.sshConnectWrite username:%#v address:%#v\n", d.Get("username").(string), d.Get("address").(string))
return &logical.Response{
Data: map[string]interface{}{
"key": "createdKey",
},
}, nil
}
const sshConnectHelpSyn = `
sshConnectionHelpSyn
`
const sshConnectHelpDesc = `
sshConnectionHelpDesc
`

View File

@ -73,6 +73,12 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory {
}, nil
},
"ssh": func() (cli.Command, error) {
return &command.SshCommand{
Meta: meta,
}, nil
},
"help": func() (cli.Command, error) {
return &command.HelpCommand{
Meta: meta,

49
command/ssh.go Normal file
View File

@ -0,0 +1,49 @@
package command
import (
"fmt"
"log"
"strings"
)
type SshCommand struct {
Meta
}
func (c *SshCommand) Run(args []string) int {
log.Printf("Vishal: SshCommand.Run: args:%#v len(args):%d\n", args, len(args))
flags := c.Meta.FlagSet("ssh", FlagSetDefault)
flags.Usage = func() { c.Ui.Error(c.Help()) }
if err := flags.Parse(args); err != nil {
return 1
}
client, err := c.Client()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
return 2
}
//if len(args) < 3, fail
log.Printf("Vishal: sshCommand.Run: args[0]: %#v\n", args[0])
sshOneTimeKey, err := client.Sys().Ssh(args[0])
if err != nil {
c.Ui.Error(fmt.Sprintf("Error getting one-time-key for establishing SSH session", err))
return 2
}
log.Printf("Vishal: client.Sys().Ssh() returned! OTK:%#v\n", sshOneTimeKey)
//if sshOneTimeKey is empty, fail
//Establish a session directly from client to the target using the one time key received without making the vault server the middle guy:w
return 0
}
func (c *SshCommand) Synopsis() string {
return "Initiate a SSH session"
}
func (c *SshCommand) Help() string {
helpText := `
SshCommand Help String
`
return strings.TrimSpace(helpText)
}

View File

@ -44,6 +44,7 @@ func Handler(core *vault.Core) http.Handler {
mux.Handle("/v1/sys/key-status", handleSysKeyStatus(core))
mux.Handle("/v1/sys/rekey/init", handleSysRekeyInit(core))
mux.Handle("/v1/sys/rekey/update", handleSysRekeyUpdate(core))
mux.Handle("/v1/ssh/connect", handleSysSsh(core))
mux.Handle("/v1/", handleLogical(core))
// Wrap the handler in another handler to trigger all help paths.

42
http/sys_ssh.go Normal file
View File

@ -0,0 +1,42 @@
package http
import (
"log"
"net/http"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/vault"
)
func handleSysSsh(core *vault.Core) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
respondError(w, http.StatusMethodNotAllowed, nil)
return
}
log.Printf("Vishal: http.sys_ssh.handleSysSsh req:%#v\n", r)
var req SshRequest
if err := parseRequest(r, &req); err != nil {
respondError(w, http.StatusBadRequest, err)
return
}
resp, ok := request(core, w, r, requestAuth(r, &logical.Request{
Operation: logical.WriteOperation,
Path: "ssh/connect",
Data: map[string]interface{}{
"username": req.Username,
"address": req.Address,
},
}))
if !ok {
return
}
respondOk(w, resp.Data)
})
}
type SshRequest struct {
Username string `json: "username"`
Address string `json: "address"`
}