adding basic support for OpenSSH CertificateFile support
small fix
This commit is contained in:
parent
a15ee80c68
commit
31a7a1d637
|
@ -0,0 +1,73 @@
|
||||||
|
package ssh_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
helperssh "github.com/hashicorp/packer/helper/ssh"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getIdentityCertFile() (certSigner ssh.Signer, err error) {
|
||||||
|
usr, _ := user.Current()
|
||||||
|
privateKeyFile := usr.HomeDir + "/.ssh/id_ed25519"
|
||||||
|
certificateFile := usr.HomeDir + "/.ssh/id_ed25519-cert.pub"
|
||||||
|
|
||||||
|
return helperssh.FileSignerWithCert(privateKeyFile, certificateFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnectFunc(t *testing.T) {
|
||||||
|
{
|
||||||
|
if os.Getenv("PACKER_ACC") == "" {
|
||||||
|
t.Skip("This test is only run with PACKER_ACC=1")
|
||||||
|
}
|
||||||
|
|
||||||
|
const host = "mybastionhost.com:2222"
|
||||||
|
|
||||||
|
certSigner, err := getIdentityCertFile()
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("we have an error %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKeys := ssh.PublicKeys(certSigner)
|
||||||
|
usr, _ := user.Current()
|
||||||
|
|
||||||
|
config := &ssh.ClientConfig{
|
||||||
|
User: usr.Username,
|
||||||
|
Auth: []ssh.AuthMethod{
|
||||||
|
publicKeys,
|
||||||
|
},
|
||||||
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
println("Dialing", config.User)
|
||||||
|
connection, err := ssh.Dial("tcp", host, config)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Failed to dial ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := connection.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Failed to create session: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
var stdoutBuf bytes.Buffer
|
||||||
|
session.Stdout = &stdoutBuf
|
||||||
|
|
||||||
|
err = session.Run("ls")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Failed to ls")
|
||||||
|
}
|
||||||
|
fmt.Println(stdoutBuf.String())
|
||||||
|
}
|
||||||
|
}
|
|
@ -111,6 +111,10 @@ type SSH struct {
|
||||||
// The `~` can be used in path and will be expanded to the home directory
|
// The `~` can be used in path and will be expanded to the home directory
|
||||||
// of current user.
|
// of current user.
|
||||||
SSHPrivateKeyFile string `mapstructure:"ssh_private_key_file" undocumented:"true"`
|
SSHPrivateKeyFile string `mapstructure:"ssh_private_key_file" undocumented:"true"`
|
||||||
|
// Path to user certificate used to authenticate with SSH.
|
||||||
|
// The `~` can be used in path and will be expanded to the
|
||||||
|
// home directory of current user.
|
||||||
|
SSHCertificateFile string `mapstructure:"ssh_certificate_file"`
|
||||||
// If `true`, a PTY will be requested for the SSH connection. This defaults
|
// If `true`, a PTY will be requested for the SSH connection. This defaults
|
||||||
// to `false`.
|
// to `false`.
|
||||||
SSHPty bool `mapstructure:"ssh_pty"`
|
SSHPty bool `mapstructure:"ssh_pty"`
|
||||||
|
@ -148,6 +152,10 @@ type SSH struct {
|
||||||
// bastion host. The `~` can be used in path and will be expanded to the
|
// bastion host. The `~` can be used in path and will be expanded to the
|
||||||
// home directory of current user.
|
// home directory of current user.
|
||||||
SSHBastionPrivateKeyFile string `mapstructure:"ssh_bastion_private_key_file"`
|
SSHBastionPrivateKeyFile string `mapstructure:"ssh_bastion_private_key_file"`
|
||||||
|
// Path to user certificate used to authenticate with bastion host.
|
||||||
|
// The `~` can be used in path and will be expanded to the
|
||||||
|
//home directory of current user.
|
||||||
|
SSHBastionCertificateFile string `mapstructure:"ssh_bastion_certificate_file"`
|
||||||
// `scp` or `sftp` - How to transfer files, Secure copy (default) or SSH
|
// `scp` or `sftp` - How to transfer files, Secure copy (default) or SSH
|
||||||
// File Transfer Protocol.
|
// File Transfer Protocol.
|
||||||
SSHFileTransferMethod string `mapstructure:"ssh_file_transfer_method"`
|
SSHFileTransferMethod string `mapstructure:"ssh_file_transfer_method"`
|
||||||
|
@ -316,11 +324,29 @@ func (c *Config) SSHConfigFunc() func(multistep.StateBag) (*ssh.ClientConfig, er
|
||||||
privateKeys = append(privateKeys, c.SSHPrivateKey)
|
privateKeys = append(privateKeys, c.SSHPrivateKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
certPath := ""
|
||||||
|
if c.SSHCertificateFile != "" {
|
||||||
|
var err error
|
||||||
|
certPath, err = packer.ExpandUser(c.SSHCertificateFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, key := range privateKeys {
|
for _, key := range privateKeys {
|
||||||
|
|
||||||
signer, err := ssh.ParsePrivateKey(key)
|
signer, err := ssh.ParsePrivateKey(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Error on parsing SSH private key: %s", err)
|
return nil, fmt.Errorf("Error on parsing SSH private key: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if certPath != "" {
|
||||||
|
signer, err = helperssh.ReadCertificate(certPath, signer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sshConfig.Auth = append(sshConfig.Auth, ssh.PublicKeys(signer))
|
sshConfig.Auth = append(sshConfig.Auth, ssh.PublicKeys(signer))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,6 +457,11 @@ func (c *Config) prepareSSH(ctx *interpolate.Context) []error {
|
||||||
if c.SSHBastionPrivateKeyFile == "" && c.SSHPrivateKeyFile != "" {
|
if c.SSHBastionPrivateKeyFile == "" && c.SSHPrivateKeyFile != "" {
|
||||||
c.SSHBastionPrivateKeyFile = c.SSHPrivateKeyFile
|
c.SSHBastionPrivateKeyFile = c.SSHPrivateKeyFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.SSHBastionCertificateFile == "" && c.SSHCertificateFile != "" {
|
||||||
|
c.SSHBastionCertificateFile = c.SSHCertificateFile
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.SSHProxyHost != "" {
|
if c.SSHProxyHost != "" {
|
||||||
|
@ -462,9 +493,23 @@ func (c *Config) prepareSSH(ctx *interpolate.Context) []error {
|
||||||
} else if _, err := os.Stat(path); err != nil {
|
} else if _, err := os.Stat(path); err != nil {
|
||||||
errs = append(errs, fmt.Errorf(
|
errs = append(errs, fmt.Errorf(
|
||||||
"ssh_private_key_file is invalid: %s", err))
|
"ssh_private_key_file is invalid: %s", err))
|
||||||
} else if _, err := helperssh.FileSigner(path); err != nil {
|
} else {
|
||||||
errs = append(errs, fmt.Errorf(
|
if c.SSHCertificateFile != "" {
|
||||||
"ssh_private_key_file is invalid: %s", err))
|
certPath, err := packer.ExpandUser(c.SSHCertificateFile)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("invalid identity certificate: #{err}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := helperssh.FileSignerWithCert(path, certPath); err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf(
|
||||||
|
"ssh_private_key_file is invalid: %s", err))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, err := helperssh.FileSigner(path); err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf(
|
||||||
|
"ssh_private_key_file is invalid: %s", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -480,9 +525,22 @@ func (c *Config) prepareSSH(ctx *interpolate.Context) []error {
|
||||||
} else if _, err := os.Stat(path); err != nil {
|
} else if _, err := os.Stat(path); err != nil {
|
||||||
errs = append(errs, fmt.Errorf(
|
errs = append(errs, fmt.Errorf(
|
||||||
"ssh_bastion_private_key_file is invalid: %s", err))
|
"ssh_bastion_private_key_file is invalid: %s", err))
|
||||||
} else if _, err := helperssh.FileSigner(path); err != nil {
|
} else {
|
||||||
errs = append(errs, fmt.Errorf(
|
if c.SSHBastionCertificateFile != "" {
|
||||||
"ssh_bastion_private_key_file is invalid: %s", err))
|
certPath, err := packer.ExpandUser(c.SSHBastionCertificateFile)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("invalid identity certificate: #{err}"))
|
||||||
|
}
|
||||||
|
if _, err := helperssh.FileSignerWithCert(path, certPath); err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf(
|
||||||
|
"ssh_bastion_private_key_file is invalid: %s", err))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, err := helperssh.FileSigner(path); err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf(
|
||||||
|
"ssh_bastion_private_key_file is invalid: %s", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ type FlatConfig struct {
|
||||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||||
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
|
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
|
||||||
|
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
|
||||||
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
|
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
|
||||||
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
|
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
|
||||||
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
|
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
|
||||||
|
@ -33,6 +34,7 @@ type FlatConfig struct {
|
||||||
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
|
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
|
||||||
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
|
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
|
||||||
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
|
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
|
||||||
|
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
|
||||||
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
|
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
|
||||||
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
|
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
|
||||||
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
|
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
|
||||||
|
@ -78,6 +80,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||||
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
|
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
|
||||||
|
"ssh_certificate_file": &hcldec.AttrSpec{Name: "ssh_certificate_file", Type: cty.String, Required: false},
|
||||||
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
|
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
|
||||||
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
|
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
|
||||||
"ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false},
|
"ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false},
|
||||||
|
@ -91,6 +94,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||||
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
|
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
|
||||||
"ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false},
|
"ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false},
|
||||||
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
|
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
|
||||||
|
"ssh_bastion_certificate_file": &hcldec.AttrSpec{Name: "ssh_bastion_certificate_file", Type: cty.String, Required: false},
|
||||||
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
|
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
|
||||||
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
|
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
|
||||||
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
|
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
|
||||||
|
@ -127,6 +131,7 @@ type FlatSSH struct {
|
||||||
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
SSHCiphers []string `mapstructure:"ssh_ciphers" cty:"ssh_ciphers" hcl:"ssh_ciphers"`
|
||||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys" hcl:"ssh_clear_authorized_keys"`
|
||||||
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
|
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" undocumented:"true" cty:"ssh_private_key_file" hcl:"ssh_private_key_file"`
|
||||||
|
SSHCertificateFile *string `mapstructure:"ssh_certificate_file" cty:"ssh_certificate_file" hcl:"ssh_certificate_file"`
|
||||||
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
|
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty" hcl:"ssh_pty"`
|
||||||
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
|
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout" hcl:"ssh_timeout"`
|
||||||
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
|
SSHWaitTimeout *string `mapstructure:"ssh_wait_timeout" undocumented:"true" cty:"ssh_wait_timeout" hcl:"ssh_wait_timeout"`
|
||||||
|
@ -140,6 +145,7 @@ type FlatSSH struct {
|
||||||
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
|
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password" hcl:"ssh_bastion_password"`
|
||||||
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
|
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive" hcl:"ssh_bastion_interactive"`
|
||||||
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
|
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file" hcl:"ssh_bastion_private_key_file"`
|
||||||
|
SSHBastionCertificateFile *string `mapstructure:"ssh_bastion_certificate_file" cty:"ssh_bastion_certificate_file" hcl:"ssh_bastion_certificate_file"`
|
||||||
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
|
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method" hcl:"ssh_file_transfer_method"`
|
||||||
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
|
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host" hcl:"ssh_proxy_host"`
|
||||||
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
|
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port" hcl:"ssh_proxy_port"`
|
||||||
|
@ -174,6 +180,7 @@ func (*FlatSSH) HCL2Spec() map[string]hcldec.Spec {
|
||||||
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
"ssh_ciphers": &hcldec.AttrSpec{Name: "ssh_ciphers", Type: cty.List(cty.String), Required: false},
|
||||||
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
"ssh_clear_authorized_keys": &hcldec.AttrSpec{Name: "ssh_clear_authorized_keys", Type: cty.Bool, Required: false},
|
||||||
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
|
"ssh_private_key_file": &hcldec.AttrSpec{Name: "ssh_private_key_file", Type: cty.String, Required: false},
|
||||||
|
"ssh_certificate_file": &hcldec.AttrSpec{Name: "ssh_certificate_file", Type: cty.String, Required: false},
|
||||||
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
|
"ssh_pty": &hcldec.AttrSpec{Name: "ssh_pty", Type: cty.Bool, Required: false},
|
||||||
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
|
"ssh_timeout": &hcldec.AttrSpec{Name: "ssh_timeout", Type: cty.String, Required: false},
|
||||||
"ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false},
|
"ssh_wait_timeout": &hcldec.AttrSpec{Name: "ssh_wait_timeout", Type: cty.String, Required: false},
|
||||||
|
@ -187,6 +194,7 @@ func (*FlatSSH) HCL2Spec() map[string]hcldec.Spec {
|
||||||
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
|
"ssh_bastion_password": &hcldec.AttrSpec{Name: "ssh_bastion_password", Type: cty.String, Required: false},
|
||||||
"ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false},
|
"ssh_bastion_interactive": &hcldec.AttrSpec{Name: "ssh_bastion_interactive", Type: cty.Bool, Required: false},
|
||||||
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
|
"ssh_bastion_private_key_file": &hcldec.AttrSpec{Name: "ssh_bastion_private_key_file", Type: cty.String, Required: false},
|
||||||
|
"ssh_bastion_certificate_file": &hcldec.AttrSpec{Name: "ssh_bastion_certificate_file", Type: cty.String, Required: false},
|
||||||
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
|
"ssh_file_transfer_method": &hcldec.AttrSpec{Name: "ssh_file_transfer_method", Type: cty.String, Required: false},
|
||||||
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
|
"ssh_proxy_host": &hcldec.AttrSpec{Name: "ssh_proxy_host", Type: cty.String, Required: false},
|
||||||
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
|
"ssh_proxy_port": &hcldec.AttrSpec{Name: "ssh_proxy_port", Type: cty.Number, Required: false},
|
||||||
|
|
|
@ -139,6 +139,30 @@ func TestConfig_winrm_use_ntlm(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSSHBastion(t *testing.T) {
|
||||||
|
c := &Config{
|
||||||
|
Type: "ssh",
|
||||||
|
SSH: SSH{
|
||||||
|
SSHUsername: "root",
|
||||||
|
SSHBastionHost: "mybastionhost.company.com",
|
||||||
|
SSHBastionPassword: "test",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Prepare(testContext(t)); len(err) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.SSHBastionCertificateFile != "" {
|
||||||
|
t.Fatalf("Identity certificate somehow set")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.SSHPrivateKeyFile != "" {
|
||||||
|
t.Fatalf("Private key file somehow set")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestSSHConfigFunc(t *testing.T) {
|
func TestSSHConfigFunc(t *testing.T) {
|
||||||
state := new(multistep.BasicStateBag)
|
state := new(multistep.BasicStateBag)
|
||||||
|
|
||||||
|
@ -170,7 +194,9 @@ func TestSSHConfigFunc(t *testing.T) {
|
||||||
if sshConfig.Config.Ciphers[0] != "partycipher" {
|
if sshConfig.Config.Ciphers[0] != "partycipher" {
|
||||||
t.Fatalf("ssh_ciphers should be a direct passthrough.")
|
t.Fatalf("ssh_ciphers should be a direct passthrough.")
|
||||||
}
|
}
|
||||||
|
if c.SSHCertificateFile != "" {
|
||||||
|
t.Fatalf("Identity certificate somehow set")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfig_winrm(t *testing.T) {
|
func TestConfig_winrm(t *testing.T) {
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.org/x/crypto/ssh/terminal"
|
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
@ -12,6 +11,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
|
|
||||||
"github.com/hashicorp/packer/communicator/ssh"
|
"github.com/hashicorp/packer/communicator/ssh"
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
"github.com/hashicorp/packer/helper/multistep"
|
||||||
helperssh "github.com/hashicorp/packer/helper/ssh"
|
helperssh "github.com/hashicorp/packer/helper/ssh"
|
||||||
|
@ -277,12 +278,23 @@ func sshBastionConfig(config *Config) (*gossh.ClientConfig, error) {
|
||||||
"Error expanding path for SSH bastion private key: %s", err)
|
"Error expanding path for SSH bastion private key: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
signer, err := helperssh.FileSigner(path)
|
if config.SSHBastionCertificateFile != "" {
|
||||||
if err != nil {
|
identityPath, err := packer.ExpandUser(config.SSHBastionCertificateFile)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error expanding path for SSH bastion identity certificate: %s", err)
|
||||||
|
}
|
||||||
|
signer, err := helperssh.FileSignerWithCert(path, identityPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
auth = append(auth, gossh.PublicKeys(signer))
|
||||||
|
} else {
|
||||||
|
signer, err := helperssh.FileSigner(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
auth = append(auth, gossh.PublicKeys(signer))
|
||||||
}
|
}
|
||||||
|
|
||||||
auth = append(auth, gossh.PublicKeys(signer))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.SSHBastionAgentAuth {
|
if config.SSHBastionAgentAuth {
|
||||||
|
|
|
@ -5,12 +5,12 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FileSigner returns an ssh.Signer for a key file.
|
func parseKeyFile(path string) ([]byte, error) {
|
||||||
func FileSigner(path string) (ssh.Signer, error) {
|
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -34,6 +34,16 @@ func FileSigner(path string) (ssh.Signer, error) {
|
||||||
"Failed to read key '%s': password protected keys are\n"+
|
"Failed to read key '%s': password protected keys are\n"+
|
||||||
"not supported. Please decrypt the key prior to use.", path)
|
"not supported. Please decrypt the key prior to use.", path)
|
||||||
}
|
}
|
||||||
|
return keyBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileSigner returns an ssh.Signer for a key file.
|
||||||
|
func FileSigner(path string) (ssh.Signer, error) {
|
||||||
|
|
||||||
|
keyBytes, err := parseKeyFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
signer, err := ssh.ParsePrivateKey(keyBytes)
|
signer, err := ssh.ParsePrivateKey(keyBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -42,3 +52,64 @@ func FileSigner(path string) (ssh.Signer, error) {
|
||||||
|
|
||||||
return signer, nil
|
return signer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ReadCertificate(certificatePath string, keySigner ssh.Signer) (ssh.Signer, error) {
|
||||||
|
|
||||||
|
if certificatePath == "" {
|
||||||
|
return keySigner, fmt.Errorf("no certificate file provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the certificate
|
||||||
|
cert, err := ioutil.ReadFile(certificatePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to read certificate file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pk, _, _, _, err := ssh.ParseAuthorizedKey(cert)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to parse public key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certificate, ok := pk.(*ssh.Certificate)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("Error loading certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = checkValidCert(certificate)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s not a valid cert: %v", certificatePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certSigner, err := ssh.NewCertSigner(certificate, keySigner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create cert signer: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return certSigner, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileSigner returns an ssh.Signer for a key file.
|
||||||
|
func FileSignerWithCert(path string, certificatePath string) (ssh.Signer, error) {
|
||||||
|
|
||||||
|
keySigner, err := FileSigner(path)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ReadCertificate(certificatePath, keySigner)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkValidCert(cert *ssh.Certificate) error {
|
||||||
|
const CertTimeInfinity = 1<<64 - 1
|
||||||
|
unixNow := time.Now().Unix()
|
||||||
|
|
||||||
|
if after := int64(cert.ValidAfter); after < 0 || unixNow < int64(cert.ValidAfter) {
|
||||||
|
return fmt.Errorf("ssh: cert is not yet valid")
|
||||||
|
}
|
||||||
|
if before := int64(cert.ValidBefore); cert.ValidBefore != uint64(CertTimeInfinity) && (unixNow >= before || before < 0) {
|
||||||
|
return fmt.Errorf("ssh: cert has expired")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue