golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/coordinator/remote/ssh_test.go (about) 1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build linux || darwin 6 7 package remote 8 9 import ( 10 "context" 11 "fmt" 12 "testing" 13 "time" 14 15 "github.com/google/go-cmp/cmp" 16 "golang.org/x/build/buildlet" 17 "golang.org/x/crypto/ssh" 18 "golang.org/x/net/nettest" 19 ) 20 21 func TestSignPublicSSHKey(t *testing.T) { 22 signer, err := ssh.ParsePrivateKey([]byte(devCertCAPrivate)) 23 if err != nil { 24 t.Fatalf("ssh.ParsePrivateKey() = %s", err) 25 } 26 ownerID := "accounts.google.com:userIDvalue" 27 sessionID := "user-maria-linux-amd64-12" 28 gotPubKey, err := SignPublicSSHKey(context.Background(), signer, []byte(devCertClientPublic), sessionID, ownerID, time.Minute) 29 if err != nil { 30 t.Fatalf("SignPublicSSHKey(...) = _, %s; want no error", err) 31 } 32 pubKey, _, _, _, err := ssh.ParseAuthorizedKey(gotPubKey) 33 if err != nil { 34 t.Fatalf("ssh.ParseAuthorizedKey(...) = %s; want no error", err) 35 } 36 certChecker := &ssh.CertChecker{} 37 wantPrinciple := fmt.Sprintf("%s@farmer.golang.org", sessionID) 38 pubKeyCert := pubKey.(*ssh.Certificate) 39 if err := certChecker.CheckCert(wantPrinciple, pubKeyCert); err != nil { 40 t.Fatalf("certChecker.CheckCert(%s, %+v) = %s", wantPrinciple, pubKeyCert, err) 41 } 42 if diff := cmp.Diff(pubKeyCert.SignatureKey.Marshal(), signer.PublicKey().Marshal()); diff != "" { 43 t.Fatalf("Public Keys mismatch (-want +got):\n%s", diff) 44 } 45 } 46 47 func TestHandleCertificateAuthFunc(t *testing.T) { 48 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 49 defer cancel() 50 51 addr, sp, s := setupSSHServer(t, ctx) 52 defer s.Close() 53 54 ownerID := "accounts.google.com:userIDvalue" 55 sessionID := sp.AddSession(ownerID, "maria", "linux-amd64", "xyz", &buildlet.FakeClient{}) 56 certSigner := parsePrivateKey(t, []byte(devCertCAPrivate)) 57 clientPubKey, err := SignPublicSSHKey(ctx, certSigner, []byte(devCertClientPublic), sessionID, ownerID, time.Minute) 58 if err != nil { 59 t.Fatalf("SignPublicSSHKey(...) = _, %s; want no error", err) 60 } 61 pubKey, _, _, _, err := ssh.ParseAuthorizedKey(clientPubKey) 62 if err != nil { 63 t.Fatalf("ParsePublicKey(...) = _, %s; want no error", err) 64 } 65 cert := pubKey.(*ssh.Certificate) 66 clientCertSigner := parsePrivateKey(t, []byte(devCertClientPrivate)) 67 clientSigner, err := ssh.NewCertSigner(cert, clientCertSigner) 68 if err != nil { 69 t.Fatalf("NewCertSigner(...) = _, %s; want no error", err) 70 } 71 clientConfig := &ssh.ClientConfig{ 72 User: sessionID, 73 Auth: []ssh.AuthMethod{ 74 ssh.PublicKeys(clientSigner), 75 }, 76 HostKeyCallback: ssh.InsecureIgnoreHostKey(), 77 Timeout: 5 * time.Second, 78 } 79 client, err := ssh.Dial("tcp", addr, clientConfig) 80 if err != nil { 81 t.Fatalf("Dial(...) = _, %s; want no error", err) 82 } 83 client.Close() 84 } 85 86 func TestHandleCertificateAuthFuncErrors(t *testing.T) { 87 t.Run("no certificate", func(t *testing.T) { 88 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 89 defer cancel() 90 91 addr, sp, s := setupSSHServer(t, ctx) 92 defer s.Close() 93 94 ownerID := "accounts.google.com:userIDvalue" 95 sessionID := sp.AddSession(ownerID, "maria", "linux-amd64", "xyz", &buildlet.FakeClient{}) 96 clientSigner := parsePrivateKey(t, []byte(devCertClientPrivate)) 97 clientConfig := &ssh.ClientConfig{ 98 User: sessionID, 99 Auth: []ssh.AuthMethod{ 100 ssh.PublicKeys(clientSigner), 101 }, 102 HostKeyCallback: ssh.InsecureIgnoreHostKey(), 103 Timeout: 5 * time.Second, 104 } 105 _, err := ssh.Dial("tcp", addr, clientConfig) 106 if err == nil { 107 t.Fatal("Dial(...) = client, nil; want error") 108 } 109 }) 110 111 t.Run("wrong certificate signer", func(t *testing.T) { 112 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 113 defer cancel() 114 115 addr, sp, s := setupSSHServer(t, ctx) 116 defer s.Close() 117 118 ownerID := "accounts.google.com:userIDvalue" 119 sessionID := sp.AddSession(ownerID, "maria", "linux-amd64", "xyz", &buildlet.FakeClient{}) 120 certSigner := parsePrivateKey(t, []byte(devCertAlternateClientPrivate)) 121 clientPubKey, err := SignPublicSSHKey(ctx, certSigner, []byte(devCertClientPublic), sessionID, ownerID, time.Minute) 122 if err != nil { 123 t.Fatalf("SignPublicSSHKey(...) = _, %s; want no error", err) 124 } 125 pubKey, _, _, _, err := ssh.ParseAuthorizedKey(clientPubKey) 126 if err != nil { 127 t.Fatalf("ParsePublicKey(...) = _, %s; want no error", err) 128 } 129 cert := pubKey.(*ssh.Certificate) 130 clientCertSigner := parsePrivateKey(t, []byte(devCertClientPrivate)) 131 clientSigner, err := ssh.NewCertSigner(cert, clientCertSigner) 132 if err != nil { 133 t.Fatalf("NewCertSigner(...) = _, %s; want no error", err) 134 } 135 clientConfig := &ssh.ClientConfig{ 136 User: sessionID, 137 Auth: []ssh.AuthMethod{ 138 ssh.PublicKeys(clientSigner), 139 }, 140 HostKeyCallback: ssh.InsecureIgnoreHostKey(), 141 Timeout: 5 * time.Second, 142 } 143 _, err = ssh.Dial("tcp", addr, clientConfig) 144 if err == nil { 145 t.Fatalf("Dial(...) = _, %s; want no error", err) 146 } 147 }) 148 149 t.Run("wrong user", func(t *testing.T) { 150 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 151 defer cancel() 152 153 addr, sp, s := setupSSHServer(t, ctx) 154 defer s.Close() 155 156 ownerID := "accounts.google.com:userIDvalue" 157 sessionID := sp.AddSession(ownerID, "maria", "linux-amd64", "xyz", &buildlet.FakeClient{}) 158 certSigner := parsePrivateKey(t, []byte(devCertCAPrivate)) 159 clientPubKey, err := SignPublicSSHKey(ctx, certSigner, []byte(devCertClientPublic), sessionID, ownerID, time.Minute) 160 if err != nil { 161 t.Fatalf("SignPublicSSHKey(...) = _, %s; want no error", err) 162 } 163 pubKey, _, _, _, err := ssh.ParseAuthorizedKey(clientPubKey) 164 if err != nil { 165 t.Fatalf("ParsePublicKey(...) = _, %s; want no error", err) 166 } 167 cert := pubKey.(*ssh.Certificate) 168 clientCertSigner := parsePrivateKey(t, []byte(devCertClientPrivate)) 169 clientSigner, err := ssh.NewCertSigner(cert, clientCertSigner) 170 if err != nil { 171 t.Fatalf("NewCertSigner(...) = _, %s; want no error", err) 172 } 173 clientConfig := &ssh.ClientConfig{ 174 User: sessionID + "_i_do_not_exist", 175 Auth: []ssh.AuthMethod{ 176 ssh.PublicKeys(clientSigner), 177 }, 178 HostKeyCallback: ssh.InsecureIgnoreHostKey(), 179 Timeout: 5 * time.Second, 180 } 181 _, err = ssh.Dial("tcp", addr, clientConfig) 182 if err == nil { 183 t.Fatal("Dial(...) = _, nil; want error") 184 } 185 }) 186 187 t.Run("wrong principle", func(t *testing.T) { 188 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 189 defer cancel() 190 191 addr, sp, s := setupSSHServer(t, ctx) 192 defer s.Close() 193 194 ownerID := "accounts.google.com:userIDvalue" 195 sessionID := sp.AddSession(ownerID, "maria", "linux-amd64", "xyz", &buildlet.FakeClient{}) 196 certSigner := parsePrivateKey(t, []byte(devCertCAPrivate)) 197 clientPubKey, err := SignPublicSSHKey(ctx, certSigner, []byte(devCertClientPublic), sessionID+"WRONG", ownerID, time.Minute) 198 if err != nil { 199 t.Fatalf("SignPublicSSHKey(...) = _, %s; want no error", err) 200 } 201 pubKey, _, _, _, err := ssh.ParseAuthorizedKey(clientPubKey) 202 if err != nil { 203 t.Fatalf("ParsePublicKey(...) = _, %s; want no error", err) 204 } 205 cert := pubKey.(*ssh.Certificate) 206 clientCertSigner := parsePrivateKey(t, []byte(devCertClientPrivate)) 207 clientSigner, err := ssh.NewCertSigner(cert, clientCertSigner) 208 if err != nil { 209 t.Fatalf("NewCertSigner(...) = _, %s; want no error", err) 210 } 211 clientConfig := &ssh.ClientConfig{ 212 User: sessionID, 213 Auth: []ssh.AuthMethod{ 214 ssh.PublicKeys(clientSigner), 215 }, 216 HostKeyCallback: ssh.InsecureIgnoreHostKey(), 217 Timeout: 5 * time.Second, 218 } 219 _, err = ssh.Dial("tcp", addr, clientConfig) 220 if err == nil { 221 t.Fatal("Dial(...) = _, nil; want error") 222 } 223 }) 224 225 t.Run("wrong owner", func(t *testing.T) { 226 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 227 defer cancel() 228 229 addr, sp, s := setupSSHServer(t, ctx) 230 defer s.Close() 231 232 ownerID := "accounts.google.com:userIDvalue" 233 sessionID := sp.AddSession(ownerID, "maria", "linux-amd64", "xyz", &buildlet.FakeClient{}) 234 certSigner := parsePrivateKey(t, []byte(devCertCAPrivate)) 235 clientPubKey, err := SignPublicSSHKey(ctx, certSigner, []byte(devCertClientPublic), sessionID, ownerID+"WRONG", time.Minute) 236 if err != nil { 237 t.Fatalf("SignPublicSSHKey(...) = _, %s; want no error", err) 238 } 239 pubKey, _, _, _, err := ssh.ParseAuthorizedKey(clientPubKey) 240 if err != nil { 241 t.Fatalf("ParsePublicKey(...) = _, %s; want no error", err) 242 } 243 cert := pubKey.(*ssh.Certificate) 244 clientCertSigner := parsePrivateKey(t, []byte(devCertClientPrivate)) 245 clientSigner, err := ssh.NewCertSigner(cert, clientCertSigner) 246 if err != nil { 247 t.Fatalf("NewCertSigner(...) = _, %s; want no error", err) 248 } 249 clientConfig := &ssh.ClientConfig{ 250 User: sessionID, 251 Auth: []ssh.AuthMethod{ 252 ssh.PublicKeys(clientSigner), 253 }, 254 HostKeyCallback: ssh.InsecureIgnoreHostKey(), 255 Timeout: 5 * time.Second, 256 } 257 _, err = ssh.Dial("tcp", addr, clientConfig) 258 if err == nil { 259 t.Fatal("Dial(...) = _, nil; want error") 260 } 261 }) 262 } 263 264 func setupSSHServer(t *testing.T, ctx context.Context) (addr string, sp *SessionPool, s *SSHServer) { 265 sp = NewSessionPool(ctx) 266 l, err := nettest.NewLocalListener("tcp") 267 if err != nil { 268 t.Fatalf("nettest.NewLocalListener(tcp) = _, %s; want no error", err) 269 } 270 addr = l.Addr().String() 271 s, err = NewSSHServer(addr, []byte(devCertAlternateClientPrivate), []byte(devCertCAPublic), []byte(devCertCAPrivate), sp) 272 if err != nil { 273 t.Fatalf("NewSSHServer(...) = %s; want no error", err) 274 } 275 go s.serve(l) 276 if err != nil { 277 t.Fatalf("server.serve(l) = %s; want no error", err) 278 } 279 return 280 } 281 282 func parsePrivateKey(t *testing.T, pemEncoded []byte) ssh.Signer { 283 cert, err := ssh.ParsePrivateKey(pemEncoded) 284 if err != nil { 285 t.Fatalf("ssh.ParsePrivateKey() = _, %s; want no error", err) 286 } 287 return cert 288 } 289 290 const ( 291 // devCertCAPrivate is a private SSH CA certificate to be used for development. 292 devCertCAPrivate = `-----BEGIN OPENSSH PRIVATE KEY----- 293 b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 294 QyNTUxOQAAACCVd2FJ3Db/oV53iRDt1RLscTn41hYXbunuCWIlXze2WAAAAJhjy3ePY8t3 295 jwAAAAtzc2gtZWQyNTUxOQAAACCVd2FJ3Db/oV53iRDt1RLscTn41hYXbunuCWIlXze2WA 296 AAAEALuUJMb/rEaFNa+vn5RejeoBiiViyda7djgEvMnQ8fRJV3YUncNv+hXneJEO3VEuxx 297 OfjWFhdu6e4JYiVfN7ZYAAAAE3Rlc3R1c2VyQGdvbGFuZy5vcmcBAg== 298 -----END OPENSSH PRIVATE KEY-----` 299 300 // devCertCAPublic is a public SSH CA certificate to be used for development. 301 devCertCAPublic = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJV3YUncNv+hXneJEO3VEuxxOfjWFhdu6e4JYiVfN7ZY testuser@golang.org` 302 303 // devCertClientPrivate is a private SSH certificate to be used for development. 304 devCertClientPrivate = `-----BEGIN OPENSSH PRIVATE KEY----- 305 b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 306 QyNTUxOQAAACBxCM6ADdHnjTIHG/IpMa3z32CLwtu3BDUR3k2NNbI3owAAAKDFZ7xtxWe8 307 bQAAAAtzc2gtZWQyNTUxOQAAACBxCM6ADdHnjTIHG/IpMa3z32CLwtu3BDUR3k2NNbI3ow 308 AAAECidrOyYbTlYxyBSPP7W/UHk3Si2dgWSfkT+eEIETcvqHEIzoAN0eeNMgcb8ikxrfPf 309 YIvC27cENRHeTY01sjejAAAAFnRlc3RfY2xpZW50QGdvbGFuZy5vcmcBAgMEBQYH 310 -----END OPENSSH PRIVATE KEY-----` 311 312 // devCertClientPublic is a public SSH certificate to be used for development. 313 devCertClientPublic = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHEIzoAN0eeNMgcb8ikxrfPfYIvC27cENRHeTY01sjej test_client@golang.org` 314 315 // devCertAlternateClientPrivate is a private SSH certificate to be used for development. 316 devCertAlternateClientPrivate = `-----BEGIN OPENSSH PRIVATE KEY----- 317 b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 318 QyNTUxOQAAACDOj8K2lbCSv+LojNcrUf0XH1vqknuEZBkAceiBHuNuEQAAAKDYNRtZ2DUb 319 WQAAAAtzc2gtZWQyNTUxOQAAACDOj8K2lbCSv+LojNcrUf0XH1vqknuEZBkAceiBHuNuEQ 320 AAAEDS4G3tQt5S4v7CD+DVyT/mwOKgIScIgFOpFt/EsCXL9M6PwraVsJK/4uiM1ytR/Rcf 321 W+qSe4RkGQBx6IEe424RAAAAF3Rlc3RfZGlzY2FyZEBnb2xhbmcub3JnAQIDBAUG 322 -----END OPENSSH PRIVATE KEY-----` 323 324 // devCertAlternateClientPublic is a public SSH to be used for development. 325 devCertAlternateClientPublic = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM6PwraVsJK/4uiM1ytR/RcfW+qSe4RkGQBx6IEe424R test_discard@golang.org` 326 )