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 }