Vault SSH: Script to install dynamic keys in target

This commit is contained in:
vishalnayak
2015-08-06 14:48:19 -04:00
parent 607732261b
commit c26782acad
5 changed files with 132 additions and 40 deletions

View File

@@ -2,6 +2,7 @@ package ssh
import ( import (
"fmt" "fmt"
"io/ioutil"
"os/user" "os/user"
"strings" "strings"
"testing" "testing"
@@ -57,13 +58,14 @@ var testOTP string
var testPort string var testPort string
var testUserName string var testUserName string
var testAdminUser string var testAdminUser string
var testInstallScript string
// Starts the server and initializes the servers IP address, // Starts the server and initializes the servers IP address,
// port and usernames to be used by the test cases. // port and usernames to be used by the test cases.
func init() { func init() {
addr, err := vault.StartTestServer() addr, err := vault.StartTestServer()
if err != nil { if err != nil {
panic(fmt.Sprintf("Error starting mock server:%s", err)) panic(fmt.Sprintf("error starting mock server:%s", err))
} }
input := strings.Split(addr, ":") input := strings.Split(addr, ":")
testIP = input[0] testIP = input[0]
@@ -71,10 +73,16 @@ func init() {
u, err := user.Current() u, err := user.Current()
if err != nil { if err != nil {
panic(fmt.Sprintf("Error getting current username: '%s'", err)) panic(fmt.Sprintf("error getting current username: '%s'", err))
} }
testUserName = u.Username testUserName = u.Username
testAdminUser = u.Username testAdminUser = u.Username
scriptBytes, err := ioutil.ReadFile("scripts/key-install-linux.sh")
if err != nil {
panic(fmt.Sprintf("error reading install script file: '%s'", err))
}
testInstallScript = string(scriptBytes)
} }
func TestSSHBackend_Lookup(t *testing.T) { func TestSSHBackend_Lookup(t *testing.T) {
@@ -87,10 +95,11 @@ func TestSSHBackend_Lookup(t *testing.T) {
"cidr": testCidr, "cidr": testCidr,
} }
dynamicData := map[string]interface{}{ dynamicData := map[string]interface{}{
"key_type": testDynamicKeyType, "key_type": testDynamicKeyType,
"key": testKeyName, "key": testKeyName,
"admin_user": testAdminUser, "admin_user": testAdminUser,
"cidr": testCidr, "cidr": testCidr,
"install_script": testInstallScript,
} }
logicaltest.Test(t, logicaltest.TestCase{ logicaltest.Test(t, logicaltest.TestCase{
Factory: Factory, Factory: Factory,
@@ -139,10 +148,11 @@ func TestSSHBackend_OTPRoleCrud(t *testing.T) {
func TestSSHBackend_DynamicRoleCrud(t *testing.T) { func TestSSHBackend_DynamicRoleCrud(t *testing.T) {
data := map[string]interface{}{ data := map[string]interface{}{
"key_type": testDynamicKeyType, "key_type": testDynamicKeyType,
"key": testKeyName, "key": testKeyName,
"admin_user": testAdminUser, "admin_user": testAdminUser,
"cidr": testCidr, "cidr": testCidr,
"install_script": testInstallScript,
} }
logicaltest.Test(t, logicaltest.TestCase{ logicaltest.Test(t, logicaltest.TestCase{
Factory: Factory, Factory: Factory,
@@ -318,11 +328,12 @@ func testNewDynamicKeyRole(t *testing.T) logicaltest.TestStep {
Operation: logical.WriteOperation, Operation: logical.WriteOperation,
Path: fmt.Sprintf("roles/%s", testDynamicRoleName), Path: fmt.Sprintf("roles/%s", testDynamicRoleName),
Data: map[string]interface{}{ Data: map[string]interface{}{
"key_type": "dynamic", "key_type": "dynamic",
"key": testKeyName, "key": testKeyName,
"admin_user": testAdminUser, "admin_user": testAdminUser,
"cidr": testCidr, "cidr": testCidr,
"port": testPort, "port": testPort,
"install_script": testInstallScript,
}, },
} }
} }

View File

@@ -165,9 +165,14 @@ func (b *backend) GenerateDynamicCredential(req *logical.Request, role *sshRole,
// Transfer the public key to target machine // Transfer the public key to target machine
publicKeyFileName := uuid.GenerateUUID() publicKeyFileName := uuid.GenerateUUID()
err = uploadPublicKeyScp(dynamicPublicKey, publicKeyFileName, username, ip, role.Port, hostKey.Key) scriptFileName := publicKeyFileName + ".sh"
err = scpUpload(role.AdminUser, ip, role.Port, hostKey.Key, publicKeyFileName, dynamicPublicKey)
if err != nil { if err != nil {
return "", "", err return "", "", fmt.Errorf("error uploading public key: %s", err)
}
err = scpUpload(role.AdminUser, ip, role.Port, hostKey.Key, scriptFileName, role.InstallScript)
if err != nil {
return "", "", fmt.Errorf("error uploading install script: %s", err)
} }
// Add the public key to authorized_keys file in target machine // Add the public key to authorized_keys file in target machine

View File

@@ -50,6 +50,10 @@ func pathRoles(b *backend) *framework.Path {
Type: framework.TypeString, Type: framework.TypeString,
Description: "number of bits in keys", Description: "number of bits in keys",
}, },
"install_script": &framework.FieldSchema{
Type: framework.TypeString,
Description: "script that installs public key in target",
},
}, },
Callbacks: map[logical.Operation]framework.OperationFunc{ Callbacks: map[logical.Operation]framework.OperationFunc{
@@ -120,6 +124,11 @@ func (b *backend) pathRoleWrite(req *logical.Request, d *framework.FieldData) (*
return logical.ErrorResponse(fmt.Sprintf("Invalid 'key': '%s'", keyName)), nil return logical.ErrorResponse(fmt.Sprintf("Invalid 'key': '%s'", keyName)), nil
} }
installScript := d.Get("install_script").(string)
if installScript == "" {
return logical.ErrorResponse("Missing install script"), nil
}
adminUser := d.Get("admin_user").(string) adminUser := d.Get("admin_user").(string)
if adminUser == "" { if adminUser == "" {
return logical.ErrorResponse("Missing admin username"), nil return logical.ErrorResponse("Missing admin username"), nil
@@ -142,13 +151,14 @@ func (b *backend) pathRoleWrite(req *logical.Request, d *framework.FieldData) (*
} }
entry, err = logical.StorageEntryJSON(fmt.Sprintf("policy/%s", roleName), sshRole{ entry, err = logical.StorageEntryJSON(fmt.Sprintf("policy/%s", roleName), sshRole{
KeyName: keyName, KeyName: keyName,
AdminUser: adminUser, AdminUser: adminUser,
DefaultUser: defaultUser, DefaultUser: defaultUser,
CIDR: cidr, CIDR: cidr,
Port: port, Port: port,
KeyType: KeyTypeDynamic, KeyType: KeyTypeDynamic,
KeyBits: keyBits, KeyBits: keyBits,
InstallScript: installScript,
}) })
} else { } else {
return logical.ErrorResponse("Invalid key type"), nil return logical.ErrorResponse("Invalid key type"), nil
@@ -212,13 +222,14 @@ func (b *backend) pathRoleDelete(req *logical.Request, d *framework.FieldData) (
} }
type sshRole struct { type sshRole struct {
KeyType string `mapstructure:"key_type" json:"key_type"` KeyType string `mapstructure:"key_type" json:"key_type"`
KeyName string `mapstructure:"key" json:"key"` KeyName string `mapstructure:"key" json:"key"`
KeyBits string `mapstructure:"key_bits" json:"key_bits"` KeyBits string `mapstructure:"key_bits" json:"key_bits"`
AdminUser string `mapstructure:"admin_user" json:"admin_user"` AdminUser string `mapstructure:"admin_user" json:"admin_user"`
DefaultUser string `mapstructure:"default_user" json:"default_user"` DefaultUser string `mapstructure:"default_user" json:"default_user"`
CIDR string `mapstructure:"cidr" json:"cidr"` CIDR string `mapstructure:"cidr" json:"cidr"`
Port string `mapstructure:"port" json:"port"` Port string `mapstructure:"port" json:"port"`
InstallScript string `mapstructure:"install_script" json:"install_script"`
} }
const pathRoleHelpSyn = ` const pathRoleHelpSyn = `

View File

@@ -0,0 +1,27 @@
#!/bin/bash
#
# This script file adds a RSA public key to the authoried_keys file in a typical
# linux machine. This script should be registered with vault server while creating
# a role for key type 'dynamic'.
#
# Vault server runs this script on the target machine with the following params:
# $1: File containing public key to be installed. Vault server uses UUID as file
# name to avoid collisions with public keys generated for requests.
#
# $2: Absolute path of the authorized_keys file.
#
# [Note: Modify the script if targt machine does not have the commands used in
# this script]
# If the key being installed is already present in the authorized_keys file, it is
# removed and the result is stored in a temporary file.
grep -vFf $1 $2 > temp_$1
# Contents of temporary file will be the contents of authorized_keys file.
cat temp_$1 > $2
# New public key is appended to authorized_keys file
cat $1 >> $2
# Auxiliary files are deleted
rm -f $1 temp_$1

View File

@@ -1,6 +1,7 @@
package ssh package ssh
import ( import (
"bytes"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
@@ -10,9 +11,11 @@ import (
"io" "io"
"net" "net"
"strings" "strings"
"time"
"github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical"
commssh "github.com/mitchellh/packer/communicator/ssh"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@@ -104,8 +107,8 @@ func generateRSAKeys(keyBits int) (publicKeyRsa string, privateKeyRsa string, er
// Concatenates the public present in that target machine's home // Concatenates the public present in that target machine's home
// folder to ~/.ssh/authorized_keys file // folder to ~/.ssh/authorized_keys file
func installPublicKeyInTarget(adminUser, publicKeyFileName, username, ip, port, hostKey string) error { func installPublicKeyInTarget(adminUser, publicKeyFileName, username, ip, port, hostkey string) error {
session, err := createSSHPublicKeysSession(adminUser, ip, port, hostKey) session, err := createSSHPublicKeysSession(adminUser, ip, port, hostkey)
if err != nil { if err != nil {
return fmt.Errorf("unable to create SSH Session using public keys: %s", err) return fmt.Errorf("unable to create SSH Session using public keys: %s", err)
} }
@@ -115,15 +118,14 @@ func installPublicKeyInTarget(adminUser, publicKeyFileName, username, ip, port,
defer session.Close() defer session.Close()
authKeysFileName := fmt.Sprintf("/home/%s/.ssh/authorized_keys", username) authKeysFileName := fmt.Sprintf("/home/%s/.ssh/authorized_keys", username)
tempKeysFileName := fmt.Sprintf("/home/%s/temp_authorized_keys", username)
// Commands to be run on target machine // Give execute permissions to install script, run and delete it.
grepCmd := fmt.Sprintf("grep -vFf %s %s > %s", publicKeyFileName, authKeysFileName, tempKeysFileName) scriptFileName := publicKeyFileName + ".sh"
catCmdRemoveDuplicate := fmt.Sprintf("cat %s > %s", tempKeysFileName, authKeysFileName) chmodCmd := fmt.Sprintf("chmod +x %s", scriptFileName)
catCmdAppendNew := fmt.Sprintf("cat %s >> %s", publicKeyFileName, authKeysFileName) scriptCmd := fmt.Sprintf("./%s %s %s", scriptFileName, publicKeyFileName, authKeysFileName)
removeCmd := fmt.Sprintf("rm -f %s %s", tempKeysFileName, publicKeyFileName) rmCmd := fmt.Sprintf("rm -f %s", scriptFileName)
targetCmd := fmt.Sprintf("%s;%s;%s", chmodCmd, scriptCmd, rmCmd)
targetCmd := fmt.Sprintf("%s;%s;%s;%s", grepCmd, catCmdRemoveDuplicate, catCmdAppendNew, removeCmd)
session.Run(targetCmd) session.Run(targetCmd)
return nil return nil
} }
@@ -198,3 +200,39 @@ func cidrContainsIP(ip, cidr string) (bool, error) {
} }
return false, nil return false, nil
} }
func scpUpload(username, ip, port, hostkey, fileName, fileContent string) error {
signer, err := ssh.ParsePrivateKey([]byte(hostkey))
clientConfig := &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
}
connfunc := func() (net.Conn, error) {
c, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", ip, port), 15*time.Second)
if err != nil {
return nil, err
}
if tcpConn, ok := c.(*net.TCPConn); ok {
tcpConn.SetKeepAlive(true)
tcpConn.SetKeepAlivePeriod(5 * time.Second)
}
return c, nil
}
config := &commssh.Config{
SSHConfig: clientConfig,
Connection: connfunc,
Pty: false,
DisableAgent: true,
}
comm, err := commssh.New(fmt.Sprintf("%s:%s", ip, port), config)
if err != nil {
return fmt.Errorf("error connecting to target: %s", err)
}
comm.Upload(fileName, bytes.NewBufferString(fileContent), nil)
return nil
}