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

View File

@@ -50,6 +50,10 @@ func pathRoles(b *backend) *framework.Path {
Type: framework.TypeString,
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{
@@ -120,6 +124,11 @@ func (b *backend) pathRoleWrite(req *logical.Request, d *framework.FieldData) (*
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)
if adminUser == "" {
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{
KeyName: keyName,
AdminUser: adminUser,
DefaultUser: defaultUser,
CIDR: cidr,
Port: port,
KeyType: KeyTypeDynamic,
KeyBits: keyBits,
KeyName: keyName,
AdminUser: adminUser,
DefaultUser: defaultUser,
CIDR: cidr,
Port: port,
KeyType: KeyTypeDynamic,
KeyBits: keyBits,
InstallScript: installScript,
})
} else {
return logical.ErrorResponse("Invalid key type"), nil
@@ -212,13 +222,14 @@ func (b *backend) pathRoleDelete(req *logical.Request, d *framework.FieldData) (
}
type sshRole struct {
KeyType string `mapstructure:"key_type" json:"key_type"`
KeyName string `mapstructure:"key" json:"key"`
KeyBits string `mapstructure:"key_bits" json:"key_bits"`
AdminUser string `mapstructure:"admin_user" json:"admin_user"`
DefaultUser string `mapstructure:"default_user" json:"default_user"`
CIDR string `mapstructure:"cidr" json:"cidr"`
Port string `mapstructure:"port" json:"port"`
KeyType string `mapstructure:"key_type" json:"key_type"`
KeyName string `mapstructure:"key" json:"key"`
KeyBits string `mapstructure:"key_bits" json:"key_bits"`
AdminUser string `mapstructure:"admin_user" json:"admin_user"`
DefaultUser string `mapstructure:"default_user" json:"default_user"`
CIDR string `mapstructure:"cidr" json:"cidr"`
Port string `mapstructure:"port" json:"port"`
InstallScript string `mapstructure:"install_script" json:"install_script"`
}
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
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
@@ -10,9 +11,11 @@ import (
"io"
"net"
"strings"
"time"
"github.com/hashicorp/vault/logical"
commssh "github.com/mitchellh/packer/communicator/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
// folder to ~/.ssh/authorized_keys file
func installPublicKeyInTarget(adminUser, publicKeyFileName, username, ip, port, hostKey string) error {
session, err := createSSHPublicKeysSession(adminUser, ip, port, hostKey)
func installPublicKeyInTarget(adminUser, publicKeyFileName, username, ip, port, hostkey string) error {
session, err := createSSHPublicKeysSession(adminUser, ip, port, hostkey)
if err != nil {
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()
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
grepCmd := fmt.Sprintf("grep -vFf %s %s > %s", publicKeyFileName, authKeysFileName, tempKeysFileName)
catCmdRemoveDuplicate := fmt.Sprintf("cat %s > %s", tempKeysFileName, authKeysFileName)
catCmdAppendNew := fmt.Sprintf("cat %s >> %s", publicKeyFileName, authKeysFileName)
removeCmd := fmt.Sprintf("rm -f %s %s", tempKeysFileName, publicKeyFileName)
// Give execute permissions to install script, run and delete it.
scriptFileName := publicKeyFileName + ".sh"
chmodCmd := fmt.Sprintf("chmod +x %s", scriptFileName)
scriptCmd := fmt.Sprintf("./%s %s %s", scriptFileName, publicKeyFileName, authKeysFileName)
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)
return nil
}
@@ -198,3 +200,39 @@ func cidrContainsIP(ip, cidr string) (bool, error) {
}
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
}