golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/relui/sign/server_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  package sign
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"io"
    11  	"log"
    12  	"sync"
    13  	"testing"
    14  
    15  	"github.com/google/uuid"
    16  	"golang.org/x/build/internal/access"
    17  	"golang.org/x/build/internal/relui/protos"
    18  	"golang.org/x/net/nettest"
    19  	"google.golang.org/grpc"
    20  	"google.golang.org/grpc/codes"
    21  	"google.golang.org/grpc/credentials/insecure"
    22  	"google.golang.org/grpc/status"
    23  )
    24  
    25  func TestUpdateSigningStatus(t *testing.T) {
    26  	ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
    27  	client, server := setupSigningTest(t, ctx)
    28  	go fakeSigningServerClient(t, ctx, client)
    29  	wg := &sync.WaitGroup{}
    30  	wg.Add(3)
    31  	go signRequestSeries(t, ctx, "request-1", server, BuildGPG, wg)
    32  	go signRequestSeries(t, ctx, "request-2", server, BuildMacOS, wg)
    33  	go signRequestSeries(t, ctx, "request-3", server, BuildWindows, wg)
    34  	wg.Wait()
    35  }
    36  
    37  func TestUpdateSigningStatusError(t *testing.T) {
    38  	t.Run("unauthenticated client", func(t *testing.T) {
    39  		ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
    40  		client, _ := setupSigningTest(t, ctx)
    41  		stream, err := client.UpdateSigningStatus(context.Background())
    42  		if err != nil {
    43  			t.Fatalf("client.UpdateSigningStatus(ctx) = %v,  %s; want no error", stream, err)
    44  		}
    45  		wantCode := codes.Unauthenticated
    46  		if _, err = stream.Recv(); status.Code(err) != wantCode {
    47  			t.Fatalf("stream.Recv() = %s, want %s", status.Code(err), wantCode)
    48  		}
    49  	})
    50  	t.Run("non-existent signing request", func(t *testing.T) {
    51  		// skipping due to go.dev/issue/54654
    52  		t.Skip("skipping flaky test. see go.dev/issue/54654")
    53  
    54  		ctx := access.FakeContextWithOutgoingIAPAuth(context.Background(), fakeIAP())
    55  		client, server := setupSigningTest(t, ctx)
    56  		go fakeSigningServerClient(t, ctx, client)
    57  		if _, _, _, err := server.ArtifactSigningStatus(ctx, "not-exist"); err == nil {
    58  			t.Fatalf("ArtifactSigningStatus(ctx, %q) = _, _, nil; want error", "not-exist")
    59  		}
    60  	})
    61  }
    62  
    63  func signRequestSeries(t *testing.T, ctx context.Context, id string, server *SigningServer, buildT BuildType, wg *sync.WaitGroup) {
    64  	defer wg.Done()
    65  	const uri = "gs://foo/bar.tar.gz"
    66  	jobID, err := server.SignArtifact(ctx, buildT, []string{uri})
    67  	if err != nil {
    68  		t.Fatalf("SignArtifact(ctx, %q, %v, %q = %s; want no error", id, buildT, uri, err)
    69  	}
    70  	_, _, _, err = server.ArtifactSigningStatus(ctx, jobID)
    71  	if err != nil {
    72  		t.Fatalf("ArtifactSigningStatus(%q) = %s; want no error", id, err)
    73  	}
    74  	_, _, _, err = server.ArtifactSigningStatus(ctx, jobID)
    75  	if err != nil {
    76  		t.Fatalf("ArtifactSigningStatus(%q) = %s; want no error", id, err)
    77  	}
    78  }
    79  
    80  // fakeSigningServerClient is a simple implementation of how we expect the client side to perform.
    81  // A signing request initiates a signing job. The first status update should return a status of
    82  // running. The second status request will return a status of Completed.
    83  func fakeSigningServerClient(t *testing.T, ctx context.Context, client protos.ReleaseServiceClient) {
    84  	stream, err := client.UpdateSigningStatus(ctx)
    85  	if err != nil {
    86  		t.Fatalf("client.UpdateSigningStatus(ctx) = %v,  %s; want no error", stream, err)
    87  	}
    88  	requests := make(chan *protos.SigningRequest, 10)
    89  	go func() {
    90  		defer close(requests)
    91  		for {
    92  			in, err := stream.Recv()
    93  			if err == io.EOF {
    94  				return
    95  			}
    96  			if err != nil && status.Code(err) == codes.Canceled {
    97  				return
    98  			}
    99  			if err != nil {
   100  				t.Logf("client.stream.Recv() = nil, %s; want no error", err)
   101  				return
   102  			}
   103  			requests <- in
   104  		}
   105  	}()
   106  	jobs := make(map[string]int) // jobID -> request count
   107  	for request := range requests {
   108  		resp := &protos.SigningStatus{
   109  			MessageId:   request.GetMessageId(),
   110  			StatusOneof: nil,
   111  		}
   112  		switch r := request.RequestOneof.(type) {
   113  		case *protos.SigningRequest_Sign:
   114  			newJob := uuid.NewString() // Create a new pretend job with an ID.
   115  			jobs[newJob] = 1
   116  			resp.StatusOneof = &protos.SigningStatus_Started{
   117  				Started: &protos.StatusStarted{
   118  					JobId: newJob,
   119  				},
   120  			}
   121  		case *protos.SigningRequest_Status:
   122  			id := r.Status.JobId
   123  			c, ok := jobs[id]
   124  			if !ok {
   125  				resp.StatusOneof = &protos.SigningStatus_NotFound{}
   126  			} else if c == 1 {
   127  				jobs[id] = 2
   128  				resp.StatusOneof = &protos.SigningStatus_Running{}
   129  			} else if c == 2 {
   130  				delete(jobs, id)
   131  				resp.StatusOneof = &protos.SigningStatus_Completed{
   132  					Completed: &protos.StatusCompleted{
   133  						GcsUri: []string{"gs://private-bucket/some-file.tar.gz"},
   134  					},
   135  				}
   136  			}
   137  		}
   138  		if err := stream.Send(resp); err != nil {
   139  			log.Fatalf("client.stream.Send(%v) = %s, want no error", resp, err)
   140  		}
   141  	}
   142  	stream.CloseSend()
   143  }
   144  
   145  func setupSigningTest(t *testing.T, ctx context.Context) (protos.ReleaseServiceClient, *SigningServer) {
   146  	lis, err := nettest.NewLocalListener("tcp")
   147  	if err != nil {
   148  		t.Fatalf("nettest.NewLocalListener(\"tcp\") =  %s; want no error", err)
   149  	}
   150  	sopts := access.FakeIAPAuthInterceptorOptions()
   151  	s := grpc.NewServer(sopts...)
   152  	ss := NewServer()
   153  	protos.RegisterReleaseServiceServer(s, ss)
   154  	go s.Serve(lis)
   155  	// create GRPC client
   156  	copts := []grpc.DialOption{
   157  		grpc.WithBlock(),
   158  		grpc.WithTransportCredentials(insecure.NewCredentials()),
   159  	}
   160  	conn, err := grpc.DialContext(ctx, lis.Addr().String(), copts...)
   161  	if err != nil {
   162  		lis.Close()
   163  		t.Fatalf("grpc.Dial(%s, %+v) = nil, %s; want no error", lis.Addr().String(), copts, err)
   164  	}
   165  	sc := protos.NewReleaseServiceClient(conn)
   166  	t.Cleanup(func() {
   167  		conn.Close()
   168  		s.Stop()
   169  		lis.Close()
   170  	})
   171  	return sc, ss
   172  }
   173  
   174  func fakeAuthContext(ctx context.Context) context.Context {
   175  	return access.ContextWithIAP(ctx, access.IAPFields{
   176  		Email: "accounts.google.com:relui-user@google.com",
   177  		ID:    "accounts.google.com:randomuuidstuff",
   178  	})
   179  }
   180  
   181  func fakeIAP() access.IAPFields {
   182  	return fakeIAPWithUser("example", "randomuuidstuff")
   183  }
   184  
   185  func fakeIAPWithUser(user string, id string) access.IAPFields {
   186  	return access.IAPFields{
   187  		Email: fmt.Sprintf("accounts.google.com:%s@gmail.com", user),
   188  		ID:    fmt.Sprintf("accounts.google.com:%s", id),
   189  	}
   190  }