ssh/lookup implementation and refactoring

This commit is contained in:
Vishal Nayak 2015-06-25 21:47:32 -04:00
parent e90fb0cc09
commit 7dbad8386c
10 changed files with 291 additions and 142 deletions

26
api/ssh.go Normal file
View File

@ -0,0 +1,26 @@
package api
import "fmt"
type Ssh struct {
c *Client
}
func (c *Client) Ssh() *Ssh {
return &Ssh{c: c}
}
func (c *Ssh) KeyCreate(data map[string]interface{}) (*Secret, error) {
r := c.c.NewRequest("PUT", fmt.Sprintf("/v1/ssh/creds/web"))
if err := r.SetJSONBody(data); err != nil {
return nil, err
}
resp, err := c.c.RawRequest(r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return ParseSecret(resp.Body)
}

View File

@ -1,30 +0,0 @@
package api
import "fmt"
func (c *Sys) Ssh(data map[string]interface{}) (*Secret, error) {
r := c.c.NewRequest("PUT", fmt.Sprintf("/v1/ssh/creds/web"))
if err := r.SetJSONBody(data); err != nil {
return nil, err
}
resp, err := c.c.RawRequest(r)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return ParseSecret(resp.Body)
/*
result := new(Secret)
err = resp.DecodeJSON(&result)
log.Printf("Vishal: api.sys_ssh.Ssh: result:%#v\n", result.Data)
var oneTimeKey OneTimeKey
err = result.Data.DecodeJSON(&oneTimeKey)
log.Printf("Vishal: oneTimeKey:%#v\n", oneTimeKey)
return &oneTimeKey, err
*/
//return result, err
}

View File

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

View File

@ -0,0 +1,85 @@
package ssh
import (
"encoding/json"
"fmt"
"log"
"net"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
)
func pathLookup(b *backend) *framework.Path {
log.Printf("Vishal: ssh.pathLookup\n")
return &framework.Path{
Pattern: "lookup",
Fields: map[string]*framework.FieldSchema{
"ip": &framework.FieldSchema{
Type: framework.TypeString,
Description: "IP address of target",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.pathLookupWrite,
},
HelpSynopsis: pathLookupSyn,
HelpDescription: pathLookupDesc,
}
}
func (b *backend) pathLookupWrite(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
ip := d.Get("ip").(string)
//ip := "127.0.0.1"
ipAddr := net.ParseIP(ip)
if ipAddr == nil {
return logical.ErrorResponse(fmt.Sprintf("Invalid IP '%s'", ip)), nil
}
keys, _ := req.Storage.List("policy/")
var matchingRoles []string
for _, item := range keys {
if contains, _ := containsIP(req.Storage, item, ip); contains {
matchingRoles = append(matchingRoles, item)
}
}
log.Printf("Vishal: req.Path: %#v \n Keys:%#v\n", req.Path, keys)
return &logical.Response{
Data: map[string]interface{}{
"roles": matchingRoles,
},
}, nil
}
func containsIP(s logical.Storage, roleName string, ip string) (bool, error) {
roleEntry, err := s.Get("policy/" + roleName)
if err != nil {
return false, fmt.Errorf("error retrieving role '%s'", err)
}
if roleEntry == nil {
return false, fmt.Errorf("role '%s' not found", roleName)
}
var role sshRole
if err := roleEntry.DecodeJSON(&role); err != nil {
return false, fmt.Errorf("error decoding role '%s'", roleName)
}
var cidrEntry sshCIDR
ipMatched := false
json.Unmarshal([]byte(role.CIDR), &cidrEntry)
for _, item := range cidrEntry.CIDR {
log.Println(item)
_, cidrIPNet, _ := net.ParseCIDR(item)
ipMatched = cidrIPNet.Contains(net.ParseIP(ip))
if ipMatched {
break
}
}
return ipMatched, nil
}
const pathLookupSyn = `
pathLookupSyn
`
const pathLookupDesc = `
pathLoookupDesc
`

View File

@ -2,9 +2,11 @@ package ssh
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net"
"strings"
"github.com/hashicorp/vault/logical"
@ -41,10 +43,12 @@ func (b *backend) pathRoleCreateWrite(
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
log.Printf("Vishal: ssh.pathRoleCreateWrite\n")
//fetch the parameters
roleName := d.Get("name").(string)
username := d.Get("username").(string)
ipAddr := d.Get("ip").(string)
ipRaw := d.Get("ip").(string)
//find the role to be used for installing dynamic key
rolePath := "policy/" + roleName
roleEntry, err := req.Storage.Get(rolePath)
if err != nil {
@ -53,67 +57,119 @@ func (b *backend) pathRoleCreateWrite(
if roleEntry == nil {
return logical.ErrorResponse(fmt.Sprintf("Role '%s' not found", roleName)), nil
}
var role sshRole
if err := roleEntry.DecodeJSON(&role); err != nil {
return nil, err
}
log.Printf("Vishal: ssh.pathRoleCreateWrite username:%#v address:%#v name:%#v result:%s\n", username, ipAddr, roleName, role)
//TODO: do the role verification here
//validate the IP address
ipAddr := net.ParseIP(ipRaw)
if ipAddr == nil {
return logical.ErrorResponse(fmt.Sprintf("Invalid IP '%s'", ipRaw)), nil
}
ip := ipAddr.String()
var cidrEntry sshCIDR
ipMatched := false
json.Unmarshal([]byte(role.CIDR), &cidrEntry)
for _, item := range cidrEntry.CIDR {
log.Println(item)
_, cidrIPNet, _ := net.ParseCIDR(item)
ipMatched = cidrIPNet.Contains(ipAddr)
if ipMatched {
break
}
}
if !ipMatched {
return logical.ErrorResponse(fmt.Sprintf("IP[%s] does not belong to role[%s]", ip, roleName)), nil
}
//fetch the host key to be used for installation
keyPath := "keys/" + role.KeyName
keyEntry, err := req.Storage.Get(keyPath)
if err != nil {
return nil, fmt.Errorf("Key '%s' not found error:%s", role.KeyName, err)
}
log.Printf("Vishal: KeyName:%s keyPath:%s\n", role.KeyName, keyPath)
var hostKey sshHostKey
if err := keyEntry.DecodeJSON(&hostKey); err != nil {
return nil, fmt.Errorf("Error reading the host key: %s", err)
}
log.Printf("Vishal: host key previously configured: \n---------------\n%#v\n--------------\n", hostKey.Key)
//TODO: Input validation for the commands below
hostKeyFileName := "./vault_ssh_" + username + "_" + ipAddr + "_shared.pem"
//store the host key to file. Use it as parameter for scp command
hostKeyFileName := "./vault_ssh_" + username + "_" + ip + "_shared.pem"
err = ioutil.WriteFile(hostKeyFileName, []byte(hostKey.Key), 0400)
otkPrivateKeyFileName := "vault_ssh_" + username + "_" + ipAddr + "_otk.pem"
otkPrivateKeyFileName := "vault_ssh_" + username + "_" + ip + "_otk.pem"
otkPublicKeyFileName := otkPrivateKeyFileName + ".pub"
//commands to be run on vault server
rmCmd := "rm -f " + otkPrivateKeyFileName + " " + otkPublicKeyFileName + ";"
sshKeygenCmd := "ssh-keygen -f " + otkPrivateKeyFileName + " -t rsa -N ''" + ";"
chmodCmd := "chmod 400 " + otkPrivateKeyFileName + ";"
scpCmd := "scp -i " + hostKeyFileName + " " + otkPublicKeyFileName + " " + username + "@" + ipAddr + ":~;"
log.Printf("Vishal: scpCmd: \n", scpCmd)
scpCmd := "scp -i " + hostKeyFileName + " " + otkPublicKeyFileName + " " + username + "@" + ip + ":~;"
localCmdString := strings.Join([]string{
rmCmd,
sshKeygenCmd,
chmodCmd,
scpCmd,
}, "")
//run the commands on vault server
err = exec_command(localCmdString)
if err != nil {
fmt.Errorf("Running command failed " + err.Error())
}
log.Printf("Vishal: Creating session\n")
session := createSSHPublicKeysSession(username, ipAddr)
//connect to target machine
session := createSSHPublicKeysSession(username, ip, hostKey.Key)
var buf bytes.Buffer
session.Stdout = &buf
log.Printf("Vishal: Installing keys\n")
if err := installSshOtkInTarget(session, username, ipAddr); err != nil {
fmt.Errorf("Failed to install one-time-key at target machine: " + err.Error())
authKeysFileName := "~/.ssh/authorized_keys"
tempKeysFileName := "~/temp_authorized_keys"
//commands to be run on target machine
grepCmd := "grep -vFf " + otkPublicKeyFileName + " " + authKeysFileName + " > " + tempKeysFileName + ";"
catCmdRemoveDuplicate := "cat " + tempKeysFileName + " > " + authKeysFileName + ";"
catCmdAppendNew := "cat " + otkPublicKeyFileName + " >> " + authKeysFileName + ";"
removeCmd := "rm -f " + tempKeysFileName + " " + otkPublicKeyFileName + ";"
remoteCmdString := strings.Join([]string{
grepCmd,
catCmdRemoveDuplicate,
catCmdAppendNew,
removeCmd,
}, "")
//run the commands on target machine
if err := session.Run(remoteCmdString); err != nil {
return nil, err
}
session.Close()
fmt.Println(buf.String())
keyBytes, err := ioutil.ReadFile(otkPrivateKeyFileName)
oneTimeKey := string(keyBytes)
log.Printf("Vishal: Returning:[%s]\n", oneTimeKey)
//preparing the secret
dynamicPrivateKeyBytes, err := ioutil.ReadFile(otkPrivateKeyFileName)
if err != nil {
fmt.Errorf("Failed to open '%s':%s", otkPrivateKeyFileName, err)
}
dynamicPrivateKey := string(dynamicPrivateKeyBytes)
dynamicPublicKeyBytes, err := ioutil.ReadFile(otkPublicKeyFileName)
if err != nil {
fmt.Errorf("Failed to open '%s':%s", otkPublicKeyFileName, err)
}
dynamicPublicKey := string(dynamicPublicKeyBytes)
return b.Secret(SecretOneTimeKeyType).Response(map[string]interface{}{
"key": oneTimeKey,
}, nil), nil
"key": dynamicPrivateKey,
}, map[string]interface{}{
"username": username,
"ip": ip,
"host_key_name": role.KeyName,
"dynamic_public_key": dynamicPublicKey,
}), nil
}
type sshCIDR struct {
CIDR []string
}
const sshConnectHelpSyn = `

View File

@ -1,7 +1,11 @@
package ssh
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"strings"
"time"
"github.com/hashicorp/vault/logical"
@ -24,8 +28,8 @@ func secretSshKey(b *backend) *framework.Secret {
Description: "ip address of host",
},
},
DefaultDuration: 1 * time.Hour,
DefaultGracePeriod: 10 * time.Minute,
DefaultDuration: 10 * time.Second, //TODO: change this
DefaultGracePeriod: 10 * time.Second, //TODO: change this
Renew: b.secretSshKeyRenew,
Revoke: b.secretSshKeyRevoke,
}
@ -45,7 +49,90 @@ func (b *backend) secretSshKeyRenew(req *logical.Request, d *framework.FieldData
}
func (b *backend) secretSshKeyRevoke(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
log.Printf("Vishal: ssh.secretPrivateKeyRevoke\n")
//TODO: implement here
log.Printf("Vishal: ssh.secretPrivateKeyRevoke req: %#v\n", req)
//fetch the values from secret
usernameRaw, ok := req.Secret.InternalData["username"]
if !ok {
return nil, fmt.Errorf("secret is missing internal data")
}
username, ok := usernameRaw.(string)
if !ok {
return nil, fmt.Errorf("secret is missing internal data")
}
ipRaw, ok := req.Secret.InternalData["ip"]
if !ok {
return nil, fmt.Errorf("secret is missing internal data")
}
ip, ok := ipRaw.(string)
if !ok {
return nil, fmt.Errorf("secret is missing internal data")
}
hostKeyNameRaw, ok := req.Secret.InternalData["host_key_name"]
if !ok {
return nil, fmt.Errorf("secret is missing internal data")
}
hostKeyName := hostKeyNameRaw.(string)
if !ok {
return nil, fmt.Errorf("secret is missing internal data")
}
dynamicPublicKeyRaw, ok := req.Secret.InternalData["dynamic_public_key"]
if !ok {
return nil, fmt.Errorf("secret is missing internal data")
}
dynamicPublicKey := dynamicPublicKeyRaw.(string)
if !ok {
return nil, fmt.Errorf("secret is missing internal data")
}
log.Printf("Vishal: username:%s ip:%s keyName:%s\n", username, ip, hostKeyName)
//fetch the host key using the key name
hostKeyPath := "keys/" + hostKeyName
hostKeyEntry, err := req.Storage.Get(hostKeyPath)
if err != nil {
return nil, fmt.Errorf("Key '%s' not found error:%s", hostKeyName, err)
}
var hostKey sshHostKey
if err := hostKeyEntry.DecodeJSON(&hostKey); err != nil {
return nil, fmt.Errorf("Error reading the host key: %s", err)
}
//write host key to file and use it as argument to scp command
hostKeyFileName := "./vault_ssh_" + username + "_" + ip + "_shared.pem"
err = ioutil.WriteFile(hostKeyFileName, []byte(hostKey.Key), 0400)
//write dynamicPublicKey to file and use it as argument to scp command
otkPrivateKeyFileName := "vault_ssh_" + username + "_" + ip + "_otk.pem"
otkPublicKeyFileName := otkPrivateKeyFileName + ".pub"
err = ioutil.WriteFile(otkPublicKeyFileName, []byte(dynamicPublicKey), 0400)
//transfer the dynamic public key to target machine and use it to remove the entry from authorized_keys file
scpCmd := "scp -i " + hostKeyFileName + " " + otkPublicKeyFileName + " " + username + "@" + ip + ":~;"
err = exec_command(scpCmd)
if err != nil {
fmt.Errorf("Running command scp failed " + err.Error())
}
authKeysFileName := "~/.ssh/authorized_keys"
tempKeysFileName := "~/temp_authorized_keys"
//commands to be run on target machine
grepCmd := "grep -vFf " + otkPublicKeyFileName + " " + authKeysFileName + " > " + tempKeysFileName + ";"
catCmdRemoveDuplicate := "cat " + tempKeysFileName + " > " + authKeysFileName + ";"
rmCmd := "rm -f " + tempKeysFileName + " " + otkPublicKeyFileName + ";"
remoteCmdString := strings.Join([]string{
grepCmd,
catCmdRemoveDuplicate,
rmCmd,
}, "")
//connect to target machine
session := createSSHPublicKeysSession(username, ip, hostKey.Key)
var buf bytes.Buffer
session.Stdout = &buf
if err := session.Run(remoteCmdString); err != nil {
return nil, err
}
session.Close()
fmt.Println(buf.String())
return nil, nil
}

View File

@ -2,10 +2,7 @@ package ssh
import (
"fmt"
"io/ioutil"
"log"
"os/exec"
"strings"
"golang.org/x/crypto/ssh"
)
@ -18,40 +15,8 @@ func exec_command(cmdString string) error {
return nil
}
func installSshOtkInTarget(session *ssh.Session, username string, ipAddr string) error {
log.Printf("Vishal: ssh.installSshOtkInTarget\n")
//TODO: Input validation for the commands below
otkPrivateKeyFileName := "vault_ssh_" + username + "_" + ipAddr + "_otk.pem"
otkPublicKeyFileName := otkPrivateKeyFileName + ".pub"
authKeysFileName := "~/.ssh/authorized_keys"
tempKeysFileName := "./temp_authorized_keys"
grepCmd := "grep -vFf " + otkPublicKeyFileName + " " + authKeysFileName + " > " + tempKeysFileName + ";"
catCmdRemoveDuplicate := "cat " + tempKeysFileName + " > " + authKeysFileName + ";"
catCmdAppendNew := "cat " + otkPublicKeyFileName + " >> " + authKeysFileName + ";"
rmCmd := "rm -f " + tempKeysFileName + " " + otkPublicKeyFileName + ";"
log.Printf("Vishal: grepCmd:%#v\n catCmdRemoveDuplicate:%#v\n catCmdAppendNew:%#v\n rmCmd: %#v\n", grepCmd, catCmdRemoveDuplicate, catCmdAppendNew, rmCmd)
remoteCmdString := strings.Join([]string{
grepCmd,
catCmdRemoveDuplicate,
catCmdAppendNew,
rmCmd,
}, "")
if err := session.Run(remoteCmdString); err != nil {
return err
}
return nil
}
func createSSHPublicKeysSession(username string, ipAddr string) *ssh.Session {
hostKeyFileName := "./vault_ssh_" + username + "_" + ipAddr + "_shared.pem"
pemBytes, err := ioutil.ReadFile(hostKeyFileName)
if err != nil {
fmt.Errorf("Reading shared key failed: " + err.Error())
}
signer, err := ssh.ParsePrivateKey(pemBytes)
func createSSHPublicKeysSession(username string, ipAddr string, hostKey string) *ssh.Session {
signer, err := ssh.ParsePrivateKey([]byte(hostKey))
if err != nil {
fmt.Errorf("Parsing Private Key failed: " + err.Error())
}

View File

@ -29,7 +29,10 @@ func (c *SshCommand) Run(args []string) int {
c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
return 2
}
//if len(args) < 3, fail
if len(args) < 1 {
c.Ui.Error(fmt.Sprintf("Insufficient arguments"))
return 2
}
log.Printf("Vishal: sshCommand.Run: args[0]: %#v\n", args[0])
input := strings.Split(args[0], "@")
username := input[0]
@ -40,13 +43,13 @@ func (c *SshCommand) Run(args []string) int {
"ip": ipAddr.String(),
}
keySecret, err := client.Sys().Ssh(data)
keySecret, err := client.Ssh().KeyCreate(data)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error getting key for establishing SSH session", err))
return 2
}
sshOneTimeKey := string(keySecret.Data["key"].(string))
log.Printf("Vishal: command.ssh.Run returned! OTK:%#v\n", sshOneTimeKey)
log.Printf("Vishal: command.ssh.Run returned! len(key):%d\n", len(sshOneTimeKey))
ag := strings.Split(args[0], "@")
sshOtkFileName := "vault_ssh_otk_" + ag[0] + "_" + ag[1] + ".pem"
err = ioutil.WriteFile(sshOtkFileName, []byte(sshOneTimeKey), 0400)

View File

@ -1,43 +0,0 @@
package http
import (
"log"
"net/http"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/vault"
)
func handleSysSsh(core *vault.Core) http.Handler {
log.Printf("Vishal: http.sys_ssh.handleSysSsh!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "PUT" {
respondError(w, http.StatusMethodNotAllowed, nil)
return
}
log.Printf("Vishal: http.sys_ssh.handleSysSsh: requesting\n")
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/creds/web",
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"`
}

View File

@ -146,7 +146,6 @@ func (r *Router) MatchingView(path string) *BarrierView {
// Route is used to route a given request
func (r *Router) Route(req *logical.Request) (*logical.Response, error) {
log.Printf("Vishal: vault.router.Route: req.Path:%#v\n", req.Path)
// Find the mount point
r.l.RLock()
mount, raw, ok := r.root.LongestPrefix(req.Path)