|
|
|
|
@ -3,6 +3,10 @@ package ansible
|
|
|
|
|
import (
|
|
|
|
|
"bufio"
|
|
|
|
|
"bytes"
|
|
|
|
|
"crypto/rand"
|
|
|
|
|
"crypto/rsa"
|
|
|
|
|
"crypto/x509"
|
|
|
|
|
"encoding/pem"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
@ -74,12 +78,15 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
|
|
|
|
|
errs = packer.MultiErrorAppend(errs, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = validateFileConfig(p.config.SSHAuthorizedKeyFile, "ssh_authorized_key_file", true)
|
|
|
|
|
if err != nil {
|
|
|
|
|
errs = packer.MultiErrorAppend(errs, err)
|
|
|
|
|
// Check that the authorized key file exists ( this should really be called the public key )
|
|
|
|
|
// Check for either file ( if you specify either file you must specify both files )
|
|
|
|
|
if len(p.config.SSHAuthorizedKeyFile) > 0 {
|
|
|
|
|
err = validateFileConfig(p.config.SSHAuthorizedKeyFile, "ssh_authorized_key_file", true)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Println(p.config.SSHAuthorizedKeyFile, "does not exist")
|
|
|
|
|
errs = packer.MultiErrorAppend(errs, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check that the host key file exists, if configured
|
|
|
|
|
if len(p.config.SSHHostKeyFile) > 0 {
|
|
|
|
|
err = validateFileConfig(p.config.SSHHostKeyFile, "ssh_host_key_file", true)
|
|
|
|
|
if err != nil {
|
|
|
|
|
@ -102,17 +109,107 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Keys struct {
|
|
|
|
|
//! This is the public key that we will allow to authenticate
|
|
|
|
|
public ssh.PublicKey
|
|
|
|
|
|
|
|
|
|
//! This is the name of the file of the private key that coorlates
|
|
|
|
|
//the the public key
|
|
|
|
|
filename string
|
|
|
|
|
|
|
|
|
|
//! This is the servers private key ( Set in case the public key
|
|
|
|
|
//is autogenerated )
|
|
|
|
|
private ssh.Signer
|
|
|
|
|
|
|
|
|
|
//! This is the flag to say the server key was generated
|
|
|
|
|
generated bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
|
|
|
|
ui.Say("Provisioning with Ansible...")
|
|
|
|
|
|
|
|
|
|
pubKeyBytes, err := ioutil.ReadFile(p.config.SSHAuthorizedKeyFile)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.New("Failed to load authorized key file")
|
|
|
|
|
keyFactory := func(pubKeyFile string, privKeyFile string) (*Keys, error) {
|
|
|
|
|
var public ssh.PublicKey
|
|
|
|
|
var private ssh.Signer
|
|
|
|
|
var filename string = ""
|
|
|
|
|
var generated bool = false
|
|
|
|
|
|
|
|
|
|
if len(pubKeyFile) > 0 {
|
|
|
|
|
pubKeyBytes, err := ioutil.ReadFile(pubKeyFile)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.New("Failed to read public key")
|
|
|
|
|
}
|
|
|
|
|
public, _, _, _, err = ssh.ParseAuthorizedKey(pubKeyBytes)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.New("Failed to parse authorized key")
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.New("Failed to generate key pair")
|
|
|
|
|
}
|
|
|
|
|
public, err = ssh.NewPublicKey(key.Public())
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.New("Failed to extract public key from generated key pair")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// To support Ansible calling back to us we need to write
|
|
|
|
|
// this file down
|
|
|
|
|
privateKeyDer := x509.MarshalPKCS1PrivateKey(key)
|
|
|
|
|
privateKeyBlock := pem.Block{
|
|
|
|
|
Type: "RSA PRIVATE KEY",
|
|
|
|
|
Headers: nil,
|
|
|
|
|
Bytes: privateKeyDer,
|
|
|
|
|
}
|
|
|
|
|
tf, err := ioutil.TempFile("", "ansible-key")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.New("failed to create temp file for generated key")
|
|
|
|
|
}
|
|
|
|
|
_, err = tf.Write(pem.EncodeToMemory(&privateKeyBlock))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.New("failed to write private key to temp file")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = tf.Close()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.New("failed to close private key temp file")
|
|
|
|
|
}
|
|
|
|
|
filename = tf.Name()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(privKeyFile) > 0 {
|
|
|
|
|
privateBytes, err := ioutil.ReadFile(privKeyFile)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.New("Failed to load private host key")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private, err = ssh.ParsePrivateKey(privateBytes)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.New("Failed to parse private host key")
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.New("Failed to generate server key pair")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private, err = ssh.NewSignerFromKey(key)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.New("Failed to extract private key from generated key pair")
|
|
|
|
|
}
|
|
|
|
|
generated = true
|
|
|
|
|
}
|
|
|
|
|
return &Keys { public, filename, private, generated },nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public, _, _, _, err := ssh.ParseAuthorizedKey(pubKeyBytes)
|
|
|
|
|
k, err := keyFactory(p.config.SSHAuthorizedKeyFile, p.config.SSHHostKeyFile)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.New("Failed to parse authorized key")
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove the private key file
|
|
|
|
|
if len(k.filename) > 0 {
|
|
|
|
|
defer os.Remove(k.filename)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
keyChecker := ssh.CertChecker{
|
|
|
|
|
@ -122,7 +219,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
|
|
|
|
return nil, errors.New("authentication failed")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !bytes.Equal(public.Marshal(), pubKey.Marshal()) {
|
|
|
|
|
if !bytes.Equal(k.public.Marshal(), pubKey.Marshal()) {
|
|
|
|
|
ui.Say("unauthorized key")
|
|
|
|
|
return nil, errors.New("authentication failed")
|
|
|
|
|
}
|
|
|
|
|
@ -130,6 +227,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
|
|
|
|
return nil, nil
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
config := &ssh.ServerConfig{
|
|
|
|
|
AuthLogCallback: func(conn ssh.ConnMetadata, method string, err error) {
|
|
|
|
|
ui.Say(fmt.Sprintf("authentication attempt from %s to %s as %s using %s", conn.RemoteAddr(), conn.LocalAddr(), conn.User(), method))
|
|
|
|
|
@ -138,17 +236,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
|
|
|
|
//NoClientAuth: true,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
privateBytes, err := ioutil.ReadFile(p.config.SSHHostKeyFile)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.New("Failed to load private host key")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private, err := ssh.ParsePrivateKey(privateBytes)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return errors.New("Failed to parse private host key")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
config.AddHostKey(private)
|
|
|
|
|
config.AddHostKey(k.private)
|
|
|
|
|
|
|
|
|
|
localListener, err := func() (net.Listener, error) {
|
|
|
|
|
port, err := strconv.ParseUint(p.config.LocalPort, 10, 16)
|
|
|
|
|
@ -211,7 +299,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := p.executeAnsible(ui); err != nil {
|
|
|
|
|
if err := p.executeAnsible(ui, comm, k.filename, k.generated); err != nil {
|
|
|
|
|
return fmt.Errorf("Error executing Ansible: %s", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -229,15 +317,24 @@ func (p *Provisioner) Cancel() {
|
|
|
|
|
os.Exit(0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (p *Provisioner) executeAnsible(ui packer.Ui) error {
|
|
|
|
|
func (p *Provisioner) executeAnsible(ui packer.Ui, comm packer.Communicator, authToken string, generated bool) error {
|
|
|
|
|
playbook, _ := filepath.Abs(p.config.PlaybookFile)
|
|
|
|
|
inventory := p.config.inventoryFile
|
|
|
|
|
|
|
|
|
|
args := []string{playbook, "-i", inventory}
|
|
|
|
|
args := []string{playbook, "-i", inventory }
|
|
|
|
|
if len(authToken) > 0 {
|
|
|
|
|
args = append(args,"--private-key",authToken)
|
|
|
|
|
}
|
|
|
|
|
args = append(args, p.config.ExtraArguments...)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cmd := exec.Command(p.config.Command, args...)
|
|
|
|
|
|
|
|
|
|
// If we have autogenerated the key files turn off host key checking
|
|
|
|
|
if generated {
|
|
|
|
|
cmd.Env = os.Environ()
|
|
|
|
|
cmd.Env = append(cmd.Env,"ANSIBLE_HOST_KEY_CHECKING=False")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stdout, err := cmd.StdoutPipe()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
|