mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-03 03:58:01 +00:00
Vault SSH: Script to install dynamic keys in target
This commit is contained in:
@@ -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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 = `
|
||||||
|
|||||||
27
builtin/logical/ssh/scripts/key-install-linux.sh
Normal file
27
builtin/logical/ssh/scripts/key-install-linux.sh
Normal 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
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user