From 0c9b576c0520f491cfb7532e8e784394ff881942 Mon Sep 17 00:00:00 2001 From: Jasper Siepkes Date: Thu, 29 Dec 2016 13:24:56 +0100 Subject: [PATCH] * Unentagled SSH communication with VM's from the Cloud API SSH private key. * Improved documentation. --- builder/triton/builder.go | 15 ++-- builder/triton/ssh.go | 78 +++++++++++++++++---- website/source/docs/builders/triton.html.md | 41 ++++++++--- 3 files changed, 103 insertions(+), 31 deletions(-) diff --git a/builder/triton/builder.go b/builder/triton/builder.go index 6dc851de3..b8195fc1c 100644 --- a/builder/triton/builder.go +++ b/builder/triton/builder.go @@ -31,16 +31,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs = multierror.Append(errs, err) } - // In Triton only the root user is setup in a VM. - b.config.Comm.SSHUsername = "root" - errs = multierror.Append(errs, b.config.AccessConfig.Prepare(&b.config.ctx)...) errs = multierror.Append(errs, b.config.SourceMachineConfig.Prepare(&b.config.ctx)...) errs = multierror.Append(errs, b.config.Comm.Prepare(&b.config.ctx)...) errs = multierror.Append(errs, b.config.TargetImageConfig.Prepare(&b.config.ctx)...) - b.config.Comm.SSHPrivateKey = b.config.KeyMaterial - return nil, errs.ErrorOrNil() } @@ -61,9 +56,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe steps := []multistep.Step{ &StepCreateSourceMachine{}, &communicator.StepConnect{ - Config: &config.Comm, - Host: commHost, - SSHConfig: sshConfig, + Config: &config.Comm, + Host: commHost, + SSHConfig: sshConfig( + b.config.Comm.SSHAgentAuth, + b.config.Comm.SSHUsername, + b.config.Comm.SSHPrivateKey, + b.config.Comm.SSHPassword), }, &common.StepProvision{}, &StepStopMachine{}, diff --git a/builder/triton/ssh.go b/builder/triton/ssh.go index 8b3e9ae7b..ccaceffb7 100644 --- a/builder/triton/ssh.go +++ b/builder/triton/ssh.go @@ -4,7 +4,13 @@ import ( "fmt" "github.com/mitchellh/multistep" + packerssh "github.com/mitchellh/packer/communicator/ssh" "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" + "io/ioutil" + "log" + "net" + "os" ) func commHost(state multistep.StateBag) (string, error) { @@ -19,18 +25,64 @@ func commHost(state multistep.StateBag) (string, error) { return machine, nil } -func sshConfig(state multistep.StateBag) (*ssh.ClientConfig, error) { - config := state.Get("config").(Config) +// SSHConfig returns a function that can be used for the SSH communicator +// config for connecting to the instance created over SSH using the private key +// or password. +func sshConfig(useAgent bool, username, privateKeyPath, password string) func(multistep.StateBag) (*ssh.ClientConfig, error) { + return func(state multistep.StateBag) (*ssh.ClientConfig, error) { - signer, err := ssh.ParsePrivateKey([]byte(config.Comm.SSHPrivateKey)) - if err != nil { - return nil, fmt.Errorf("Error setting up SSH config: %s", err) - } + if useAgent { + log.Println("Configuring SSH agent.") - return &ssh.ClientConfig{ - User: config.Comm.SSHUsername, - Auth: []ssh.AuthMethod{ - ssh.PublicKeys(signer), - }, - }, nil -} + authSock := os.Getenv("SSH_AUTH_SOCK") + if authSock == "" { + return nil, fmt.Errorf("SSH_AUTH_SOCK is not set") + } + + sshAgent, err := net.Dial("unix", authSock) + if err != nil { + return nil, fmt.Errorf("Cannot connect to SSH Agent socket %q: %s", authSock, err) + } + + return &ssh.ClientConfig{ + User: username, + Auth: []ssh.AuthMethod{ + ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers), + }, + }, nil + } + + hasKey := privateKeyPath != "" + + if hasKey { + log.Printf("Configuring SSH private key '%s'.", privateKeyPath) + + privateKeyBytes, err := ioutil.ReadFile(privateKeyPath) + if err != nil { + return nil, fmt.Errorf("Unable to read SSH private key: %s", err) + } + + signer, err := ssh.ParsePrivateKey(privateKeyBytes) + if err != nil { + return nil, fmt.Errorf("Error setting up SSH config: %s", err) + } + + return &ssh.ClientConfig{ + User: username, + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(signer), + }, + }, nil + } else { + log.Println("Configuring SSH keyboard interactive.") + + return &ssh.ClientConfig{ + User: username, + Auth: []ssh.AuthMethod{ + ssh.Password(password), + ssh.KeyboardInteractive( + packerssh.PasswordKeyboardInteractive(password)), + }}, nil + } + } +} \ No newline at end of file diff --git a/website/source/docs/builders/triton.html.md b/website/source/docs/builders/triton.html.md index 21c50c48f..301cfb795 100644 --- a/website/source/docs/builders/triton.html.md +++ b/website/source/docs/builders/triton.html.md @@ -18,12 +18,16 @@ Type: `triton` The `triton` Packer builder is able to create new images for use with Triton. These images can be used with both the [Joyent public cloud](https://www.joyent.com/) (which is powered by Triton) as well with -private [Triton](https://github.com/joyent/triton) installations. This builder -uses the Triton Cloud API to create images. The builder creates and launches a -temporary VM based on a specified source image, runs any provisioning necessary, -uses the Triton "VM to image" functionality to create a reusable image and -finally destroys the temporary VM. This reusable image can then be used to -launch new VM's. +private [Triton](https://github.com/joyent/triton) installations. + +This builder uses the Triton Cloud API to create these images. Triton also +supports the Docker API however this builder does *not*. If you want to create +Docker images on Triton you should use the Packer Docker builder. + +The builder creates and launches a temporary VM based on a specified source +image, runs any provisioning necessary, uses the Triton "VM to image" +functionality to create a reusable image and finally destroys the temporary VM. +This reusable image can then be used to launch new VM's. The builder does *not* manage images. Once it creates an image, it is up to you to use it or delete it. @@ -47,9 +51,16 @@ builder. of `triton_key_id` is stored. For example `~/.ssh/id_rsa`. - `source_machine_image` (string) - The UUID of the image to base the new - image on. On the Joyent public cloud this could for example be + image on. Triton supports multiple types of images, called 'brands' in + Triton / Joyent lingo, for contains and VM's. See the chapter [Containers + and virtual machines](https://docs.joyent.com/public-cloud/instances) in the + Joyent Triton documentation for detailed information. The following brands + are currently supported by this builder:`joyent` and`kvm`. The choice of + base image automatically decides the brand. On the Joyent public cloud a + valid `source_machine_image` could for example be `70e3ae72-96b6-11e6-9056-9737fd4d0764` for version 16.3.1 of the 64bit - SmartOS base image. + SmartOS base image (a 'joyent' brand image). + - `source_machine_package` (string) - The Triton package to use while building the image. Does not affect (and does not have to be the same) as the package which will be used for a VM instance running this image. On the Joyent @@ -111,18 +122,28 @@ builder. ## Basic Example -Below is a minimal example to create an image on the Joyent public cloud: +Below is a minimal example to create an joyent-brand image on the Joyent public +cloud: ``` {.javascript} "builders": [{ "type": "triton", "triton_account": "triton_username", "triton_key_id": "6b:95:03:3d:d3:6e:52:69:01:96:1a:46:4a:8d:c1:7e", - "triton_key_material": "${file("~/.ssh/id_rsa")}", + "triton_key_material": "~/.ssh/id_rsa", "source_machine_name": "image-builder", "source_machine_package": "g3-standard-0.5-smartos", "source_machine_image": "70e3ae72-96b6-11e6-9056-9737fd4d0764", + "ssh_username": "root", + "ssh_private_key_file": "~/.ssh/id_rsa", "image_name": "my_new_image", "image_version": "1.0.0", }], ``` + +In the above example the SSH key used for `triton_key_material` (connecting to +the Cloud API) and the `ssh_private_key_file` (connecting to the VM once it has +started) are the same. This is because Triton automatically configures the root +users to be able to login via SSH with the same key used to create the VM via +the Cloud API. In more advanced scenarios for example when using a +`source_machine_image` one might use different credentials.