Input validations, help strings, default_user support

This commit is contained in:
Vishal Nayak
2015-06-30 16:30:13 -04:00
parent 9ba1d26f4e
commit 2163818bd6
14 changed files with 199 additions and 157 deletions

View File

@@ -2,7 +2,6 @@ package aws
import ( import (
"fmt" "fmt"
"log"
"github.com/hashicorp/aws-sdk-go/aws" "github.com/hashicorp/aws-sdk-go/aws"
"github.com/hashicorp/aws-sdk-go/gen/iam" "github.com/hashicorp/aws-sdk-go/gen/iam"
@@ -33,8 +32,6 @@ func pathUser(b *backend) *framework.Path {
func (b *backend) pathUserRead( func (b *backend) pathUserRead(
req *logical.Request, d *framework.FieldData) (*logical.Response, error) { req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
policyName := d.Get("name").(string) policyName := d.Get("name").(string)
log.Printf("Vishal: policyName: %#v\n", policyName)
log.Printf("Vishal: data d: %#v\n", d)
// Read the policy // Read the policy
policy, err := req.Storage.Get("policy/" + policyName) policy, err := req.Storage.Get("policy/" + policyName)

View File

@@ -1,7 +1,6 @@
package ssh package ssh
import ( import (
"log"
"strings" "strings"
"github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical"
@@ -13,7 +12,6 @@ func Factory(map[string]string) (logical.Backend, error) {
} }
func Backend() *framework.Backend { func Backend() *framework.Backend {
log.Printf("Vishal: ssh.Backend\n")
var b backend var b backend
b.Backend = &framework.Backend{ b.Backend = &framework.Backend{
Help: strings.TrimSpace(backendHelp), Help: strings.TrimSpace(backendHelp),
@@ -42,8 +40,12 @@ type backend struct {
} }
const backendHelp = ` const backendHelp = `
The ssh backend enables secure connections to remote hosts. The SSH backend dynamically generates SSH private keys for remote hosts.
The key generated has a configurable lease set and are automatically
revoked at the end of the lease.
After mounting this backend, configure it using the endpoints within After mounting this backend, configure the lease using the 'config/lease'
the "config/" path. endpoint. The shared SSH key belonging to any infrastructure should be
registered with the 'roles/' endpoint before dynamic keys for remote hosts
can be generated.
` `

View File

@@ -2,7 +2,6 @@ package ssh
import ( import (
"fmt" "fmt"
"log"
"time" "time"
"github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical"
@@ -10,7 +9,6 @@ import (
) )
func pathConfigLease(b *backend) *framework.Path { func pathConfigLease(b *backend) *framework.Path {
log.Printf("Vishal: ssh.pathConfigLease\n")
return &framework.Path{ return &framework.Path{
Pattern: "config/lease", Pattern: "config/lease",
Fields: map[string]*framework.FieldSchema{ Fields: map[string]*framework.FieldSchema{
@@ -25,7 +23,7 @@ func pathConfigLease(b *backend) *framework.Path {
}, },
Callbacks: map[logical.Operation]framework.OperationFunc{ Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.pathLeaseWrite, logical.WriteOperation: b.pathConfigLeaseWrite,
}, },
HelpSynopsis: pathConfigLeaseHelpSyn, HelpSynopsis: pathConfigLeaseHelpSyn,
@@ -33,10 +31,15 @@ func pathConfigLease(b *backend) *framework.Path {
} }
} }
func (b *backend) pathLeaseWrite(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { func (b *backend) pathConfigLeaseWrite(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
log.Printf("Vishal: ssh.pathLeaseWrite\n")
leaseRaw := d.Get("lease").(string) leaseRaw := d.Get("lease").(string)
leaseMaxRaw := d.Get("lease_max").(string) leaseMaxRaw := d.Get("lease_max").(string)
if leaseRaw == "" {
return logical.ErrorResponse("Invalid 'lease'"), nil
}
if leaseMaxRaw == "" {
return logical.ErrorResponse("Invalid 'lease_max'"), nil
}
lease, err := time.ParseDuration(leaseRaw) lease, err := time.ParseDuration(leaseRaw)
if err != nil { if err != nil {
@@ -49,7 +52,6 @@ func (b *backend) pathLeaseWrite(req *logical.Request, d *framework.FieldData) (
"Invalid lease: %s", err)), nil "Invalid lease: %s", err)), nil
} }
// Store it
entry, err := logical.StorageEntryJSON("config/lease", &configLease{ entry, err := logical.StorageEntryJSON("config/lease", &configLease{
Lease: lease, Lease: lease,
LeaseMax: leaseMax, LeaseMax: leaseMax,
@@ -64,11 +66,6 @@ func (b *backend) pathLeaseWrite(req *logical.Request, d *framework.FieldData) (
return nil, nil return nil, nil
} }
type configLease struct {
Lease time.Duration
LeaseMax time.Duration
}
func (b *backend) Lease(s logical.Storage) (*configLease, error) { func (b *backend) Lease(s logical.Storage) (*configLease, error) {
entry, err := s.Get("config/lease") entry, err := s.Get("config/lease")
if err != nil { if err != nil {
@@ -86,12 +83,17 @@ func (b *backend) Lease(s logical.Storage) (*configLease, error) {
return &result, nil return &result, nil
} }
type configLease struct {
Lease time.Duration
LeaseMax time.Duration
}
const pathConfigLeaseHelpSyn = ` const pathConfigLeaseHelpSyn = `
Configure the default lease information for SSH one time keys. Configure the default lease information for SSH one time keys.
` `
const pathConfigLeaseHelpDesc = ` const pathConfigLeaseHelpDesc = `
This configures the default lease information used for SSH one time keys This configures the default lease information used for SSH keys
generated by this backend. The lease specifies the duration that a generated by this backend. The lease specifies the duration that a
credential will be valid for, as well as the maximum session for credential will be valid for, as well as the maximum session for
a set of credentials. a set of credentials.

View File

@@ -1,6 +1,7 @@
package ssh package ssh
import ( import (
"fmt"
"log" "log"
"github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical"
@@ -8,7 +9,6 @@ import (
) )
func pathKeys(b *backend) *framework.Path { func pathKeys(b *backend) *framework.Path {
log.Printf("Vishal: ssh.pathConfigAddHostKey\n")
return &framework.Path{ return &framework.Path{
Pattern: "keys/(?P<name>\\w+)", Pattern: "keys/(?P<name>\\w+)",
Fields: map[string]*framework.FieldSchema{ Fields: map[string]*framework.FieldSchema{
@@ -26,14 +26,13 @@ func pathKeys(b *backend) *framework.Path {
logical.WriteOperation: b.pathKeysWrite, logical.WriteOperation: b.pathKeysWrite,
logical.DeleteOperation: b.pathKeysDelete, logical.DeleteOperation: b.pathKeysDelete,
}, },
HelpSynopsis: pathConfigAddHostKeySyn, HelpSynopsis: pathKeysSyn,
HelpDescription: pathConfigAddHostKeyDesc, HelpDescription: pathKeysDesc,
} }
} }
func (b *backend) pathKeysRead(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { func (b *backend) pathKeysRead(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
keyName := d.Get("name").(string) keyName := d.Get("name").(string)
log.Printf("Vishal: ssh.pathKeysRead: keyName: %#v\n", keyName)
keyPath := "keys/" + keyName keyPath := "keys/" + keyName
entry, err := req.Storage.Get(keyPath) entry, err := req.Storage.Get(keyPath)
if err != nil { if err != nil {
@@ -52,7 +51,6 @@ func (b *backend) pathKeysRead(req *logical.Request, d *framework.FieldData) (*l
func (b *backend) pathKeysDelete(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { func (b *backend) pathKeysDelete(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
keyName := d.Get("name").(string) keyName := d.Get("name").(string)
log.Printf("Vishal: ssh.pathKeysDelete: keyName: %#v\n", keyName)
keyPath := "keys/" + keyName keyPath := "keys/" + keyName
err := req.Storage.Delete(keyPath) err := req.Storage.Delete(keyPath)
if err != nil { if err != nil {
@@ -63,13 +61,16 @@ func (b *backend) pathKeysDelete(req *logical.Request, d *framework.FieldData) (
func (b *backend) pathKeysWrite(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { func (b *backend) pathKeysWrite(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
log.SetFlags(log.LstdFlags | log.Lshortfile) log.SetFlags(log.LstdFlags | log.Lshortfile)
log.Printf("Vishal: ssh.pathKeysWrite\n")
keyName := d.Get("name").(string) keyName := d.Get("name").(string)
keyString := d.Get("key").(string) keyString := d.Get("key").(string)
if keyString == "" {
return nil, fmt.Errorf("Invalid 'key'")
}
keyPath := "keys/" + keyName keyPath := "keys/" + keyName
log.Printf("Vishal: ssh.path_keys.pathKeysWrite: keyPath: %#v\n", keyPath)
entry, err := logical.StorageEntryJSON(keyPath, &sshHostKey{ entry, err := logical.StorageEntryJSON(keyPath, &sshHostKey{
Key: keyString, Key: keyString,
}) })
@@ -86,10 +87,11 @@ type sshHostKey struct {
Key string Key string
} }
const pathConfigAddHostKeySyn = ` const pathKeysSyn = `
pathConfigAddHostKeySyn Register a shared key which can be used to install dynamic key in remote machine.
` `
const pathConfigAddHostKeyDesc = ` const pathKeysDesc = `
pathConfigAddHostKeyDesc The shared key registered will be used to install and uninstall dynamic keys in remote machine.
This key should have "root" privileges which enables installing keys for unprivileged usernames.
` `

View File

@@ -2,7 +2,6 @@ package ssh
import ( import (
"fmt" "fmt"
"log"
"net" "net"
"strings" "strings"
@@ -11,7 +10,6 @@ import (
) )
func pathLookup(b *backend) *framework.Path { func pathLookup(b *backend) *framework.Path {
log.Printf("Vishal: ssh.pathLookup\n")
return &framework.Path{ return &framework.Path{
Pattern: "lookup", Pattern: "lookup",
Fields: map[string]*framework.FieldSchema{ Fields: map[string]*framework.FieldSchema{
@@ -29,20 +27,29 @@ func pathLookup(b *backend) *framework.Path {
} }
func (b *backend) pathLookupWrite(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { func (b *backend) pathLookupWrite(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
ip := d.Get("ip").(string) ipAddr := d.Get("ip").(string)
//ip := "127.0.0.1" if ipAddr == "" {
ipAddr := net.ParseIP(ip) return logical.ErrorResponse("Missing 'ip'"), nil
if ipAddr == nil {
return logical.ErrorResponse(fmt.Sprintf("Invalid IP '%s'", ip)), nil
} }
keys, _ := req.Storage.List("policy/") ip := net.ParseIP(ipAddr)
if ip == nil {
return logical.ErrorResponse(fmt.Sprintf("Invalid IP '%s'", ip.String())), nil
}
keys, err := req.Storage.List("policy/")
if err != nil {
return nil, err
}
if len(keys) == 0 {
return nil, fmt.Errorf("No roles registered for IP '%s'", ip.String())
}
var matchingRoles []string var matchingRoles []string
for _, item := range keys { for _, item := range keys {
if contains, _ := containsIP(req.Storage, item, ip); contains { if contains, _ := containsIP(req.Storage, item, ip.String()); contains {
matchingRoles = append(matchingRoles, item) matchingRoles = append(matchingRoles, item)
} }
} }
log.Printf("Vishal: req.Path: %#v \n Keys:%#v\n", req.Path, keys)
return &logical.Response{ return &logical.Response{
Data: map[string]interface{}{ Data: map[string]interface{}{
"roles": matchingRoles, "roles": matchingRoles,
@@ -51,6 +58,9 @@ func (b *backend) pathLookupWrite(req *logical.Request, d *framework.FieldData)
} }
func containsIP(s logical.Storage, roleName string, ip string) (bool, error) { func containsIP(s logical.Storage, roleName string, ip string) (bool, error) {
if roleName == "" || ip == "" {
return false, fmt.Errorf("invalid parameters")
}
roleEntry, err := s.Get("policy/" + roleName) roleEntry, err := s.Get("policy/" + roleName)
if err != nil { if err != nil {
return false, fmt.Errorf("error retrieving role '%s'", err) return false, fmt.Errorf("error retrieving role '%s'", err)
@@ -64,8 +74,10 @@ func containsIP(s logical.Storage, roleName string, ip string) (bool, error) {
} }
ipMatched := false ipMatched := false
for _, item := range strings.Split(role.CIDR, ",") { for _, item := range strings.Split(role.CIDR, ",") {
log.Println(item) _, cidrIPNet, err := net.ParseCIDR(item)
_, cidrIPNet, _ := net.ParseCIDR(item) if err != nil {
return false, fmt.Errorf(fmt.Sprintf("Invalid cidr entry '%s'", item))
}
ipMatched = cidrIPNet.Contains(net.ParseIP(ip)) ipMatched = cidrIPNet.Contains(net.ParseIP(ip))
if ipMatched { if ipMatched {
break break
@@ -75,9 +87,10 @@ func containsIP(s logical.Storage, roleName string, ip string) (bool, error) {
} }
const pathLookupSyn = ` const pathLookupSyn = `
pathLookupSyn Lists 'roles' that can be used to create a dynamic key.
` `
const pathLookupDesc = ` const pathLookupDesc = `
pathLoookupDesc CIDR blocks will be associated with multiple 'roles'.
This endpoint lists all the 'roles' that are associated with the supplied IP address.
` `

View File

@@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"net" "net"
"strings" "strings"
@@ -13,7 +12,6 @@ import (
) )
func pathRoleCreate(b *backend) *framework.Path { func pathRoleCreate(b *backend) *framework.Path {
log.Printf("Vishal: ssh.sshConnect\n")
return &framework.Path{ return &framework.Path{
Pattern: "creds/(?P<name>\\w+)", Pattern: "creds/(?P<name>\\w+)",
Fields: map[string]*framework.FieldSchema{ Fields: map[string]*framework.FieldSchema{
@@ -33,19 +31,22 @@ func pathRoleCreate(b *backend) *framework.Path {
Callbacks: map[logical.Operation]framework.OperationFunc{ Callbacks: map[logical.Operation]framework.OperationFunc{
logical.WriteOperation: b.pathRoleCreateWrite, logical.WriteOperation: b.pathRoleCreateWrite,
}, },
HelpSynopsis: sshConnectHelpSyn, HelpSynopsis: pathRoleCreateHelpSyn,
HelpDescription: sshConnectHelpDesc, HelpDescription: pathRoleCreateHelpDesc,
} }
} }
func (b *backend) pathRoleCreateWrite( func (b *backend) pathRoleCreateWrite(
req *logical.Request, d *framework.FieldData) (*logical.Response, error) { req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
log.Printf("Vishal: ssh.pathRoleCreateWrite\n")
//fetch the parameters
roleName := d.Get("name").(string) roleName := d.Get("name").(string)
username := d.Get("username").(string) username := d.Get("username").(string)
ipRaw := d.Get("ip").(string) ipRaw := d.Get("ip").(string)
if roleName == "" {
return logical.ErrorResponse("Invalid 'name'"), nil
}
if ipRaw == "" {
return logical.ErrorResponse("Invalid 'ip'"), nil
}
//find the role to be used for installing dynamic key //find the role to be used for installing dynamic key
rolePath := "policy/" + roleName rolePath := "policy/" + roleName
@@ -61,6 +62,10 @@ func (b *backend) pathRoleCreateWrite(
return nil, err return nil, err
} }
if username == "" {
username = role.DefaultUser
}
//validate the IP address //validate the IP address
ipAddr := net.ParseIP(ipRaw) ipAddr := net.ParseIP(ipRaw)
if ipAddr == nil { if ipAddr == nil {
@@ -70,8 +75,10 @@ func (b *backend) pathRoleCreateWrite(
ipMatched := false ipMatched := false
for _, item := range strings.Split(role.CIDR, ",") { for _, item := range strings.Split(role.CIDR, ",") {
log.Println(item) _, cidrIPNet, err := net.ParseCIDR(item)
_, cidrIPNet, _ := net.ParseCIDR(item) if err != nil {
return logical.ErrorResponse(fmt.Sprintf("Invalid cidr entry '%s'", item)), nil
}
ipMatched = cidrIPNet.Contains(ipAddr) ipMatched = cidrIPNet.Contains(ipAddr)
if ipMatched { if ipMatched {
break break
@@ -96,40 +103,30 @@ func (b *backend) pathRoleCreateWrite(
hostKeyFileName := "./vault_ssh_" + username + "_" + ip + "_shared.pem" hostKeyFileName := "./vault_ssh_" + username + "_" + ip + "_shared.pem"
err = ioutil.WriteFile(hostKeyFileName, []byte(hostKey.Key), 0600) err = ioutil.WriteFile(hostKeyFileName, []byte(hostKey.Key), 0600)
otkPrivateKeyFileName := "vault_ssh_" + username + "_" + ip + "_otk.pem" dynamicPrivateKeyFileName := "vault_ssh_" + username + "_" + ip + "_otk.pem"
otkPublicKeyFileName := otkPrivateKeyFileName + ".pub" dynamicPublicKeyFileName := dynamicPrivateKeyFileName + ".pub"
//commands to be run on vault server //delete the temporary files if they are already present
removeFile(otkPrivateKeyFileName) err = removeFile(dynamicPrivateKeyFileName)
removeFile(otkPublicKeyFileName) if err != nil {
return nil, fmt.Errorf(fmt.Sprintf("Error removing dynamic private key file: '%s'", err))
}
err = removeFile(dynamicPublicKeyFileName)
if err != nil {
return nil, fmt.Errorf(fmt.Sprintf("Error removing dynamic private key file: '%s'", err))
}
//generate RSA key pair
dynamicPublicKey, dynamicPrivateKey, _ := generateRSAKeys() dynamicPublicKey, dynamicPrivateKey, _ := generateRSAKeys()
ioutil.WriteFile(otkPrivateKeyFileName, []byte(dynamicPrivateKey), 0600)
ioutil.WriteFile(otkPublicKeyFileName, []byte(dynamicPublicKey), 0644)
uploadFileScp(otkPublicKeyFileName, username, ip, hostKey.Key) //save the public key pair to a file
/* ioutil.WriteFile(dynamicPublicKeyFileName, []byte(dynamicPublicKey), 0644)
otkPublicKeyFileNameBase := filepath.Base(otkPublicKeyFileName)
otkPublicKeyFile, _ := os.Open(otkPublicKeyFileName) //send the public key to target machine
otkPublicKeyStat, err := otkPublicKeyFile.Stat() err = uploadFileScp(dynamicPublicKeyFileName, username, ip, hostKey.Key)
if os.IsNotExist(err) { if err != nil {
return nil, fmt.Errorf("File does not exist") return nil, err
} }
session := createSSHPublicKeysSession(username, ip, hostKey.Key)
if session == nil {
return nil, fmt.Errorf("Invalid session object")
}
go func() {
w, _ := session.StdinPipe()
fmt.Fprintln(w, "C0644", otkPublicKeyStat.Size(), otkPublicKeyFileNameBase)
io.Copy(w, otkPublicKeyFile)
fmt.Fprint(w, "\x00")
w.Close()
}()
if err := session.Run(fmt.Sprintf("scp -vt %s", otkPublicKeyFileNameBase)); err != nil {
panic("Failed to run: " + err.Error())
}
session.Close()
*/
//connect to target machine //connect to target machine
session, err := createSSHPublicKeysSession(username, ip, hostKey.Key) session, err := createSSHPublicKeysSession(username, ip, hostKey.Key)
@@ -142,14 +139,14 @@ func (b *backend) pathRoleCreateWrite(
var buf bytes.Buffer var buf bytes.Buffer
session.Stdout = &buf session.Stdout = &buf
authKeysFileName := "~/.ssh/authorized_keys" authKeysFileName := "/home/" + username + "/.ssh/authorized_keys"
tempKeysFileName := "~/temp_authorized_keys" tempKeysFileName := "/home/" + username + "/temp_authorized_keys"
//commands to be run on target machine //commands to be run on target machine
grepCmd := "grep -vFf " + otkPublicKeyFileName + " " + authKeysFileName + " > " + tempKeysFileName + ";" grepCmd := "grep -vFf " + dynamicPublicKeyFileName + " " + authKeysFileName + " > " + tempKeysFileName + ";"
catCmdRemoveDuplicate := "cat " + tempKeysFileName + " > " + authKeysFileName + ";" catCmdRemoveDuplicate := "cat " + tempKeysFileName + " > " + authKeysFileName + ";"
catCmdAppendNew := "cat " + otkPublicKeyFileName + " >> " + authKeysFileName + ";" catCmdAppendNew := "cat " + dynamicPublicKeyFileName + " >> " + authKeysFileName + ";"
removeCmd := "rm -f " + tempKeysFileName + " " + otkPublicKeyFileName + ";" removeCmd := "rm -f " + tempKeysFileName + " " + dynamicPublicKeyFileName + ";"
remoteCmdString := strings.Join([]string{ remoteCmdString := strings.Join([]string{
grepCmd, grepCmd,
catCmdRemoveDuplicate, catCmdRemoveDuplicate,
@@ -178,10 +175,17 @@ type sshCIDR struct {
CIDR []string CIDR []string
} }
const sshConnectHelpSyn = ` const pathRoleCreateHelpSyn = `
sshConnectionHelpSyn Creates a dynamic key for the target machine.
` `
const sshConnectHelpDesc = ` const pathRoleCreateHelpDesc = `
rshConnectionHelpDesc This path will generates a new key for establishing SSH session with
target host. Previously registered shared key belonging to target
infrastructure will be used to install the new key at the target. If
this backend is mounted at 'ssh', then "ssh/creds/role" would generate
a dynamic key for 'web' role.
The dynamic keys will have a lease associated with them. The access
keys can be revoked by using the lease ID.
` `

View File

@@ -1,14 +1,15 @@
package ssh package ssh
import ( import (
"log" "fmt"
"net"
"strings"
"github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework" "github.com/hashicorp/vault/logical/framework"
) )
func pathRoles(b *backend) *framework.Path { func pathRoles(b *backend) *framework.Path {
log.Printf("Vishal: ssh.pathRoles\n")
return &framework.Path{ return &framework.Path{
Pattern: "roles/(?P<name>\\w+)", Pattern: "roles/(?P<name>\\w+)",
Fields: map[string]*framework.FieldSchema{ Fields: map[string]*framework.FieldSchema{
@@ -46,18 +47,38 @@ func pathRoles(b *backend) *framework.Path {
} }
func (b *backend) pathRoleWrite(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { func (b *backend) pathRoleWrite(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
log.Printf("Vishal: ssh.pathRoleWrite\n")
roleName := d.Get("name").(string) roleName := d.Get("name").(string)
keyName := d.Get("key").(string) keyName := d.Get("key").(string)
adminUser := d.Get("admin_user").(string) adminUser := d.Get("admin_user").(string)
defaultUser := d.Get("default_user").(string) defaultUser := d.Get("default_user").(string)
cidr := d.Get("cidr").(string) cidr := d.Get("cidr").(string)
log.Printf("Vishal: name[%s] key[%s] admin_user[%s] default_user[%s] cidr[%s]\n", roleName, keyName, adminUser, defaultUser, cidr) //input validations
if roleName == "" {
return logical.ErrorResponse("Invalid 'roleName'"), nil
}
if keyName == "" {
return logical.ErrorResponse("Invalid 'key'"), nil
}
if adminUser == "" {
return logical.ErrorResponse("Invalid 'admin_user'"), nil
}
if cidr == "" {
return logical.ErrorResponse("Invalid 'cidr'"), nil
}
for _, item := range strings.Split(cidr, ",") {
_, _, err := net.ParseCIDR(item)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("Invalid cidr entry '%s'", item)), nil
}
}
rolePath := "policy/" + roleName rolePath := "policy/" + roleName
if defaultUser == "" {
defaultUser = adminUser
}
entry, err := logical.StorageEntryJSON(rolePath, sshRole{ entry, err := logical.StorageEntryJSON(rolePath, sshRole{
KeyName: keyName, KeyName: keyName,
AdminUser: adminUser, AdminUser: adminUser,
@@ -69,7 +90,6 @@ func (b *backend) pathRoleWrite(req *logical.Request, d *framework.FieldData) (*
return nil, err return nil, err
} }
log.Printf("Vishal: entryJSON:%s\n", entry.Value)
if err := req.Storage.Put(entry); err != nil { if err := req.Storage.Put(entry); err != nil {
return nil, err return nil, err
} }
@@ -78,7 +98,6 @@ func (b *backend) pathRoleWrite(req *logical.Request, d *framework.FieldData) (*
} }
func (b *backend) pathRoleRead(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { func (b *backend) pathRoleRead(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
log.Printf("Vishal: ssh.pathRoleRead\n")
roleName := d.Get("name").(string) roleName := d.Get("name").(string)
rolePath := "policy/" + roleName rolePath := "policy/" + roleName
entry, err := req.Storage.Get(rolePath) entry, err := req.Storage.Get(rolePath)
@@ -96,7 +115,6 @@ func (b *backend) pathRoleRead(req *logical.Request, d *framework.FieldData) (*l
} }
func (b *backend) pathRoleDelete(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { func (b *backend) pathRoleDelete(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
log.Printf("Vishal: ssh.pathRoleDelete\n")
roleName := d.Get("name").(string) roleName := d.Get("name").(string)
rolePath := "policy/" + roleName rolePath := "policy/" + roleName
err := req.Storage.Delete(rolePath) err := req.Storage.Delete(rolePath)
@@ -114,9 +132,13 @@ type sshRole struct {
} }
const pathRoleHelpSyn = ` const pathRoleHelpSyn = `
Manage the roles that can be created with this backend. Manage the 'roles' that can be created with this backend.
` `
const pathRoleHelpDesc = ` const pathRoleHelpDesc = `
This path lets you manage the roles that can be created with this backend. This path allows you to manage the roles that are used to create dynamic keys.
These roles will be having privileged access to all the hosts mentioned by CIDR blocks.
For example, if the backend is mounted at "ssh" and the role is created at "ssh/roles/web", then a user could request for a new key at "ssh/creds/web" for the supplied username and IP address.
The 'cidr' field takes comma seperated CIDR blocks. The 'admin_user' should have root access in all the hosts represented by the 'cidr' field. When the user requests key for an IP, the key will be installed for the user mentioned by 'default_user' field. The 'key' field takes a named key which can be configured by 'ssh/keys/' endpoint.
` `

View File

@@ -1,10 +1,8 @@
package ssh package ssh
import ( import (
"bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"strings" "strings"
"time" "time"
@@ -15,7 +13,6 @@ import (
const SecretOneTimeKeyType = "secret_one_type_key_type" const SecretOneTimeKeyType = "secret_one_type_key_type"
func secretSshKey(b *backend) *framework.Secret { func secretSshKey(b *backend) *framework.Secret {
log.Printf("Vishal: ssh.secretPrivateKey\n")
return &framework.Secret{ return &framework.Secret{
Type: SecretOneTimeKeyType, Type: SecretOneTimeKeyType,
Fields: map[string]*framework.FieldSchema{ Fields: map[string]*framework.FieldSchema{
@@ -28,15 +25,14 @@ func secretSshKey(b *backend) *framework.Secret {
Description: "ip address of host", Description: "ip address of host",
}, },
}, },
DefaultDuration: 10 * time.Second, //TODO: change this DefaultDuration: 5 * time.Second, //TODO: change this
DefaultGracePeriod: 10 * time.Second, //TODO: change this DefaultGracePeriod: 1 * time.Second, //TODO: change this
Renew: b.secretSshKeyRenew, Renew: b.secretSshKeyRenew,
Revoke: b.secretSshKeyRevoke, Revoke: b.secretSshKeyRevoke,
} }
} }
func (b *backend) secretSshKeyRenew(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { func (b *backend) secretSshKeyRenew(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
log.Printf("Vishal: ssh.secretPrivateKeyRenew\n")
lease, err := b.Lease(req.Storage) lease, err := b.Lease(req.Storage)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -49,8 +45,6 @@ func (b *backend) secretSshKeyRenew(req *logical.Request, d *framework.FieldData
} }
func (b *backend) secretSshKeyRevoke(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { func (b *backend) secretSshKeyRevoke(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
log.Printf("Vishal: ssh.secretPrivateKeyRevoke req: %#v\n", req)
//fetch the values from secret
usernameRaw, ok := req.Secret.InternalData["username"] usernameRaw, ok := req.Secret.InternalData["username"]
if !ok { if !ok {
return nil, fmt.Errorf("secret is missing internal data") return nil, fmt.Errorf("secret is missing internal data")
@@ -83,7 +77,6 @@ func (b *backend) secretSshKeyRevoke(req *logical.Request, d *framework.FieldDat
if !ok { if !ok {
return nil, fmt.Errorf("secret is missing internal data") 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 //fetch the host key using the key name
hostKeyPath := "keys/" + hostKeyName hostKeyPath := "keys/" + hostKeyName
@@ -101,24 +94,23 @@ func (b *backend) secretSshKeyRevoke(req *logical.Request, d *framework.FieldDat
err = ioutil.WriteFile(hostKeyFileName, []byte(hostKey.Key), 0400) err = ioutil.WriteFile(hostKeyFileName, []byte(hostKey.Key), 0400)
//write dynamicPublicKey to file and use it as argument to scp command //write dynamicPublicKey to file and use it as argument to scp command
otkPrivateKeyFileName := "vault_ssh_" + username + "_" + ip + "_otk.pem" dynamicPrivateKeyFileName := "vault_ssh_" + username + "_" + ip + "_otk.pem"
otkPublicKeyFileName := otkPrivateKeyFileName + ".pub" dynamicPublicKeyFileName := dynamicPrivateKeyFileName + ".pub"
err = ioutil.WriteFile(otkPublicKeyFileName, []byte(dynamicPublicKey), 0400) err = ioutil.WriteFile(dynamicPublicKeyFileName, []byte(dynamicPublicKey), 0400)
//transfer the dynamic public key to target machine and use it to remove the entry from authorized_keys file //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 = uploadFileScp(dynamicPublicKeyFileName, username, ip, hostKey.Key)
err = exec_command(scpCmd)
if err != nil { if err != nil {
fmt.Errorf("Running command scp failed " + err.Error()) return nil, fmt.Errorf("Public key transfer failed: %s", err)
} }
authKeysFileName := "~/.ssh/authorized_keys" authKeysFileName := "~/.ssh/authorized_keys"
tempKeysFileName := "~/temp_authorized_keys" tempKeysFileName := "~/temp_authorized_keys"
//commands to be run on target machine //commands to be run on target machine
grepCmd := "grep -vFf " + otkPublicKeyFileName + " " + authKeysFileName + " > " + tempKeysFileName + ";" grepCmd := "grep -vFf " + dynamicPublicKeyFileName + " " + authKeysFileName + " > " + tempKeysFileName + ";"
catCmdRemoveDuplicate := "cat " + tempKeysFileName + " > " + authKeysFileName + ";" catCmdRemoveDuplicate := "cat " + tempKeysFileName + " > " + authKeysFileName + ";"
rmCmd := "rm -f " + tempKeysFileName + " " + otkPublicKeyFileName + ";" rmCmd := "rm -f " + tempKeysFileName + " " + dynamicPublicKeyFileName + ";"
remoteCmdString := strings.Join([]string{ remoteCmdString := strings.Join([]string{
grepCmd, grepCmd,
catCmdRemoveDuplicate, catCmdRemoveDuplicate,
@@ -134,12 +126,10 @@ func (b *backend) secretSshKeyRevoke(req *logical.Request, d *framework.FieldDat
return nil, fmt.Errorf("Invalid session object") return nil, fmt.Errorf("Invalid session object")
} }
var buf bytes.Buffer //run the commands in target machine
session.Stdout = &buf
if err := session.Run(remoteCmdString); err != nil { if err := session.Run(remoteCmdString); err != nil {
return nil, err return nil, err
} }
session.Close()
fmt.Println(buf.String())
return nil, nil return nil, nil
} }

View File

@@ -8,7 +8,6 @@ import (
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"io" "io"
"log"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@@ -16,6 +15,11 @@ import (
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
/*
Executes the command represented by the input.
Multiple commands can be run by concatinating strings with ';'.
Currently, it is supported only for linux platforms and user bash shell.
*/
func exec_command(cmdString string) error { func exec_command(cmdString string) error {
cmd := exec.Command("/bin/bash", "-c", cmdString) cmd := exec.Command("/bin/bash", "-c", cmdString)
if _, err := cmd.Output(); err != nil { if _, err := cmd.Output(); err != nil {
@@ -24,11 +28,16 @@ func exec_command(cmdString string) error {
return nil return nil
} }
/*
Transfers the file to the target machine by establishing an SSH session with the target.
Uses the public key authentication method and hence the parameter 'key' takes in the private key.
The fileName parameter takes an absolute path.
*/
func uploadFileScp(fileName, username, ip, key string) error { func uploadFileScp(fileName, username, ip, key string) error {
nameBase := filepath.Base(fileName) nameBase := filepath.Base(fileName)
file, err := os.Open(fileName) file, err := os.Open(fileName)
if err != nil { if err != nil {
return fmt.Errorf("Unable to open file") return err
} }
stat, err := file.Stat() stat, err := file.Stat()
if os.IsNotExist(err) { if os.IsNotExist(err) {
@@ -36,7 +45,7 @@ func uploadFileScp(fileName, username, ip, key string) error {
} }
session, err := createSSHPublicKeysSession(username, ip, key) session, err := createSSHPublicKeysSession(username, ip, key)
if err != nil { if err != nil {
return fmt.Errorf("Unable to create SSH Session using public keys: %s", err) return err
} }
if session == nil { if session == nil {
return fmt.Errorf("Invalid session object") return fmt.Errorf("Invalid session object")
@@ -50,12 +59,19 @@ func uploadFileScp(fileName, username, ip, key string) error {
w.Close() w.Close()
}() }()
if err := session.Run(fmt.Sprintf("scp -vt %s", nameBase)); err != nil { if err := session.Run(fmt.Sprintf("scp -vt %s", nameBase)); err != nil {
return fmt.Errorf("Failed to run: %s", err) return err
} }
return nil return nil
} }
func createSSHPublicKeysSession(username string, ipAddr string, hostKey string) (*ssh.Session, error) { /*
Creates a SSH session object which can be used to run commands in the target machine.
The session will use public key authentication method with port 22.
*/
func createSSHPublicKeysSession(username, ipAddr, hostKey string) (*ssh.Session, error) {
if username == "" || ipAddr == "" || hostKey == "" {
return nil, fmt.Errorf("Invalid parameters")
}
signer, err := ssh.ParsePrivateKey([]byte(hostKey)) signer, err := ssh.ParsePrivateKey([]byte(hostKey))
if err != nil { if err != nil {
return nil, fmt.Errorf("Parsing Private Key failed: %s", err) return nil, fmt.Errorf("Parsing Private Key failed: %s", err)
@@ -70,7 +86,7 @@ func createSSHPublicKeysSession(username string, ipAddr string, hostKey string)
client, err := ssh.Dial("tcp", ipAddr+":22", config) client, err := ssh.Dial("tcp", ipAddr+":22", config)
if err != nil { if err != nil {
return nil, fmt.Errorf("Dial Failed: %s", err) return nil, err
} }
if client == nil { if client == nil {
return nil, fmt.Errorf("Invalid client object: %s", err) return nil, fmt.Errorf("Invalid client object: %s", err)
@@ -78,35 +94,45 @@ func createSSHPublicKeysSession(username string, ipAddr string, hostKey string)
session, err := client.NewSession() session, err := client.NewSession()
if err != nil { if err != nil {
return nil, fmt.Errorf("Creating new client session failed: %s", err) return nil, err
} }
return session, nil return session, nil
} }
func removeFile(fileName string) { /*
Deletes the file in the current directory.
The parameter is just the name of the file and not a path.
*/
func removeFile(fileName string) error {
if fileName == "" {
return fmt.Errorf("Invalid file name")
}
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
log.Printf("Error fetching working directory:%s", err) return err
return
} }
absFileName := wd + "/" + fileName absFileName := wd + "/" + fileName
if _, err := os.Stat(absFileName); err == nil { if _, err := os.Stat(absFileName); err == nil {
err := os.Remove(absFileName) err := os.Remove(absFileName)
if err != nil { if err != nil {
log.Printf(fmt.Sprintf("Failed: %s", err)) return err
return
} }
} }
return nil
} }
func generateRSAKeys() (string, string, error) { /*
Creates a new RSA key pair with key length of 2048.
The private key will be of pem format and the public key will be of OpenSSH format.
*/
func generateRSAKeys() (publicKeyRsa string, privateKeyRsa string, err error) {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048) privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil { if err != nil {
return "", "", fmt.Errorf("error generating RSA key-pair: %s", err) return "", "", fmt.Errorf("error generating RSA key-pair: %s", err)
} }
privateKeyRsa := string(pem.EncodeToMemory(&pem.Block{ privateKeyRsa = string(pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY", Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(privateKey), Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
})) }))
@@ -115,10 +141,6 @@ func generateRSAKeys() (string, string, error) {
if err != nil { if err != nil {
return "", "", fmt.Errorf("error generating RSA key-pair: %s", err) return "", "", fmt.Errorf("error generating RSA key-pair: %s", err)
} }
publicKeyRsa := "ssh-rsa " + base64.StdEncoding.EncodeToString(sshPublicKey.Marshal()) publicKeyRsa = "ssh-rsa " + base64.StdEncoding.EncodeToString(sshPublicKey.Marshal())
return
//ioutil.WriteFile("testkey.pem", []byte(privateKeyRsa), 0600)
//ioutil.WriteFile("testkey.pub", []byte(publicKeyRsa), 0600)
return publicKeyRsa, privateKeyRsa, nil
} }

View File

@@ -3,7 +3,6 @@ package command
import ( import (
"fmt" "fmt"
"io" "io"
"log"
"os" "os"
"strings" "strings"
@@ -19,7 +18,6 @@ type WriteCommand struct {
} }
func (c *WriteCommand) Run(args []string) int { func (c *WriteCommand) Run(args []string) int {
log.Printf("Vishal: writeCommand\n")
var format string var format string
var force bool var force bool
flags := c.Meta.FlagSet("write", FlagSetDefault) flags := c.Meta.FlagSet("write", FlagSetDefault)
@@ -57,7 +55,6 @@ func (c *WriteCommand) Run(args []string) int {
return 2 return 2
} }
log.Printf("Vishal: write Path: %#v\n", path)
secret, err := client.Logical().Write(path, data) secret, err := client.Logical().Write(path, data)
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf( c.Ui.Error(fmt.Sprintf(

View File

@@ -3,7 +3,6 @@ package http
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
@@ -22,7 +21,6 @@ const AuthHeaderName = "X-Vault-Token"
// its own to mount the Vault API within another web server. // its own to mount the Vault API within another web server.
func Handler(core *vault.Core) http.Handler { func Handler(core *vault.Core) http.Handler {
// Create the muxer to handle the actual endpoints // Create the muxer to handle the actual endpoints
log.Printf("Vishal: http.handler.Handler\n")
mux := http.NewServeMux() mux := http.NewServeMux()
mux.Handle("/v1/sys/init", handleSysInit(core)) mux.Handle("/v1/sys/init", handleSysInit(core))
mux.Handle("/v1/sys/seal-status", handleSysSealStatus(core)) mux.Handle("/v1/sys/seal-status", handleSysSealStatus(core))
@@ -77,7 +75,6 @@ func parseRequest(r *http.Request, out interface{}) error {
// request is a helper to perform a request and properly exit in the // request is a helper to perform a request and properly exit in the
// case of an error. // case of an error.
func request(core *vault.Core, w http.ResponseWriter, rawReq *http.Request, r *logical.Request) (*logical.Response, bool) { func request(core *vault.Core, w http.ResponseWriter, rawReq *http.Request, r *logical.Request) (*logical.Response, bool) {
log.Printf("Vishal: http.handler.request: \n")
resp, err := core.HandleRequest(r) resp, err := core.HandleRequest(r)
if err == vault.ErrStandby { if err == vault.ErrStandby {
respondStandby(core, w, rawReq.URL) respondStandby(core, w, rawReq.URL)

View File

@@ -2,7 +2,6 @@ package http
import ( import (
"io" "io"
"log"
"net" "net"
"net/http" "net/http"
"strings" "strings"
@@ -14,7 +13,6 @@ import (
func handleLogical(core *vault.Core) http.Handler { func handleLogical(core *vault.Core) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("Vishal: handleLogical called\n")
// Determine the path... // Determine the path...
if !strings.HasPrefix(r.URL.Path, "/v1/") { if !strings.HasPrefix(r.URL.Path, "/v1/") {
respondError(w, http.StatusNotFound, nil) respondError(w, http.StatusNotFound, nil)
@@ -59,7 +57,6 @@ func handleLogical(core *vault.Core) http.Handler {
// Make the internal request. We attach the connection info // Make the internal request. We attach the connection info
// as well in case this is an authentication request that requires // as well in case this is an authentication request that requires
// it. Vault core handles stripping this if we need to. // it. Vault core handles stripping this if we need to.
log.Printf("Vishal: http.logical.handleLogical: requesting\n")
resp, ok := request(core, w, r, requestAuth(r, &logical.Request{ resp, ok := request(core, w, r, requestAuth(r, &logical.Request{
Operation: op, Operation: op,
Path: path, Path: path,

View File

@@ -345,7 +345,6 @@ func (c *Core) Shutdown() error {
// HandleRequest is used to handle a new incoming request // HandleRequest is used to handle a new incoming request
func (c *Core) HandleRequest(req *logical.Request) (resp *logical.Response, err error) { func (c *Core) HandleRequest(req *logical.Request) (resp *logical.Response, err error) {
log.Printf("Vishal: vault.core.HandleRequest: req.Path:%#v\n", req.Path)
c.stateLock.RLock() c.stateLock.RLock()
defer c.stateLock.RUnlock() defer c.stateLock.RUnlock()
if c.sealed { if c.sealed {

View File

@@ -4,7 +4,6 @@ import (
"crypto/sha1" "crypto/sha1"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"log"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -150,7 +149,6 @@ func (r *Router) Route(req *logical.Request) (*logical.Response, error) {
r.l.RLock() r.l.RLock()
mount, raw, ok := r.root.LongestPrefix(req.Path) mount, raw, ok := r.root.LongestPrefix(req.Path)
if !ok { if !ok {
log.Printf("Vishal: vault.router.Route: here\n")
// Re-check for a backend by appending a slash. This lets "foo" mean // Re-check for a backend by appending a slash. This lets "foo" mean
// "foo/" at the root level which is almost always what we want. // "foo/" at the root level which is almost always what we want.
req.Path += "/" req.Path += "/"