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  )