Skip to content
This repository has been archived by the owner on Sep 9, 2020. It is now read-only.

Commit

Permalink
cleanup ansible provisioner key generation
Browse files Browse the repository at this point in the history
* Clearly separate host signer and user key generation into separate
  functions and data structures.

* Remove inaccurate comment about needing to specify both files if
  either one is specified.

* Rename parameters for clarity according to their meaning to the
  callee.

* Style the code with gofmt.
  • Loading branch information
bhcleek committed Feb 10, 2016
1 parent 86781c8 commit a23610e
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 118 deletions.
206 changes: 103 additions & 103 deletions provisioner/ansible/provisioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
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 )
// Check that the authorized key file exists
if len(p.config.SSHAuthorizedKeyFile) > 0 {
err = validateFileConfig(p.config.SSHAuthorizedKeyFile, "ssh_authorized_key_file", true)
if err != nil {
Expand Down Expand Up @@ -109,107 +108,18 @@ 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...")

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
}

k, err := keyFactory(p.config.SSHAuthorizedKeyFile, p.config.SSHHostKeyFile)
k, err := newUserKey(p.config.SSHAuthorizedKeyFile)
if err != nil {
return err
}

hostSigner, err := newSigner(p.config.SSHHostKeyFile)
// Remove the private key file
if len(k.filename) > 0 {
defer os.Remove(k.filename)
if len(k.privKeyFile) > 0 {
defer os.Remove(k.privKeyFile)
}

keyChecker := ssh.CertChecker{
Expand All @@ -219,7 +129,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
return nil, errors.New("authentication failed")
}

if !bytes.Equal(k.public.Marshal(), pubKey.Marshal()) {
if !bytes.Equal(k.Marshal(), pubKey.Marshal()) {
ui.Say("unauthorized key")
return nil, errors.New("authentication failed")
}
Expand All @@ -236,7 +146,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
//NoClientAuth: true,
}

config.AddHostKey(k.private)
config.AddHostKey(hostSigner)

localListener, err := func() (net.Listener, error) {
port, err := strconv.ParseUint(p.config.LocalPort, 10, 16)
Expand Down Expand Up @@ -299,7 +209,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
}()
}

if err := p.executeAnsible(ui, comm, k.filename, k.generated); err != nil {
if err := p.executeAnsible(ui, comm, k.privKeyFile, !hostSigner.generated); err != nil {
return fmt.Errorf("Error executing Ansible: %s", err)
}

Expand All @@ -317,20 +227,19 @@ func (p *Provisioner) Cancel() {
os.Exit(0)
}

func (p *Provisioner) executeAnsible(ui packer.Ui, comm packer.Communicator, authToken string, generated bool) error {
func (p *Provisioner) executeAnsible(ui packer.Ui, comm packer.Communicator, privKeyFile string, checkHostKey bool) error {
playbook, _ := filepath.Abs(p.config.PlaybookFile)
inventory := p.config.inventoryFile

args := []string{playbook, "-i", inventory}
if len(authToken) > 0 {
args = append(args, "--private-key", authToken)
if len(privKeyFile) > 0 {
args = append(args, "--private-key", privKeyFile)
}
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 {
if !checkHostKey {
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "ANSIBLE_HOST_KEY_CHECKING=False")
}
Expand Down Expand Up @@ -385,6 +294,97 @@ func validateFileConfig(name string, config string, req bool) error {
return nil
}

type userKey struct {
ssh.PublicKey
privKeyFile string
}

func newUserKey(pubKeyFile string) (*userKey, error) {
userKey := new(userKey)
if len(pubKeyFile) > 0 {
pubKeyBytes, err := ioutil.ReadFile(pubKeyFile)
if err != nil {
return nil, errors.New("Failed to read public key")
}
userKey.PublicKey, _, _, _, err = ssh.ParseAuthorizedKey(pubKeyBytes)
if err != nil {
return nil, errors.New("Failed to parse authorized key")
}

return userKey, nil
}

key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, errors.New("Failed to generate key pair")
}
userKey.PublicKey, 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")
}
userKey.privKeyFile = tf.Name()

return userKey, nil
}

type signer struct {
ssh.Signer
generated bool
}

func newSigner(privKeyFile string) (*signer, error) {
signer := new(signer)

if len(privKeyFile) > 0 {
privateBytes, err := ioutil.ReadFile(privKeyFile)
if err != nil {
return nil, errors.New("Failed to load private host key")
}

signer.Signer, err = ssh.ParsePrivateKey(privateBytes)
if err != nil {
return nil, errors.New("Failed to parse private host key")
}

return signer, nil
}

key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, errors.New("Failed to generate server key pair")
}

signer.Signer, err = ssh.NewSignerFromKey(key)
if err != nil {
return nil, errors.New("Failed to extract private key from generated key pair")
}
signer.generated = true

return signer, nil
}

// Ui provides concurrency-safe access to packer.Ui.
type Ui struct {
sem chan int
Expand Down
29 changes: 14 additions & 15 deletions website/source/docs/provisioners/ansible.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,17 @@ Required 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.
- `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
connections. This value is a starting point. The provisioner will attempt
Expand All @@ -67,9 +66,9 @@ Optional Parameters:
listen on a system-chosen port.


- `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
read and write on stdin and stdout, respectively. Defaults to
- `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 read and write on stdin and stdout, respectively. Defaults to
`/usr/lib/sftp-server -e`.

- `extra_arguments` (string) - Extra arguments to pass to Ansible
Expand Down

0 comments on commit a23610e

Please sign in to comment.