Merge pull request #3149 from mtb-xt/master

Generate key files for Ansible provisioner
This commit is contained in:
Chris Bednarski 2016-02-04 18:56:21 -08:00
commit 33effdb19b
3 changed files with 141 additions and 31 deletions

View File

@ -16,6 +16,7 @@ import (
amazonchrootbuilder "github.com/mitchellh/packer/builder/amazon/chroot" amazonchrootbuilder "github.com/mitchellh/packer/builder/amazon/chroot"
amazonebsbuilder "github.com/mitchellh/packer/builder/amazon/ebs" amazonebsbuilder "github.com/mitchellh/packer/builder/amazon/ebs"
amazoninstancebuilder "github.com/mitchellh/packer/builder/amazon/instance" amazoninstancebuilder "github.com/mitchellh/packer/builder/amazon/instance"
ansibleprovisioner "github.com/mitchellh/packer/provisioner/ansible"
ansiblelocalprovisioner "github.com/mitchellh/packer/provisioner/ansible-local" ansiblelocalprovisioner "github.com/mitchellh/packer/provisioner/ansible-local"
artificepostprocessor "github.com/mitchellh/packer/post-processor/artifice" artificepostprocessor "github.com/mitchellh/packer/post-processor/artifice"
atlaspostprocessor "github.com/mitchellh/packer/post-processor/atlas" atlaspostprocessor "github.com/mitchellh/packer/post-processor/atlas"
@ -79,6 +80,7 @@ var Builders = map[string]packer.Builder{
var Provisioners = map[string]packer.Provisioner{ var Provisioners = map[string]packer.Provisioner{
"ansible": new(ansibleprovisioner.Provisioner),
"ansible-local": new(ansiblelocalprovisioner.Provisioner), "ansible-local": new(ansiblelocalprovisioner.Provisioner),
"chef-client": new(chefclientprovisioner.Provisioner), "chef-client": new(chefclientprovisioner.Provisioner),
"chef-solo": new(chefsoloprovisioner.Provisioner), "chef-solo": new(chefsoloprovisioner.Provisioner),

View File

@ -3,6 +3,10 @@ package ansible
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -74,12 +78,15 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
errs = packer.MultiErrorAppend(errs, err) 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) err = validateFileConfig(p.config.SSHAuthorizedKeyFile, "ssh_authorized_key_file", true)
if err != nil { if err != nil {
log.Println(p.config.SSHAuthorizedKeyFile, "does not exist")
errs = packer.MultiErrorAppend(errs, err) errs = packer.MultiErrorAppend(errs, err)
} }
}
// Check that the host key file exists, if configured
if len(p.config.SSHHostKeyFile) > 0 { if len(p.config.SSHHostKeyFile) > 0 {
err = validateFileConfig(p.config.SSHHostKeyFile, "ssh_host_key_file", true) err = validateFileConfig(p.config.SSHHostKeyFile, "ssh_host_key_file", true)
if err != nil { if err != nil {
@ -102,17 +109,107 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
return nil 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 { func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
ui.Say("Provisioning with Ansible...") ui.Say("Provisioning with Ansible...")
pubKeyBytes, err := ioutil.ReadFile(p.config.SSHAuthorizedKeyFile) 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 { if err != nil {
return errors.New("Failed to load authorized key file") 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")
} }
public, _, _, _, err := ssh.ParseAuthorizedKey(pubKeyBytes) // 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 { if err != nil {
return errors.New("Failed to parse authorized key") 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
}
k, err := keyFactory(p.config.SSHAuthorizedKeyFile, p.config.SSHHostKeyFile)
if err != nil {
return err
}
// Remove the private key file
if len(k.filename) > 0 {
defer os.Remove(k.filename)
} }
keyChecker := ssh.CertChecker{ keyChecker := ssh.CertChecker{
@ -122,7 +219,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
return nil, errors.New("authentication failed") 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") ui.Say("unauthorized key")
return nil, errors.New("authentication failed") return nil, errors.New("authentication failed")
} }
@ -130,6 +227,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
return nil, nil return nil, nil
}, },
} }
config := &ssh.ServerConfig{ config := &ssh.ServerConfig{
AuthLogCallback: func(conn ssh.ConnMetadata, method string, err error) { 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)) 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, //NoClientAuth: true,
} }
privateBytes, err := ioutil.ReadFile(p.config.SSHHostKeyFile) config.AddHostKey(k.private)
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)
localListener, err := func() (net.Listener, error) { localListener, err := func() (net.Listener, error) {
port, err := strconv.ParseUint(p.config.LocalPort, 10, 16) 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) return fmt.Errorf("Error executing Ansible: %s", err)
} }
@ -229,15 +317,24 @@ func (p *Provisioner) Cancel() {
os.Exit(0) 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) playbook, _ := filepath.Abs(p.config.PlaybookFile)
inventory := p.config.inventoryFile 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...) args = append(args, p.config.ExtraArguments...)
cmd := exec.Command(p.config.Command, args...) 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() stdout, err := cmd.StdoutPipe()
if err != nil { if err != nil {
return err return err

View File

@ -45,12 +45,21 @@ Required Parameters:
- `playbook_file` - The playbook file to be run by Ansible. - `playbook_file` - The playbook file to be run by Ansible.
- `ssh_host_key_file` - The SSH key that will be used to run the SSH server to which Ansible connects.
- `ssh_authorized_key_file` - The SSH public key of the Ansible `ssh_user`.
Optional Parameters: Optional Parameters:
- `ssh_host_key_file` (string) - The SSH key that will be used to run
the SSH server on the host machine to forward commands to the target
machine. Ansible connects to this server and will validate the
identity of the server using the system known_hosts. The default behaviour is
to generate and use a one time key, and disable
host_key_verification in ansible to allow it to connect to the
server
- `ssh_authorized_key_file` (string) - The SSH public key of the
Ansible `ssh_user`. The default behaviour is to generate and use a
one time key. If this file is generated the coorisponding private
key will be passed via the `--private-key` option to Ansible.
- `local_port` (string) - The port on which to attempt to listen for SSH - `local_port` (string) - The port on which to attempt to listen for SSH
connections. This value is a starting point. The provisioner will attempt connections. This value is a starting point. The provisioner will attempt
listen for SSH connections on the first available of ten ports, starting at listen for SSH connections on the first available of ten ports, starting at
@ -58,11 +67,13 @@ Optional Parameters:
listen on a system-chosen port. listen on a system-chosen port.
- `sftp_command` (string) - The command to run on the machine to handle the - `sftp_command` (string) - The command to run on the provisioned machine to handle the
SFTP protocol that Ansible will use to transfer files. The command should SFTP protocol that Ansible will use to transfer files. The command should
read and write on stdin and stdout, respectively. Defaults to read and write on stdin and stdout, respectively. Defaults to
`/usr/lib/sftp-server -e`. `/usr/lib/sftp-server -e`.
- `extra_arguments` (string) - Extra arguments to pass to Ansible
## Limitations ## Limitations
The `ansible` provisioner does not support SCP to transfer files. The `ansible` provisioner does not support SCP to transfer files.