communicator/ssh: support for bastion SSH
This commit is contained in:
parent
b2609db395
commit
cbaaf0da52
@ -1,8 +1,11 @@
|
|||||||
package ssh
|
package ssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConnectFunc is a convenience method for returning a function
|
// ConnectFunc is a convenience method for returning a function
|
||||||
@ -23,3 +26,43 @@ func ConnectFunc(network, addr string) func() (net.Conn, error) {
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BastionConnectFunc is a convenience method for returning a function
|
||||||
|
// that connects to a host over a bastion connection.
|
||||||
|
func BastionConnectFunc(
|
||||||
|
bProto string,
|
||||||
|
bAddr string,
|
||||||
|
bConf *ssh.ClientConfig,
|
||||||
|
proto string,
|
||||||
|
addr string) func() (net.Conn, error) {
|
||||||
|
return func() (net.Conn, error) {
|
||||||
|
// Connect to the bastion
|
||||||
|
bastion, err := ssh.Dial(bProto, bAddr, bConf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error connecting to bastion: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect through to the end host
|
||||||
|
conn, err := bastion.Dial(proto, addr)
|
||||||
|
if err != nil {
|
||||||
|
bastion.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap it up so we close both things properly
|
||||||
|
return &bastionConn{
|
||||||
|
Conn: conn,
|
||||||
|
Bastion: bastion,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type bastionConn struct {
|
||||||
|
net.Conn
|
||||||
|
Bastion *ssh.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *bastionConn) Close() error {
|
||||||
|
c.Conn.Close()
|
||||||
|
return c.Bastion.Close()
|
||||||
|
}
|
||||||
|
@ -23,6 +23,11 @@ type Config struct {
|
|||||||
SSHPty bool `mapstructure:"ssh_pty"`
|
SSHPty bool `mapstructure:"ssh_pty"`
|
||||||
SSHTimeout time.Duration `mapstructure:"ssh_timeout"`
|
SSHTimeout time.Duration `mapstructure:"ssh_timeout"`
|
||||||
SSHHandshakeAttempts int `mapstructure:"ssh_handshake_attempts"`
|
SSHHandshakeAttempts int `mapstructure:"ssh_handshake_attempts"`
|
||||||
|
SSHBastionHost string `mapstructure:"ssh_bastion_host"`
|
||||||
|
SSHBastionPort int `mapstructure:"ssh_bastion_port"`
|
||||||
|
SSHBastionUsername string `mapstructure:"ssh_bastion_username"`
|
||||||
|
SSHBastionPassword string `mapstructure:"ssh_bastion_password"`
|
||||||
|
SSHBastionPrivateKey string `mapstructure:"ssh_bastion_private_key_file"`
|
||||||
|
|
||||||
// WinRM
|
// WinRM
|
||||||
WinRMUser string `mapstructure:"winrm_username"`
|
WinRMUser string `mapstructure:"winrm_username"`
|
||||||
@ -77,6 +82,12 @@ func (c *Config) prepareSSH(ctx *interpolate.Context) []error {
|
|||||||
c.SSHHandshakeAttempts = 10
|
c.SSHHandshakeAttempts = 10
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.SSHBastionHost != "" {
|
||||||
|
if c.SSHBastionPort == 0 {
|
||||||
|
c.SSHBastionPort = 22
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
var errs []error
|
var errs []error
|
||||||
if c.SSHUsername == "" {
|
if c.SSHUsername == "" {
|
||||||
@ -93,6 +104,13 @@ func (c *Config) prepareSSH(ctx *interpolate.Context) []error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.SSHBastionHost != "" {
|
||||||
|
if c.SSHBastionPassword == "" && c.SSHBastionPrivateKey == "" {
|
||||||
|
errs = append(errs, errors.New(
|
||||||
|
"ssh_bastion_password or ssh_bastion_private_key_file must be specified"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,10 +4,12 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mitchellh/multistep"
|
"github.com/mitchellh/multistep"
|
||||||
|
commonssh "github.com/mitchellh/packer/common/ssh"
|
||||||
"github.com/mitchellh/packer/communicator/ssh"
|
"github.com/mitchellh/packer/communicator/ssh"
|
||||||
"github.com/mitchellh/packer/packer"
|
"github.com/mitchellh/packer/packer"
|
||||||
gossh "golang.org/x/crypto/ssh"
|
gossh "golang.org/x/crypto/ssh"
|
||||||
@ -79,6 +81,24 @@ func (s *StepConnectSSH) Cleanup(multistep.StateBag) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan struct{}) (packer.Communicator, error) {
|
func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan struct{}) (packer.Communicator, error) {
|
||||||
|
// Determine if we're using a bastion host, and if so, retrieve
|
||||||
|
// that configuration. This configuration doesn't change so we
|
||||||
|
// do this one before entering the retry loop.
|
||||||
|
var bProto, bAddr string
|
||||||
|
var bConf *gossh.ClientConfig
|
||||||
|
if s.Config.SSHBastionHost != "" {
|
||||||
|
// The protocol is hardcoded for now, but may be configurable one day
|
||||||
|
bProto = "tcp"
|
||||||
|
bAddr = fmt.Sprintf(
|
||||||
|
"%s:%d", s.Config.SSHBastionHost, s.Config.SSHBastionPort)
|
||||||
|
|
||||||
|
conf, err := sshBastionConfig(s.Config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error configuring bastion: %s", err)
|
||||||
|
}
|
||||||
|
bConf = conf
|
||||||
|
}
|
||||||
|
|
||||||
handshakeAttempts := 0
|
handshakeAttempts := 0
|
||||||
|
|
||||||
var comm packer.Communicator
|
var comm packer.Communicator
|
||||||
@ -117,10 +137,18 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan stru
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
address := fmt.Sprintf("%s:%d", host, port)
|
|
||||||
|
|
||||||
// Attempt to connect to SSH port
|
// Attempt to connect to SSH port
|
||||||
connFunc := ssh.ConnectFunc("tcp", address)
|
var connFunc func() (net.Conn, error)
|
||||||
|
address := fmt.Sprintf("%s:%d", host, port)
|
||||||
|
if bAddr != "" {
|
||||||
|
// We're using a bastion host, so use the bastion connfunc
|
||||||
|
connFunc = ssh.BastionConnectFunc(
|
||||||
|
bProto, bAddr, bConf, "tcp", address)
|
||||||
|
} else {
|
||||||
|
// No bastion host, connect directly
|
||||||
|
connFunc = ssh.ConnectFunc("tcp", address)
|
||||||
|
}
|
||||||
|
|
||||||
nc, err := connFunc()
|
nc, err := connFunc()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[DEBUG] TCP connection to SSH ip/port failed: %s", err)
|
log.Printf("[DEBUG] TCP connection to SSH ip/port failed: %s", err)
|
||||||
@ -164,3 +192,27 @@ func (s *StepConnectSSH) waitForSSH(state multistep.StateBag, cancel <-chan stru
|
|||||||
|
|
||||||
return comm, nil
|
return comm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sshBastionConfig(config *Config) (*gossh.ClientConfig, error) {
|
||||||
|
auth := make([]gossh.AuthMethod, 0, 2)
|
||||||
|
if config.SSHBastionPassword != "" {
|
||||||
|
auth = append(auth,
|
||||||
|
gossh.Password(config.SSHBastionPassword),
|
||||||
|
gossh.KeyboardInteractive(
|
||||||
|
ssh.PasswordKeyboardInteractive(config.SSHBastionPassword)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.SSHBastionPrivateKey != "" {
|
||||||
|
signer, err := commonssh.FileSigner(config.SSHBastionPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
auth = append(auth, gossh.PublicKeys(signer))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gossh.ClientConfig{
|
||||||
|
User: config.SSHBastionUsername,
|
||||||
|
Auth: auth,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user