github.com/google/fleetspeak@v0.1.15-0.20240426164851-4f31f62c1aea/fleetspeak/src/inttesting/integrationtest/clone.go (about)

     1  // Copyright 2018 Google Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package integrationtest
    16  
    17  import (
    18  	"context"
    19  	"crypto/x509"
    20  	"net"
    21  	"os"
    22  	"path"
    23  	"path/filepath"
    24  	"testing"
    25  	"time"
    26  
    27  	log "github.com/golang/glog"
    28  	"google.golang.org/grpc"
    29  	"google.golang.org/grpc/credentials/insecure"
    30  
    31  	"github.com/google/fleetspeak/fleetspeak/src/client"
    32  	"github.com/google/fleetspeak/fleetspeak/src/client/config"
    33  	chttps "github.com/google/fleetspeak/fleetspeak/src/client/https"
    34  	cservice "github.com/google/fleetspeak/fleetspeak/src/client/service"
    35  	"github.com/google/fleetspeak/fleetspeak/src/common"
    36  	"github.com/google/fleetspeak/fleetspeak/src/comtesting"
    37  	"github.com/google/fleetspeak/fleetspeak/src/server"
    38  	"github.com/google/fleetspeak/fleetspeak/src/server/admin"
    39  	"github.com/google/fleetspeak/fleetspeak/src/server/comms"
    40  	"github.com/google/fleetspeak/fleetspeak/src/server/db"
    41  	"github.com/google/fleetspeak/fleetspeak/src/server/https"
    42  	"github.com/google/fleetspeak/fleetspeak/src/server/sertesting"
    43  	"github.com/google/fleetspeak/fleetspeak/src/server/service"
    44  
    45  	clpb "github.com/google/fleetspeak/fleetspeak/src/client/proto/fleetspeak_client"
    46  	fspb "github.com/google/fleetspeak/fleetspeak/src/common/proto/fleetspeak"
    47  	sgrpc "github.com/google/fleetspeak/fleetspeak/src/server/proto/fleetspeak_server"
    48  	spb "github.com/google/fleetspeak/fleetspeak/src/server/proto/fleetspeak_server"
    49  	durationpb "google.golang.org/protobuf/types/known/durationpb"
    50  )
    51  
    52  // CloneHandlingTest runs an integration test using ds in which cloned clients
    53  // are dealt with.
    54  func CloneHandlingTest(t *testing.T, ds db.Store) {
    55  	tmpConfPath := t.TempDir()
    56  
    57  	fin := sertesting.SetClientCacheMaxAge(time.Second)
    58  	defer fin()
    59  
    60  	// Create FS server certs and server communicator.
    61  	cert, key, err := comtesting.ServerCert()
    62  	if err != nil {
    63  		t.Fatal(err)
    64  	}
    65  	addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
    66  	if err != nil {
    67  		t.Fatal(err)
    68  	}
    69  	listener, err := net.ListenTCP("tcp", addr)
    70  	if err != nil {
    71  		t.Fatal(err)
    72  	}
    73  	com, err := https.NewCommunicator(https.Params{Listener: listener, Cert: cert, Key: key})
    74  	if err != nil {
    75  		t.Fatal(err)
    76  	}
    77  	log.Infof("Communicator listening to: %v", listener.Addr())
    78  
    79  	// Create a FS server.
    80  	server, err := server.MakeServer(
    81  		&spb.ServerConfig{
    82  			Services: []*spb.ServiceConfig{{
    83  				Name:    "NOOP Service",
    84  				Factory: "NOOP",
    85  			}},
    86  			BroadcastPollTime: durationpb.New(time.Second),
    87  		},
    88  		server.Components{
    89  			Datastore:        ds,
    90  			ServiceFactories: map[string]service.Factory{"NOOP": service.NOOPFactory},
    91  			Communicators:    []comms.Communicator{com}})
    92  	if err != nil {
    93  		t.Fatal(err)
    94  	}
    95  	defer server.Stop()
    96  
    97  	// Create a FS Admin interface, and start it listening.
    98  	as := admin.NewServer(ds, nil)
    99  	gas := grpc.NewServer()
   100  	sgrpc.RegisterAdminServer(gas, as)
   101  	aas, err := net.ResolveTCPAddr("tcp", "localhost:0")
   102  	if err != nil {
   103  		t.Fatal(err)
   104  	}
   105  	asl, err := net.ListenTCP("tcp", aas)
   106  	if err != nil {
   107  		t.Fatal(err)
   108  	}
   109  	defer gas.Stop()
   110  	go func() {
   111  		log.Infof("Finished with AdminServer[%v]: %v", asl.Addr(), gas.Serve(asl))
   112  	}()
   113  	conn, err := grpc.Dial(asl.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
   114  	if err != nil {
   115  		t.Fatalf("unable to connect to FS AdminInterface: %v", err)
   116  	}
   117  	admin := sgrpc.NewAdminClient(conn)
   118  
   119  	// Prepare a general client config.
   120  	configPath := filepath.Join(tmpConfPath, "client_1")
   121  	if err := os.Mkdir(configPath, 0777); err != nil {
   122  		t.Fatalf("Unable to create client directory [%v]: %v", configPath, err)
   123  	}
   124  
   125  	ph, err := config.NewFilesystemPersistenceHandler(configPath, filepath.Join(configPath, "writeback"))
   126  	if err != nil {
   127  		t.Fatal(err)
   128  	}
   129  
   130  	conf := config.Configuration{
   131  		PersistenceHandler: ph,
   132  		TrustedCerts:       x509.NewCertPool(),
   133  		ClientLabels: []*fspb.Label{
   134  			{ServiceName: "client", Label: "clone_test"},
   135  		},
   136  		FixedServices: []*fspb.ClientServiceConfig{{Name: "NOOP", Factory: "NOOP"}},
   137  		Servers:       []string{listener.Addr().String()},
   138  		CommunicatorConfig: &clpb.CommunicatorConfig{
   139  			MaxPollDelaySeconds:    2,
   140  			MaxBufferDelaySeconds:  1,
   141  			MinFailureDelaySeconds: 1,
   142  		},
   143  	}
   144  	if !conf.TrustedCerts.AppendCertsFromPEM(cert) {
   145  		t.Fatal("unable to add server cert to pool")
   146  	}
   147  
   148  	cl, err := client.New(
   149  		conf,
   150  		client.Components{
   151  			ServiceFactories: map[string]cservice.Factory{"NOOP": cservice.NOOPFactory},
   152  			Communicator:     &chttps.Communicator{},
   153  		})
   154  	if err != nil {
   155  		t.Fatalf("Unable to start initial client: %v", err)
   156  	}
   157  	cl.Stop()
   158  
   159  	wb, err := os.ReadFile(filepath.Join(configPath, "writeback"))
   160  	if err != nil {
   161  		t.Fatalf("Unable to read example writeback file: %v", err)
   162  	}
   163  	for _, pc := range []string{"client_2", "client_3", "client_4", "client_5"} {
   164  		configPath := filepath.Join(tmpConfPath, pc)
   165  		if err := os.Mkdir(configPath, 0777); err != nil {
   166  			t.Fatalf("Unable to create client directory [%v]: %v", configPath, err)
   167  		}
   168  		os.WriteFile(filepath.Join(configPath, "writeback"), wb, 0644)
   169  	}
   170  
   171  	// We have 5 config dirs with the same key. Start a client running against
   172  	// each of them.
   173  
   174  	for i, pc := range []string{"client_1", "client_2", "client_3", "client_4", "client_5"} {
   175  		pcConfigPath := filepath.Join(tmpConfPath, pc)
   176  		ph, err := config.NewFilesystemPersistenceHandler(pcConfigPath, path.Join(pcConfigPath, "writeback"))
   177  		if err != nil {
   178  			t.Fatal(err)
   179  		}
   180  
   181  		conf := config.Configuration{
   182  			PersistenceHandler: ph,
   183  			TrustedCerts:       x509.NewCertPool(),
   184  			ClientLabels: []*fspb.Label{
   185  				{ServiceName: "client", Label: "clone_test"},
   186  			},
   187  			FixedServices: []*fspb.ClientServiceConfig{{Name: "NOOP", Factory: "NOOP"}},
   188  			Servers:       []string{listener.Addr().String()},
   189  			CommunicatorConfig: &clpb.CommunicatorConfig{
   190  				MaxPollDelaySeconds:    2,
   191  				MaxBufferDelaySeconds:  1,
   192  				MinFailureDelaySeconds: 1,
   193  			},
   194  		}
   195  		if !conf.TrustedCerts.AppendCertsFromPEM(cert) {
   196  			t.Fatal("unable to add server cert to pool")
   197  		}
   198  		cl, err := client.New(
   199  			conf,
   200  			client.Components{
   201  				ServiceFactories: map[string]cservice.Factory{"NOOP": cservice.NOOPFactory},
   202  				Communicator:     &chttps.Communicator{},
   203  			})
   204  		if err != nil {
   205  			t.Fatalf("Unable to start client %d: %v", i+1, err)
   206  		}
   207  		defer cl.Stop()
   208  	}
   209  
   210  	var id common.ClientID
   211  
   212  	// Wait for one client id to show up through the admin interface.
   213  	ctx, fin := context.WithTimeout(context.Background(), 20*time.Second)
   214  	for {
   215  		res, err := admin.ListClients(ctx, &spb.ListClientsRequest{})
   216  		if err != nil {
   217  			t.Fatalf("Unable to list clients: %v", err)
   218  		}
   219  		if len(res.Clients) > 1 {
   220  			t.Fatalf("Only expected 1 client, got %d", len(res.Clients))
   221  		}
   222  		if len(res.Clients) == 1 {
   223  			cid, err := common.BytesToClientID(res.Clients[0].ClientId)
   224  			if err != nil {
   225  				t.Fatalf("Unable to parse listed client id: %v", err)
   226  			}
   227  			id = cid
   228  			break
   229  		}
   230  		select {
   231  		case <-ctx.Done():
   232  			t.Fatalf("Timed out waiting for client id: %v", ctx.Err())
   233  		case <-time.After(200 * time.Millisecond):
   234  			continue
   235  		}
   236  	}
   237  	fin()
   238  
   239  	// Wait for 20 contacts.
   240  	ctx, fin = context.WithTimeout(context.Background(), 20*time.Second)
   241  	for {
   242  		res, err := admin.ListClientContacts(ctx, &spb.ListClientContactsRequest{ClientId: id.Bytes()})
   243  		if err != nil {
   244  			t.Fatalf("Unable to list client contacts: %v", err)
   245  		}
   246  		if len(res.Contacts) >= 20 {
   247  			break
   248  		}
   249  		select {
   250  		case <-ctx.Done():
   251  			t.Fatalf("Timed out waiting for at least 20 contacts: %v", ctx.Err())
   252  		case <-time.After(200 * time.Millisecond):
   253  			continue
   254  		}
   255  	}
   256  	fin()
   257  
   258  	// Blacklist the id.
   259  	ctx, fin = context.WithTimeout(context.Background(), 5*time.Second)
   260  	if _, err := admin.BlacklistClient(ctx, &spb.BlacklistClientRequest{ClientId: id.Bytes()}); err != nil {
   261  		t.Fatalf("Unable to blacklist client: %v", err)
   262  	}
   263  	fin()
   264  
   265  	// Wait for a total of 6 client ids to show up in the admin interface.
   266  	ctx, fin = context.WithTimeout(context.Background(), 20*time.Second)
   267  	for {
   268  		res, err := admin.ListClients(ctx, &spb.ListClientsRequest{})
   269  		if err != nil {
   270  			t.Fatalf("Unable to list clients: %v", err)
   271  		}
   272  		if len(res.Clients) == 6 {
   273  			break
   274  		}
   275  		select {
   276  		case <-ctx.Done():
   277  			t.Fatalf("Timed out waiting for clients to rekey: %v", ctx.Err())
   278  		case <-time.After(200 * time.Millisecond):
   279  			continue
   280  		}
   281  	}
   282  	fin()
   283  }