adding basic support for OpenSSH CertificateFile support

small fix
This commit is contained in:
Roger Hu 2020-07-02 00:10:48 -07:00 committed by Megan Marsh
parent a15ee80c68
commit 31a7a1d637
6 changed files with 263 additions and 15 deletions

View File

@ -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())
}
}

View File

@ -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))
}
}
} }
} }
} }

View File

@ -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},

View File

@ -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) {

View File

@ -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 {

View File

@ -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
}