updated ssh authentication and testing for ssh
This commit is contained in:
parent
01eb224469
commit
c456d9608b
|
@ -125,11 +125,13 @@ func (c *Communicator) Connect(o terraform.UIOutput) (err error) {
|
||||||
" User: %s\n"+
|
" User: %s\n"+
|
||||||
" Password: %t\n"+
|
" Password: %t\n"+
|
||||||
" Private key: %t\n"+
|
" Private key: %t\n"+
|
||||||
|
" Certificate: %t\n"+
|
||||||
" SSH Agent: %t\n"+
|
" SSH Agent: %t\n"+
|
||||||
" Checking Host Key: %t",
|
" Checking Host Key: %t",
|
||||||
c.connInfo.Host, c.connInfo.User,
|
c.connInfo.Host, c.connInfo.User,
|
||||||
c.connInfo.Password != "",
|
c.connInfo.Password != "",
|
||||||
c.connInfo.PrivateKey != "",
|
c.connInfo.PrivateKey != "",
|
||||||
|
c.connInfo.Certificate != "",
|
||||||
c.connInfo.Agent,
|
c.connInfo.Agent,
|
||||||
c.connInfo.HostKey != "",
|
c.connInfo.HostKey != "",
|
||||||
))
|
))
|
||||||
|
|
|
@ -58,10 +58,10 @@ const testServerHostCert = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC1
|
||||||
|
|
||||||
const testCAPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCrozyZIhdEvalCn+eSzHH94cO9ykiywA13ntWI7mJcHBwYTeCYWG8E9zGXyp2iDOjCGudM0Tdt8o0OofKChk9Z/qiUN0G8y1kmaXBlBM3qA5R9NPpvMYMNkYLfX6ivtZCnqrsbzaoqN2Oc/7H2StHzJWh/XCGu9otQZA6vdv1oSmAsZOjw/xIGaGQqDUaLq21J280PP1qSbdJHf76iSHE+TWe3YpqV946JWM5tCh0DykZ10VznvxYpUjzhr07IN3tVKxOXbPnnU7lX6IaLIWgfzLqwSyheeux05c3JLF9iF4sFu8ou4hwQz1iuUTU1jxgwZP0w/bkXgFFs0949lW81`
|
const testCAPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCrozyZIhdEvalCn+eSzHH94cO9ykiywA13ntWI7mJcHBwYTeCYWG8E9zGXyp2iDOjCGudM0Tdt8o0OofKChk9Z/qiUN0G8y1kmaXBlBM3qA5R9NPpvMYMNkYLfX6ivtZCnqrsbzaoqN2Oc/7H2StHzJWh/XCGu9otQZA6vdv1oSmAsZOjw/xIGaGQqDUaLq21J280PP1qSbdJHf76iSHE+TWe3YpqV946JWM5tCh0DykZ10VznvxYpUjzhr07IN3tVKxOXbPnnU7lX6IaLIWgfzLqwSyheeux05c3JLF9iF4sFu8ou4hwQz1iuUTU1jxgwZP0w/bkXgFFs0949lW81`
|
||||||
|
|
||||||
func newMockLineServer(t *testing.T, signer ssh.Signer) string {
|
func newMockLineServer(t *testing.T, signer ssh.Signer, pubKey string) string {
|
||||||
serverConfig := &ssh.ServerConfig{
|
serverConfig := &ssh.ServerConfig{
|
||||||
PasswordCallback: acceptUserPass("user", "pass"),
|
PasswordCallback: acceptUserPass("user", "pass"),
|
||||||
PublicKeyCallback: acceptPublicKey(testClientPublicKey),
|
PublicKeyCallback: acceptPublicKey(pubKey),
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
@ -119,7 +119,7 @@ func newMockLineServer(t *testing.T, signer ssh.Signer) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNew_Invalid(t *testing.T) {
|
func TestNew_Invalid(t *testing.T) {
|
||||||
address := newMockLineServer(t, nil)
|
address := newMockLineServer(t, nil, testClientPublicKey)
|
||||||
parts := strings.Split(address, ":")
|
parts := strings.Split(address, ":")
|
||||||
|
|
||||||
r := &terraform.InstanceState{
|
r := &terraform.InstanceState{
|
||||||
|
@ -147,7 +147,7 @@ func TestNew_Invalid(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStart(t *testing.T) {
|
func TestStart(t *testing.T) {
|
||||||
address := newMockLineServer(t, nil)
|
address := newMockLineServer(t, nil, testClientPublicKey)
|
||||||
parts := strings.Split(address, ":")
|
parts := strings.Split(address, ":")
|
||||||
|
|
||||||
r := &terraform.InstanceState{
|
r := &terraform.InstanceState{
|
||||||
|
@ -180,7 +180,7 @@ func TestStart(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLostConnection(t *testing.T) {
|
func TestLostConnection(t *testing.T) {
|
||||||
address := newMockLineServer(t, nil)
|
address := newMockLineServer(t, nil, testClientPublicKey)
|
||||||
parts := strings.Split(address, ":")
|
parts := strings.Split(address, ":")
|
||||||
|
|
||||||
r := &terraform.InstanceState{
|
r := &terraform.InstanceState{
|
||||||
|
@ -229,11 +229,11 @@ func TestHostKey(t *testing.T) {
|
||||||
// get the server's public key
|
// get the server's public key
|
||||||
signer, err := ssh.ParsePrivateKey([]byte(testServerPrivateKey))
|
signer, err := ssh.ParsePrivateKey([]byte(testServerPrivateKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("unable to parse private key: " + err.Error())
|
t.Fatalf("unable to parse private key: %v", err)
|
||||||
}
|
}
|
||||||
pubKey := fmt.Sprintf("ssh-rsa %s", base64.StdEncoding.EncodeToString(signer.PublicKey().Marshal()))
|
pubKey := fmt.Sprintf("ssh-rsa %s", base64.StdEncoding.EncodeToString(signer.PublicKey().Marshal()))
|
||||||
|
|
||||||
address := newMockLineServer(t, nil)
|
address := newMockLineServer(t, nil, testClientPublicKey)
|
||||||
host, p, _ := net.SplitHostPort(address)
|
host, p, _ := net.SplitHostPort(address)
|
||||||
port, _ := strconv.Atoi(p)
|
port, _ := strconv.Atoi(p)
|
||||||
|
|
||||||
|
@ -269,7 +269,7 @@ func TestHostKey(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// now check with the wrong HostKey
|
// now check with the wrong HostKey
|
||||||
address = newMockLineServer(t, nil)
|
address = newMockLineServer(t, nil, testClientPublicKey)
|
||||||
_, p, _ = net.SplitHostPort(address)
|
_, p, _ = net.SplitHostPort(address)
|
||||||
port, _ = strconv.Atoi(p)
|
port, _ = strconv.Atoi(p)
|
||||||
|
|
||||||
|
@ -308,7 +308,7 @@ func TestHostCert(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
address := newMockLineServer(t, signer)
|
address := newMockLineServer(t, signer, testClientPublicKey)
|
||||||
host, p, _ := net.SplitHostPort(address)
|
host, p, _ := net.SplitHostPort(address)
|
||||||
port, _ := strconv.Atoi(p)
|
port, _ := strconv.Atoi(p)
|
||||||
|
|
||||||
|
@ -344,7 +344,7 @@ func TestHostCert(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// now check with the wrong HostKey
|
// now check with the wrong HostKey
|
||||||
address = newMockLineServer(t, signer)
|
address = newMockLineServer(t, signer, testClientPublicKey)
|
||||||
_, p, _ = net.SplitHostPort(address)
|
_, p, _ = net.SplitHostPort(address)
|
||||||
port, _ = strconv.Atoi(p)
|
port, _ = strconv.Atoi(p)
|
||||||
|
|
||||||
|
@ -367,6 +367,105 @@ func TestHostCert(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SERVER_PEM = `-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEA8CkDr7uxCFt6lQUVwS8NyPO+fQNxORoGnMnN/XhVJZvpqyKR
|
||||||
|
Uji9R0d8D66bYxUUsabXjP2y4HTVzbZtnvXFZZshk0cOtJjjekpYJaLK2esPR/iX
|
||||||
|
wvSltNkrDQDPN/RmgEEMIevW8AgrPsqrnybFHxTpd7rEUHXBOe4nMNRIg3XHykB6
|
||||||
|
jZk8q5bBPUe3I/f0DK5TJEBpTc6dO3P/j93u55VUqr39/SPRHnld2mCw+c8v6UOh
|
||||||
|
sssO/DIZFPScD3DYqsk2N+/nz9zXfcOTdWGhawgxuIo1DTokrNQbG3pDrLqcWgqj
|
||||||
|
13vqJFCmRA0O2CQIwJePd6+Np/XO3Uh/KL6FlQIDAQABAoIBAQCmvQMXNmvCDqk7
|
||||||
|
30zsVDvw4fHGH+azK3Od1aqTqcEMHISOUbCtckFPxLzIsoSltRQqB1kuRVG07skm
|
||||||
|
Stsu+xny4lLcSwBVuLRuykEK2EyYIc/5Owo6y9pkhkaSf5ZfFes4bnD6+B/BhRpp
|
||||||
|
PRMMq0E+xCkX/G6iIi9mhgdlqm0x/vKtjzQeeshw9+gRcRLUpX+UeKFKXMXcDayx
|
||||||
|
qekr1bAaQKNBhTK+CbZjcqzG4f+BXVGRTZ9nsPAV+yTnWUCU0TghwPmtthHbebqa
|
||||||
|
9hlkum7qik/bQj/tjJ8/b0vTfHQSVxhtPG/ZV2Tn9ZuL/vrkYqeyMU8XkJ/uaEvH
|
||||||
|
WPyOcB4BAoGBAP5o5JSEtPog+U3JFrLNSRjz5ofZNVkJzice+0XyqlzJDHhX5tF8
|
||||||
|
mriYQZLLXYhckBm4IdkhTn/dVbXNQTzyy2WVuO5nU8bkCMvGL9CGpW4YGqwGf7NX
|
||||||
|
e4H3emtRjLv8VZpUHe/RUUDhmYvMSt1qmXuskfpROuGfLhQBUd6A4J+BAoGBAPGp
|
||||||
|
UcMKjrxZ5qjYU6DLgS+xeca4Eu70HgdbSQbRo45WubXjyXvTRFij36DrpxJWf1D7
|
||||||
|
lIsyBifoTra/lAuC1NQXGYWjTCdk2ey8Ll5qOgiXvE6lINHABr+U/Z90/g6LuML2
|
||||||
|
VzaZbq/QLcT3yVsdyTogKckzCaKsCpusyHE1CXAVAoGAd6kMglKc8N0bhZukgnsN
|
||||||
|
+5+UeacPcY6sGTh4RWErAjNKGzx1A2lROKvcg9gFaULoQECcIw2IZ5nKW5VsLueg
|
||||||
|
BWrTrcaJ4A2XmYjhKnp6SvspaGoyHD90hx/Iw7t6r1yzQsB3yDmytwqldtyjBdvC
|
||||||
|
zynPC2azhDWjraMlR7tka4ECgYAxwvLiHa9sm3qCtCDsUFtmrb3srITBjaUNUL/F
|
||||||
|
1q8+JR+Sk7gudj9xnTT0VvINNaB71YIt83wPBagHu4VJpYQbtDH+MbUBu6OgOtO1
|
||||||
|
f1w53rzY2OncJxV8p7pd9mJGLoE6LC2jQY7oRw7Vq0xcJdME1BCmrIrEY3a/vaF8
|
||||||
|
pjYuTQKBgQCIOH23Xita8KmhH0NdlWxZfcQt1j3AnOcKe6UyN4BsF8hqS7eTA52s
|
||||||
|
WjG5X2IBl7gs1eMM1qkqR8npS9nwfO/pBmZPwjiZoilypXxWj+c+P3vwre2yija4
|
||||||
|
bXgFVj4KFBwhr1+8KcobxC0SAPEouMvSkxzjjw+gnebozUtPlud9jA==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
`
|
||||||
|
const CLIENT_CERT_SIGNED_BY_SERVER = `ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgbMDNUn4M2TtzrSH7MOT2QsvLzZWjehJ5TYrBOp9p+lwAAAADAQABAAABAQCyu57E7zIWRyEWuaiOiikOSZKFjbwLkpE9fboFfLLsNUJj4zw+5bZUJtzWK8roPjgL8s1oPncro5wuTtI2Nu4fkpeFK0Hb33o6Eyksuj4Om4+6Uemn1QEcb0bZqK8Zyg9Dg9deP7LeE0v78b5/jZafFgwxv+/sMhM0PRD34NCDYcYmkkHlvQtQWFAdbPXCgghObedZyYdoqZVuhTsiPMWtQS/cc9M4tv6mPOuQlhZt3R/Oh/kwUyu45oGRb5bhO4JicozFS3oeClpU+UMbgslkzApJqxZBWN7+PDFSZhKk2GslyeyP4sH3E30Z00yVi/lQYgmQsB+Hg6ClemNQMNu/AAAAAAAAAAAAAAACAAAABHVzZXIAAAAIAAAABHVzZXIAAAAAWzBjXAAAAAB/POfPAAAAAAAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEA8CkDr7uxCFt6lQUVwS8NyPO+fQNxORoGnMnN/XhVJZvpqyKRUji9R0d8D66bYxUUsabXjP2y4HTVzbZtnvXFZZshk0cOtJjjekpYJaLK2esPR/iXwvSltNkrDQDPN/RmgEEMIevW8AgrPsqrnybFHxTpd7rEUHXBOe4nMNRIg3XHykB6jZk8q5bBPUe3I/f0DK5TJEBpTc6dO3P/j93u55VUqr39/SPRHnld2mCw+c8v6UOhsssO/DIZFPScD3DYqsk2N+/nz9zXfcOTdWGhawgxuIo1DTokrNQbG3pDrLqcWgqj13vqJFCmRA0O2CQIwJePd6+Np/XO3Uh/KL6FlQAAAQ8AAAAHc3NoLXJzYQAAAQC6sKEQHyl954BQn2BXuTgOB3NkENBxN7SD8ZaS8PNkDESytLjSIqrzoE6m7xuzprA+G23XRrCY/um3UvM7+7+zbwig2NIBbGbp3QFliQHegQKW6hTZP09jAQZk5jRrrEr/QT/s+gtHPmjxJK7XOQYxhInDKj+aJg62ExcwpQlP/0ATKNOIkdzTzzq916p0UOnnVaaPMKibh5Lv69GafIhKJRZSuuLN9fvs1G1RuUbxn/BNSeoRCr54L++Ztg09fJxunoyELs8mwgzCgB3pdZoUR2Z6ak05W4mvH3lkSz2BKUrlwxI6mterxhJy1GuN1K/zBG0gEMl2UTLajGK3qKM8 itbitloaner@MacBook-Pro-4.fios-router.home`
|
||||||
|
const CLIENT_PEM = `-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEAsruexO8yFkchFrmojoopDkmShY28C5KRPX26BXyy7DVCY+M8
|
||||||
|
PuW2VCbc1ivK6D44C/LNaD53K6OcLk7SNjbuH5KXhStB2996OhMpLLo+DpuPulHp
|
||||||
|
p9UBHG9G2aivGcoPQ4PXXj+y3hNL+/G+f42WnxYMMb/v7DITND0Q9+DQg2HGJpJB
|
||||||
|
5b0LUFhQHWz1woIITm3nWcmHaKmVboU7IjzFrUEv3HPTOLb+pjzrkJYWbd0fzof5
|
||||||
|
MFMruOaBkW+W4TuCYnKMxUt6HgpaVPlDG4LJZMwKSasWQVje/jwxUmYSpNhrJcns
|
||||||
|
j+LB9xN9GdNMlYv5UGIJkLAfh4OgpXpjUDDbvwIDAQABAoIBAEu2ctFVyk/pnbi0
|
||||||
|
uRR4rl+hBvKQUeJNGj2ELvL4Ggs5nIAX2IOEZ7JKLC6FqpSrFq7pEd5g57aSvixX
|
||||||
|
s3DH4CN7w7fj1ShBCNPlHgIWewdRGpeA74vrDWdwNAEsFdDE6aZeCTOhpDGy1vNJ
|
||||||
|
OrtpzS5i9pN0jTvvEneEjtWSZIHiiVlN+0hsFaiwZ6KXON+sDccZPmnP6Fzwj5Rc
|
||||||
|
WS0dKSwnxnx0otWgwWFs8nr306nSeMsNmQkHsS9lz4DEVpp9owdzrX1JmbQvNYAV
|
||||||
|
ohmB3ET4JYFgerqPXJfed9poueGuWCP6MYhsjNeHN35QhofxdO5/0i3JlZfqwZei
|
||||||
|
tNq/0oECgYEA6SqjRqDiIp3ajwyB7Wf0cIQG/P6JZDyN1jl//htgniliIH5UP1Tm
|
||||||
|
uAMG5MincV6X9lOyXyh6Yofu5+NR0yt9SqbDZVJ3ZCxKTun7pxJvQFd7wl5bMkiJ
|
||||||
|
qVfS08k6gQHHDoO+eel+DtpIfWc+e3tvX0aihSU0GZEMqDXYkkphLGECgYEAxDxb
|
||||||
|
+JwJ3N5UEjjkuvFBpuJnmjIaN9HvQkTv3inlx1gLE4iWBZXXsu4aWF8MCUeAAZyP
|
||||||
|
42hQDSkCYX/A22tYCEn/jfrU6A+6rkWBTjdUlYLvlSkhosSnO+117WEItb5cUE95
|
||||||
|
hF4UY7LNs1AsDkV4WE87f/EjpxSwUAjB2Lfd/B8CgYAJ/JiHsuZcozQ0Qk3iVDyF
|
||||||
|
ATKnbWOHFozgqw/PW27U92LLj32eRM2o/gAylmGNmoaZt1YBe2NaiwXxiqv7hnZU
|
||||||
|
VzYxRcn1UWxRWvY7Xq/DKrwTRCVVzwOObEOMbKcD1YaoGX50DEso6bKHJH/pnAzW
|
||||||
|
INlfKIvFuI+5OK0w/tyQoQKBgQCf/jpaOxaLfrV62eobRQJrByLDBGB97GsvU7di
|
||||||
|
IjTWz8DQH0d5rE7d8uWF8ZCFrEcAiV6DYZQK9smbJqbd/uoacAKtBro5rkFdPwwK
|
||||||
|
8m/DKqsdqRhkdgOHh7bjYH7Sdy8ax4Fi27WyB6FQtmgFBrz0+zyetsODwQlzZ4Bs
|
||||||
|
qpSRrwKBgQC0vWHrY5aGIdF+b8EpP0/SSLLALpMySHyWhDyxYcPqdhszYbjDcavv
|
||||||
|
xrrLXNUD2duBHKPVYE+7uVoDkpZXLUQ4x8argo/IwQM6Kh2ma1y83TYMT6XhL1+B
|
||||||
|
5UPcl6RXZBCkiU7nFIG6/0XKFqVWc3fU8e09X+iJwXIJ5Jatywtg+g==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestCertificateBasedAuth(t *testing.T) {
|
||||||
|
signer, err := ssh.ParsePrivateKey([]byte(SERVER_PEM))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to parse private key: %v", err)
|
||||||
|
}
|
||||||
|
address := newMockLineServer(t, signer, CLIENT_CERT_SIGNED_BY_SERVER)
|
||||||
|
host, p, _ := net.SplitHostPort(address)
|
||||||
|
port, _ := strconv.Atoi(p)
|
||||||
|
|
||||||
|
connInfo := &connectionInfo{
|
||||||
|
User: "user",
|
||||||
|
Host: host,
|
||||||
|
PrivateKey: CLIENT_PEM,
|
||||||
|
Certificate: CLIENT_CERT_SIGNED_BY_SERVER,
|
||||||
|
Port: port,
|
||||||
|
Timeout: "30s",
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := prepareSSHConfig(connInfo)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &Communicator{
|
||||||
|
connInfo: connInfo,
|
||||||
|
config: cfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmd remote.Cmd
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
cmd.Command = "echo foo"
|
||||||
|
cmd.Stdout = stdout
|
||||||
|
|
||||||
|
if err := c.Start(&cmd); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := c.Disconnect(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAccUploadFile(t *testing.T) {
|
func TestAccUploadFile(t *testing.T) {
|
||||||
// use the local ssh server and scp binary to check uploads
|
// use the local ssh server and scp binary to check uploads
|
||||||
if ok := os.Getenv("SSH_UPLOAD_TEST"); ok == "" {
|
if ok := os.Getenv("SSH_UPLOAD_TEST"); ok == "" {
|
||||||
|
@ -572,11 +671,12 @@ func acceptUserPass(goodUser, goodPass string) func(ssh.ConnMetadata, []byte) (*
|
||||||
}
|
}
|
||||||
|
|
||||||
func acceptPublicKey(keystr string) func(ssh.ConnMetadata, ssh.PublicKey) (*ssh.Permissions, error) {
|
func acceptPublicKey(keystr string) func(ssh.ConnMetadata, ssh.PublicKey) (*ssh.Permissions, error) {
|
||||||
goodkey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(keystr))
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("error parsing key: %s", err))
|
|
||||||
}
|
|
||||||
return func(_ ssh.ConnMetadata, inkey ssh.PublicKey) (*ssh.Permissions, error) {
|
return func(_ ssh.ConnMetadata, inkey ssh.PublicKey) (*ssh.Permissions, error) {
|
||||||
|
goodkey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(keystr))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if bytes.Equal(inkey.Marshal(), goodkey.Marshal()) {
|
if bytes.Equal(inkey.Marshal(), goodkey.Marshal()) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,16 +41,17 @@ const (
|
||||||
// only keys we look at. If a PrivateKey is given, that is used instead
|
// only keys we look at. If a PrivateKey is given, that is used instead
|
||||||
// of a password.
|
// of a password.
|
||||||
type connectionInfo struct {
|
type connectionInfo struct {
|
||||||
User string
|
User string
|
||||||
Password string
|
Password string
|
||||||
PrivateKey string `mapstructure:"private_key"`
|
PrivateKey string `mapstructure:"private_key"`
|
||||||
Host string
|
Certificate string `mapstructure:"certificate"`
|
||||||
HostKey string `mapstructure:"host_key"`
|
Host string
|
||||||
Port int
|
HostKey string `mapstructure:"host_key"`
|
||||||
Agent bool
|
Port int
|
||||||
Timeout string
|
Agent bool
|
||||||
ScriptPath string `mapstructure:"script_path"`
|
Timeout string
|
||||||
TimeoutVal time.Duration `mapstructure:"-"`
|
ScriptPath string `mapstructure:"script_path"`
|
||||||
|
TimeoutVal time.Duration `mapstructure:"-"`
|
||||||
|
|
||||||
BastionUser string `mapstructure:"bastion_user"`
|
BastionUser string `mapstructure:"bastion_user"`
|
||||||
BastionPassword string `mapstructure:"bastion_password"`
|
BastionPassword string `mapstructure:"bastion_password"`
|
||||||
|
@ -151,12 +152,13 @@ func prepareSSHConfig(connInfo *connectionInfo) (*sshConfig, error) {
|
||||||
host := fmt.Sprintf("%s:%d", connInfo.Host, connInfo.Port)
|
host := fmt.Sprintf("%s:%d", connInfo.Host, connInfo.Port)
|
||||||
|
|
||||||
sshConf, err := buildSSHClientConfig(sshClientConfigOpts{
|
sshConf, err := buildSSHClientConfig(sshClientConfigOpts{
|
||||||
user: connInfo.User,
|
user: connInfo.User,
|
||||||
host: host,
|
host: host,
|
||||||
privateKey: connInfo.PrivateKey,
|
privateKey: connInfo.PrivateKey,
|
||||||
password: connInfo.Password,
|
password: connInfo.Password,
|
||||||
hostKey: connInfo.HostKey,
|
hostKey: connInfo.HostKey,
|
||||||
sshAgent: sshAgent,
|
certificate: connInfo.Certificate,
|
||||||
|
sshAgent: sshAgent,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -192,12 +194,13 @@ func prepareSSHConfig(connInfo *connectionInfo) (*sshConfig, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type sshClientConfigOpts struct {
|
type sshClientConfigOpts struct {
|
||||||
privateKey string
|
privateKey string
|
||||||
password string
|
password string
|
||||||
sshAgent *sshAgent
|
sshAgent *sshAgent
|
||||||
user string
|
certificate string
|
||||||
host string
|
user string
|
||||||
hostKey string
|
host string
|
||||||
|
hostKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildSSHClientConfig(opts sshClientConfigOpts) (*ssh.ClientConfig, error) {
|
func buildSSHClientConfig(opts sshClientConfigOpts) (*ssh.ClientConfig, error) {
|
||||||
|
@ -235,11 +238,23 @@ func buildSSHClientConfig(opts sshClientConfigOpts) (*ssh.ClientConfig, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.privateKey != "" {
|
if opts.privateKey != "" {
|
||||||
pubKeyAuth, err := readPrivateKey(opts.privateKey)
|
if opts.certificate != "" {
|
||||||
if err != nil {
|
log.Println("using client certificate for authentication")
|
||||||
return nil, err
|
|
||||||
|
certSigner, err := signCertWithPrivateKey(opts.privateKey, opts.certificate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conf.Auth = append(conf.Auth, certSigner)
|
||||||
|
} else {
|
||||||
|
log.Println("using private key for authentication")
|
||||||
|
|
||||||
|
pubKeyAuth, err := readPrivateKey(opts.privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conf.Auth = append(conf.Auth, pubKeyAuth)
|
||||||
}
|
}
|
||||||
conf.Auth = append(conf.Auth, pubKeyAuth)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.password != "" {
|
if opts.password != "" {
|
||||||
|
@ -255,6 +270,31 @@ func buildSSHClientConfig(opts sshClientConfigOpts) (*ssh.ClientConfig, error) {
|
||||||
return conf, nil
|
return conf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a Cert Signer and return ssh.AuthMethod
|
||||||
|
func signCertWithPrivateKey(pk string, certificate string) (ssh.AuthMethod, error) {
|
||||||
|
rawPk, err := ssh.ParseRawPrivateKey([]byte(pk))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse private key %q: %s", pk, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pcert, _, _, _, err := ssh.ParseAuthorizedKey([]byte(certificate))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse certificate %q: %s", certificate, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
usigner, err := ssh.NewSignerFromKey(rawPk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create signer from raw private key %q: %s", rawPk, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ucertSigner, err := ssh.NewCertSigner(pcert.(*ssh.Certificate), usigner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create cert signer %q: %s", usigner, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ssh.PublicKeys(ucertSigner), nil
|
||||||
|
}
|
||||||
|
|
||||||
func readPrivateKey(pk string) (ssh.AuthMethod, error) {
|
func readPrivateKey(pk string) (ssh.AuthMethod, error) {
|
||||||
// We parse the private key on our own first so that we can
|
// We parse the private key on our own first so that we can
|
||||||
// show a nicer error if the private key has a password.
|
// show a nicer error if the private key has a password.
|
||||||
|
|
|
@ -14,6 +14,7 @@ func TestProvisioner_connInfo(t *testing.T) {
|
||||||
"user": "root",
|
"user": "root",
|
||||||
"password": "supersecret",
|
"password": "supersecret",
|
||||||
"private_key": "someprivatekeycontents",
|
"private_key": "someprivatekeycontents",
|
||||||
|
"certificate": "somecertificate",
|
||||||
"host": "127.0.0.1",
|
"host": "127.0.0.1",
|
||||||
"port": "22",
|
"port": "22",
|
||||||
"timeout": "30s",
|
"timeout": "30s",
|
||||||
|
@ -37,6 +38,9 @@ func TestProvisioner_connInfo(t *testing.T) {
|
||||||
if conf.PrivateKey != "someprivatekeycontents" {
|
if conf.PrivateKey != "someprivatekeycontents" {
|
||||||
t.Fatalf("bad: %v", conf)
|
t.Fatalf("bad: %v", conf)
|
||||||
}
|
}
|
||||||
|
if conf.Certificate != "somecertificate" {
|
||||||
|
t.Fatalf("bad: %v", conf)
|
||||||
|
}
|
||||||
if conf.Host != "127.0.0.1" {
|
if conf.Host != "127.0.0.1" {
|
||||||
t.Fatalf("bad: %v", conf)
|
t.Fatalf("bad: %v", conf)
|
||||||
}
|
}
|
||||||
|
@ -74,6 +78,7 @@ func TestProvisioner_connInfoIpv6(t *testing.T) {
|
||||||
"user": "root",
|
"user": "root",
|
||||||
"password": "supersecret",
|
"password": "supersecret",
|
||||||
"private_key": "someprivatekeycontents",
|
"private_key": "someprivatekeycontents",
|
||||||
|
"certificate": "somecertificate",
|
||||||
"host": "::1",
|
"host": "::1",
|
||||||
"port": "22",
|
"port": "22",
|
||||||
"timeout": "30s",
|
"timeout": "30s",
|
||||||
|
@ -101,14 +106,13 @@ func TestProvisioner_connInfoHostname(t *testing.T) {
|
||||||
r := &terraform.InstanceState{
|
r := &terraform.InstanceState{
|
||||||
Ephemeral: terraform.EphemeralState{
|
Ephemeral: terraform.EphemeralState{
|
||||||
ConnInfo: map[string]string{
|
ConnInfo: map[string]string{
|
||||||
"type": "ssh",
|
"type": "ssh",
|
||||||
"user": "root",
|
"user": "root",
|
||||||
"password": "supersecret",
|
"password": "supersecret",
|
||||||
"private_key": "someprivatekeycontents",
|
"private_key": "someprivatekeycontents",
|
||||||
"host": "example.com",
|
"host": "example.com",
|
||||||
"port": "22",
|
"port": "22",
|
||||||
"timeout": "30s",
|
"timeout": "30s",
|
||||||
|
|
||||||
"bastion_host": "example.com",
|
"bastion_host": "example.com",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -252,6 +252,10 @@ var connectionBlockSupersetSchema = &configschema.Block{
|
||||||
Type: cty.String,
|
Type: cty.String,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
"certificate": {
|
||||||
|
Type: cty.String,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
"host_key": {
|
"host_key": {
|
||||||
Type: cty.String,
|
Type: cty.String,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
|
|
@ -77,6 +77,10 @@ provisioner "file" {
|
||||||
[the `file` function](/docs/configuration/functions/file.html). This takes
|
[the `file` function](/docs/configuration/functions/file.html). This takes
|
||||||
preference over the password if provided.
|
preference over the password if provided.
|
||||||
|
|
||||||
|
* `certificate` - The contents of a signed CA Certificate. The certificate argument must be
|
||||||
|
used in conjunction with a `private_key`. These can
|
||||||
|
be loaded from a file on disk using the [the `file` function](/docs/configuration/functions/file.html).
|
||||||
|
|
||||||
* `agent` - Set to `false` to disable using `ssh-agent` to authenticate. On Windows the
|
* `agent` - Set to `false` to disable using `ssh-agent` to authenticate. On Windows the
|
||||||
only supported SSH authentication agent is
|
only supported SSH authentication agent is
|
||||||
[Pageant](http://the.earth.li/~sgtatham/putty/0.66/htmldoc/Chapter9.html#pageant).
|
[Pageant](http://the.earth.li/~sgtatham/putty/0.66/htmldoc/Chapter9.html#pageant).
|
||||||
|
|
Loading…
Reference in New Issue