github.com/heimweh/terraform@v0.7.4/communicator/ssh/communicator_test.go (about) 1 // +build !race 2 3 package ssh 4 5 import ( 6 "bytes" 7 "fmt" 8 "net" 9 "regexp" 10 "strings" 11 "testing" 12 13 "github.com/hashicorp/terraform/communicator/remote" 14 "github.com/hashicorp/terraform/terraform" 15 "golang.org/x/crypto/ssh" 16 ) 17 18 // private key for mock server 19 const testServerPrivateKey = `-----BEGIN RSA PRIVATE KEY----- 20 MIIEpAIBAAKCAQEA19lGVsTqIT5iiNYRgnoY1CwkbETW5cq+Rzk5v/kTlf31XpSU 21 70HVWkbTERECjaYdXM2gGcbb+sxpq6GtXf1M3kVomycqhxwhPv4Cr6Xp4WT/jkFx 22 9z+FFzpeodGJWjOH6L2H5uX1Cvr9EDdQp9t9/J32/qBFntY8GwoUI/y/1MSTmMiF 23 tupdMODN064vd3gyMKTwrlQ8tZM6aYuyOPsutLlUY7M5x5FwMDYvnPDSeyT/Iw0z 24 s3B+NCyqeeMd2T7YzQFnRATj0M7rM5LoSs7DVqVriOEABssFyLj31PboaoLhOKgc 25 qoM9khkNzr7FHVvi+DhYM2jD0DwvqZLN6NmnLwIDAQABAoIBAQCGVj+kuSFOV1lT 26 +IclQYA6bM6uY5mroqcSBNegVxCNhWU03BxlW//BE9tA/+kq53vWylMeN9mpGZea 27 riEMIh25KFGWXqXlOOioH8bkMsqA8S7sBmc7jljyv+0toQ9vCCtJ+sueNPhxQQxH 28 D2YvUjfzBQ04I9+wn30BByDJ1QA/FoPsunxIOUCcRBE/7jxuLYcpR+JvEF68yYIh 29 atXRld4W4in7T65YDR8jK1Uj9XAcNeDYNpT/M6oFLx1aPIlkG86aCWRO19S1jLPT 30 b1ZAKHHxPMCVkSYW0RqvIgLXQOR62D0Zne6/2wtzJkk5UCjkSQ2z7ZzJpMkWgDgN 31 ifCULFPBAoGBAPoMZ5q1w+zB+knXUD33n1J+niN6TZHJulpf2w5zsW+m2K6Zn62M 32 MXndXlVAHtk6p02q9kxHdgov34Uo8VpuNjbS1+abGFTI8NZgFo+bsDxJdItemwC4 33 KJ7L1iz39hRN/ZylMRLz5uTYRGddCkeIHhiG2h7zohH/MaYzUacXEEy3AoGBANz8 34 e/msleB+iXC0cXKwds26N4hyMdAFE5qAqJXvV3S2W8JZnmU+sS7vPAWMYPlERPk1 35 D8Q2eXqdPIkAWBhrx4RxD7rNc5qFNcQWEhCIxC9fccluH1y5g2M+4jpMX2CT8Uv+ 36 3z+NoJ5uDTXZTnLCfoZzgZ4nCZVZ+6iU5U1+YXFJAoGBANLPpIV920n/nJmmquMj 37 orI1R/QXR9Cy56cMC65agezlGOfTYxk5Cfl5Ve+/2IJCfgzwJyjWUsFx7RviEeGw 38 64o7JoUom1HX+5xxdHPsyZ96OoTJ5RqtKKoApnhRMamau0fWydH1yeOEJd+TRHhc 39 XStGfhz8QNa1dVFvENczja1vAoGABGWhsd4VPVpHMc7lUvrf4kgKQtTC2PjA4xoc 40 QJ96hf/642sVE76jl+N6tkGMzGjnVm4P2j+bOy1VvwQavKGoXqJBRd5Apppv727g 41 /SM7hBXKFc/zH80xKBBgP/i1DR7kdjakCoeu4ngeGywvu2jTS6mQsqzkK+yWbUxJ 42 I7mYBsECgYB/KNXlTEpXtz/kwWCHFSYA8U74l7zZbVD8ul0e56JDK+lLcJ0tJffk 43 gqnBycHj6AhEycjda75cs+0zybZvN4x65KZHOGW/O/7OAWEcZP5TPb3zf9ned3Hl 44 NsZoFj52ponUM6+99A2CmezFCN16c4mbA//luWF+k3VVqR6BpkrhKw== 45 -----END RSA PRIVATE KEY-----` 46 47 var serverConfig = &ssh.ServerConfig{ 48 PasswordCallback: acceptUserPass("user", "pass"), 49 PublicKeyCallback: acceptPublicKey(testClientPublicKey), 50 } 51 52 func init() { 53 // Parse and set the private key of the server, required to accept connections 54 signer, err := ssh.ParsePrivateKey([]byte(testServerPrivateKey)) 55 if err != nil { 56 panic("unable to parse private key: " + err.Error()) 57 } 58 serverConfig.AddHostKey(signer) 59 } 60 61 func newMockLineServer(t *testing.T) string { 62 l, err := net.Listen("tcp", "127.0.0.1:0") 63 if err != nil { 64 t.Fatalf("Unable to listen for connection: %s", err) 65 } 66 67 go func() { 68 defer l.Close() 69 c, err := l.Accept() 70 if err != nil { 71 t.Errorf("Unable to accept incoming connection: %s", err) 72 } 73 defer c.Close() 74 conn, chans, _, err := ssh.NewServerConn(c, serverConfig) 75 if err != nil { 76 t.Logf("Handshaking error: %v", err) 77 } 78 t.Log("Accepted SSH connection") 79 80 for newChannel := range chans { 81 channel, requests, err := newChannel.Accept() 82 if err != nil { 83 t.Errorf("Unable to accept channel.") 84 } 85 t.Log("Accepted channel") 86 87 go func(in <-chan *ssh.Request) { 88 for req := range in { 89 if req.WantReply { 90 req.Reply(true, nil) 91 } 92 } 93 }(requests) 94 95 go func(newChannel ssh.NewChannel) { 96 conn.OpenChannel(newChannel.ChannelType(), nil) 97 }(newChannel) 98 99 defer channel.Close() 100 } 101 conn.Close() 102 }() 103 104 return l.Addr().String() 105 } 106 107 func TestNew_Invalid(t *testing.T) { 108 address := newMockLineServer(t) 109 parts := strings.Split(address, ":") 110 111 r := &terraform.InstanceState{ 112 Ephemeral: terraform.EphemeralState{ 113 ConnInfo: map[string]string{ 114 "type": "ssh", 115 "user": "user", 116 "password": "i-am-invalid", 117 "host": parts[0], 118 "port": parts[1], 119 "timeout": "30s", 120 }, 121 }, 122 } 123 124 c, err := New(r) 125 if err != nil { 126 t.Fatalf("error creating communicator: %s", err) 127 } 128 129 err = c.Connect(nil) 130 if err == nil { 131 t.Fatal("should have had an error connecting") 132 } 133 } 134 135 func TestStart(t *testing.T) { 136 address := newMockLineServer(t) 137 parts := strings.Split(address, ":") 138 139 r := &terraform.InstanceState{ 140 Ephemeral: terraform.EphemeralState{ 141 ConnInfo: map[string]string{ 142 "type": "ssh", 143 "user": "user", 144 "password": "pass", 145 "host": parts[0], 146 "port": parts[1], 147 "timeout": "30s", 148 }, 149 }, 150 } 151 152 c, err := New(r) 153 if err != nil { 154 t.Fatalf("error creating communicator: %s", err) 155 } 156 157 var cmd remote.Cmd 158 stdout := new(bytes.Buffer) 159 cmd.Command = "echo foo" 160 cmd.Stdout = stdout 161 162 err = c.Start(&cmd) 163 if err != nil { 164 t.Fatalf("error executing remote command: %s", err) 165 } 166 } 167 168 func TestScriptPath(t *testing.T) { 169 cases := []struct { 170 Input string 171 Pattern string 172 }{ 173 { 174 "/tmp/script.sh", 175 `^/tmp/script\.sh$`, 176 }, 177 { 178 "/tmp/script_%RAND%.sh", 179 `^/tmp/script_(\d+)\.sh$`, 180 }, 181 } 182 183 for _, tc := range cases { 184 r := &terraform.InstanceState{ 185 Ephemeral: terraform.EphemeralState{ 186 ConnInfo: map[string]string{ 187 "type": "ssh", 188 "script_path": tc.Input, 189 }, 190 }, 191 } 192 comm, err := New(r) 193 if err != nil { 194 t.Fatalf("err: %s", err) 195 } 196 output := comm.ScriptPath() 197 198 match, err := regexp.Match(tc.Pattern, []byte(output)) 199 if err != nil { 200 t.Fatalf("bad: %s\n\nerr: %s", tc.Input, err) 201 } 202 if !match { 203 t.Fatalf("bad: %s\n\n%s", tc.Input, output) 204 } 205 } 206 } 207 208 func TestScriptPath_randSeed(t *testing.T) { 209 // Pre GH-4186 fix, this value was the deterministic start the pseudorandom 210 // chain of unseeded math/rand values for Int31(). 211 staticSeedPath := "/tmp/terraform_1298498081.sh" 212 c, err := New(&terraform.InstanceState{}) 213 if err != nil { 214 t.Fatalf("err: %s", err) 215 } 216 path := c.ScriptPath() 217 if path == staticSeedPath { 218 t.Fatalf("rand not seeded! got: %s", path) 219 } 220 } 221 222 const testClientPrivateKey = `-----BEGIN RSA PRIVATE KEY----- 223 MIIEpQIBAAKCAQEAxOgNXOJ/jrRDxBZTSk2X9otNy9zcpUmJr5ifDi5sy7j2ZiQS 224 beBt1Wf+tLNWis8Cyq06ttEvjjRuM75yucyD6GrqDTXVCSm4PeOIQeDhPhw26wYZ 225 O0h/mFgrAiCwaEl8AFXVBInOhVn/0nqgUpkwckh76bjTsNeifkiugK3cfJOuBdrU 226 ZGbgugJjmYo4Cmv7ECo1gCFT5N+BAjOji3z3N5ClBH5HaWC77jH7kTH0k5cZ+ZRQ 227 tG9EqLyvWlnzTAR/Yly0JInkOa16Ms1Au5sIJwEoJfHKsNVK06IlLob53nblwYW0 228 H5gv1Kb/rS+nUkpPtA5YFShB7iZnPLPPv6qXSwIDAQABAoIBAC0UY1rMkB9/rbQK 229 2G6+bPgI1HrDydAdkeQdsOxyPH43jlG8GGwHYZ3l/S4pkLqewijcmACay6Rm5IP8 230 Kg/XfquLLqJvnKJIZuHkYaGTdn3dv8T21Hf6FRwvs0j9auW1TSpWfDpZwmpNPIBX 231 irTeVXUUmynbIrvt4km/IhRbuYrbbb964CLYD1DCl3XssXxoRNvPpc5EtOuyDorA 232 5g1hvZR1FqbOAmOuNQMYJociMuWB8mCaHb+o1Sg4A65OLXxoKs0cuwInJ/n/R4Z3 233 +GrV+x5ypBMxXgjjQtKMLEOujkvxs1cp34hkbhKMHHXxbMu5jl74YtGGsLLk90rq 234 ieZGIgECgYEA49OM9mMCrDoFUTZdJaSARA/MOXkdQgrqVTv9kUHee7oeMZZ6lS0i 235 bPU7g+Bq+UAN0qcw9x992eAElKjBA71Q5UbZYWh29BDMZd8bRJmwz4P6aSMoYLWI 236 Sr31caJU9LdmPFatarNeehjSJtlTuoZD9+NElnnUwNaTeOOo5UdhTQsCgYEA3UGm 237 QWoDUttFwK9oL2KL8M54Bx6EzNhnyk03WrqBbR7PJcPKnsF0R/0soQ+y0FW0r8RJ 238 TqG6ze5fUJII72B4GlMTQdP+BIvaKQttwWQTNIjbbv4NksF445gdVOO1xi9SvQ7k 239 uvMVxOb+1jL3HAFa3furWu2tJRDs6dhuaILLxsECgYEAhnhlKUBDYZhVbxvhWsh/ 240 lKymY/3ikQqUSX7BKa1xPiIalDY3YDllql4MpMgfG8L85asdMZ96ztB0o7H/Ss/B 241 IbLxt5bLLz+DBVXsaE82lyVU9h10RbCgI01/w3SHJHHjfBXFAcehKfvgfmGkE+IP 242 2A5ie1aphrCgFqh5FetNuQUCgYEAibL42I804FUtFR1VduAa/dRRqQSaW6528dWa 243 lLGsKRBalUNEEAeP6dmr89UEUVp1qEo94V0QGGe5FDi+rNPaC3AWdQqNdaDgNlkx 244 hoFU3oYqIuqj4ejc5rBd2N4a2+vJz3W8bokozDGC+iYf2mMRfUPKwj1XW9Er0OFs 245 3UhBsEECgYEAto/iJB7ZlCM7EyV9JW0tsEt83rbKMQ/Ex0ShbBIejej0Xx7bwx60 246 tVgay+bzJnNkXu6J4XVI98A/WsdI2kW4hL0STYdHV5HVA1l87V4ZbvTF2Bx8a8RJ 247 OF3UjpMTWKqOprw9nAu5VuwNRVzORF8ER8rgGeaR2/gsSvIYFy9VXq8= 248 -----END RSA PRIVATE KEY-----` 249 250 var testClientPublicKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDE6A1c4n+OtEPEFlNKTZf2i03L3NylSYmvmJ8OLmzLuPZmJBJt4G3VZ/60s1aKzwLKrTq20S+ONG4zvnK5zIPoauoNNdUJKbg944hB4OE+HDbrBhk7SH+YWCsCILBoSXwAVdUEic6FWf/SeqBSmTBySHvpuNOw16J+SK6Ardx8k64F2tRkZuC6AmOZijgKa/sQKjWAIVPk34ECM6OLfPc3kKUEfkdpYLvuMfuRMfSTlxn5lFC0b0SovK9aWfNMBH9iXLQkieQ5rXoyzUC7mwgnASgl8cqw1UrToiUuhvneduXBhbQfmC/Upv+tL6dSSk+0DlgVKEHuJmc8s8+/qpdL` 251 252 func acceptUserPass(goodUser, goodPass string) func(ssh.ConnMetadata, []byte) (*ssh.Permissions, error) { 253 return func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { 254 if c.User() == goodUser && string(pass) == goodPass { 255 return nil, nil 256 } 257 return nil, fmt.Errorf("password rejected for %q", c.User()) 258 } 259 } 260 261 func acceptPublicKey(keystr string) func(ssh.ConnMetadata, ssh.PublicKey) (*ssh.Permissions, error) { 262 goodkey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(keystr)) 263 if err != nil { 264 panic(fmt.Errorf("error parsing key: %s", err)) 265 } 266 return func(_ ssh.ConnMetadata, inkey ssh.PublicKey) (*ssh.Permissions, error) { 267 if bytes.Compare(inkey.Marshal(), goodkey.Marshal()) == 0 { 268 return nil, nil 269 } 270 271 return nil, fmt.Errorf("public key rejected") 272 } 273 }