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