github.com/google/fleetspeak@v0.1.15-0.20240426164851-4f31f62c1aea/fleetspeak/src/admin/cli/cli.go (about) 1 // Copyright 2017 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 cli contains methods useful for implementing administrative command 16 // line utilities. 17 package cli 18 19 import ( 20 "context" 21 "fmt" 22 "os" 23 "path" 24 "sort" 25 "strconv" 26 "strings" 27 "time" 28 29 log "github.com/golang/glog" 30 "google.golang.org/grpc" 31 32 "github.com/google/fleetspeak/fleetspeak/src/admin/history" 33 "github.com/google/fleetspeak/fleetspeak/src/common" 34 35 sgrpc "github.com/google/fleetspeak/fleetspeak/src/server/proto/fleetspeak_server" 36 spb "github.com/google/fleetspeak/fleetspeak/src/server/proto/fleetspeak_server" 37 ) 38 39 // dateFmt is a fairly dense, 23 character date format string, suitable for 40 // tabular date information. 41 const dateFmt = "15:04:05.000 2006.01.02" 42 43 // Usage prints usage information describing the command line flags and behavior 44 // of programs based on Execute. 45 func Usage() { 46 n := path.Base(os.Args[0]) 47 fmt.Fprintf(os.Stderr, 48 "Usage:\n"+ 49 " %s listclients [<client_id>...]\n"+ 50 " %s listcontacts <client_id> [limit]\n"+ 51 " %s analysehistory <client_id>\n"+ 52 " %s blacklistclient <client_id>\n"+ 53 "\n", n, n, n, n) 54 } 55 56 // Execute examines command line flags and executes one of the standard command line 57 // actions, as summarized by Usage. It requires a grpc connection to an admin server 58 // and the command line parameters to interpret. 59 func Execute(conn *grpc.ClientConn, args ...string) { 60 admin := sgrpc.NewAdminClient(conn) 61 62 if len(args) == 0 { 63 fmt.Fprint(os.Stderr, "A command is required.\n") 64 Usage() 65 os.Exit(1) 66 } 67 68 switch args[0] { 69 case "listclients": 70 ListClients(admin, args[1:]...) 71 case "listcontacts": 72 ListContacts(admin, args[1:]...) 73 case "analysehistory": 74 AnalyseHistory(admin, args[1:]...) 75 case "blacklistclient": 76 BlacklistClient(admin, args[1:]...) 77 default: 78 fmt.Fprintf(os.Stderr, "Unknown command: %v\n", args[0]) 79 Usage() 80 os.Exit(1) 81 } 82 } 83 84 // ListClients prints a list of all clients in the fleetspeak system. 85 func ListClients(c sgrpc.AdminClient, args ...string) { 86 var ids [][]byte 87 for i, arg := range args { 88 id, err := common.StringToClientID(arg) 89 if err != nil { 90 log.Exitf("Unable to convert [%s] (index %d) to client id: %v", arg, i, err) 91 } 92 ids = append(ids, id.Bytes()) 93 } 94 ctx := context.Background() 95 res, err := c.ListClients(ctx, &spb.ListClientsRequest{ClientIds: ids}, grpc.MaxCallRecvMsgSize(1024*1024*1024)) 96 if err != nil { 97 log.Exitf("ListClients RPC failed: %v", err) 98 } 99 if len(res.Clients) == 0 { 100 fmt.Println("No clients found.") 101 return 102 } 103 sort.Sort(byContactTime(res.Clients)) 104 fmt.Printf("%-16s %-23s %s\n", "Client ID:", "Last Seen:", "Labels:") 105 for _, cl := range res.Clients { 106 id, err := common.BytesToClientID(cl.ClientId) 107 if err != nil { 108 log.Errorf("Ignoring invalid client id [%v], %v", cl.ClientId, err) 109 continue 110 } 111 var ls []string 112 for _, l := range cl.Labels { 113 ls = append(ls, l.ServiceName+":"+l.Label) 114 } 115 ts := cl.LastContactTime.AsTime() 116 tag := "" 117 if cl.Blacklisted { 118 tag = " *blacklisted*" 119 } 120 fmt.Printf("%v %v [%v]%s\n", id, ts.Format(dateFmt), strings.Join(ls, ","), tag) 121 } 122 } 123 124 // byContactTime adapts []*spb.Client for use by sort.Sort. 125 type byContactTime []*spb.Client 126 127 func (b byContactTime) Len() int { return len(b) } 128 func (b byContactTime) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 129 func (b byContactTime) Less(i, j int) bool { return contactTime(b[i]).Before(contactTime(b[j])) } 130 131 func contactTime(c *spb.Client) time.Time { 132 return time.Unix(c.LastContactTime.Seconds, int64(c.LastContactTime.Nanos)) 133 } 134 135 // ListContacts prints a list contacts that the system has recorded for a 136 // client. args[0] must be a client id. If present, args[1] limits to the most 137 // recent args[1] contacts. 138 func ListContacts(c sgrpc.AdminClient, args ...string) { 139 if len(args) == 0 || len(args) > 2 { 140 Usage() 141 os.Exit(1) 142 } 143 id, err := common.StringToClientID(args[0]) 144 if err != nil { 145 log.Exitf("Unable to parse %s as client id: %v", args[0], err) 146 } 147 var lim int 148 if len(args) == 2 { 149 lim, err = strconv.Atoi(args[1]) 150 if err != nil { 151 log.Exitf("Unable to parse %s as a limit: %v", args[1], err) 152 } 153 } 154 155 ctx := context.Background() 156 res, err := c.ListClientContacts(ctx, &spb.ListClientContactsRequest{ClientId: id.Bytes()}, grpc.MaxCallRecvMsgSize(1024*1024*1024)) 157 if err != nil { 158 log.Exitf("ListClientContacts RPC failed: %v", err) 159 } 160 if len(res.Contacts) == 0 { 161 fmt.Println("No contacts found.") 162 return 163 } 164 165 fmt.Printf("Found %d contacts.\n", len(res.Contacts)) 166 167 sort.Sort(byTimestamp(res.Contacts)) 168 fmt.Printf("%-23s %s", "Timestamp:", "Observed IP:\n") 169 for i, con := range res.Contacts { 170 if lim > 0 && i > lim { 171 break 172 } 173 if err := con.Timestamp.CheckValid(); err != nil { 174 log.Errorf("Unable to parse timestamp for contact: %v", err) 175 continue 176 } 177 ts := con.Timestamp.AsTime() 178 fmt.Printf("%s %s\n", ts.Format(dateFmt), con.ObservedAddress) 179 } 180 } 181 182 // byTimestamp adapts []*spb.ClientContact for use by sort.Sort. Places most 183 // recent contacts first. 184 type byTimestamp []*spb.ClientContact 185 186 func (b byTimestamp) Len() int { return len(b) } 187 func (b byTimestamp) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 188 func (b byTimestamp) Less(i, j int) bool { return timestamp(b[i]).After(timestamp(b[j])) } 189 190 func timestamp(c *spb.ClientContact) time.Time { 191 return time.Unix(c.Timestamp.Seconds, int64(c.Timestamp.Nanos)) 192 } 193 194 // AnalyseHistory prints a summary analysis of a client's history. args[0] must 195 // be a client id. 196 func AnalyseHistory(c sgrpc.AdminClient, args ...string) { 197 if len(args) != 1 { 198 Usage() 199 os.Exit(1) 200 } 201 id, err := common.StringToClientID(args[0]) 202 if err != nil { 203 log.Exitf("Unable to parse %s as client id: %v", args[0], err) 204 } 205 ctx := context.Background() 206 res, err := c.ListClientContacts(ctx, &spb.ListClientContactsRequest{ClientId: id.Bytes()}, grpc.MaxCallRecvMsgSize(1024*1024*1024)) 207 if err != nil { 208 log.Exitf("ListClientContacts RPC failed: %v", err) 209 } 210 if len(res.Contacts) == 0 { 211 fmt.Println("No contacts found.") 212 return 213 } 214 s, err := history.Summarize(res.Contacts) 215 if err != nil { 216 log.Exitf("Error creating summary: %v", err) 217 } 218 fmt.Printf(`Raw Summary: 219 First Recorded Contact: %v 220 Last Recorded Contact: %v 221 Contact Count: %d 222 Observed IP Count: %d 223 Split Points: %d 224 Splits: %d 225 Skips: %d 226 `, s.Start, s.End, s.Count, s.IPCount, s.SplitPoints, s.Splits, s.Skips) 227 if s.Splits > 0 { 228 fmt.Printf("This client appears to have be restored %d times from %d different backup images.\n", s.Splits, s.SplitPoints) 229 } 230 if s.Skips > s.Splits { 231 fmt.Printf("Observed %d Skips, but only %d splits. The machine may have been cloned.\n", s.Skips, s.Splits) 232 } 233 } 234 235 // BlacklistClient blacklists a client id, forcing any client(s) using it to 236 // rekey. args[0] must be a client id. 237 func BlacklistClient(c sgrpc.AdminClient, args ...string) { 238 if len(args) != 1 { 239 Usage() 240 os.Exit(1) 241 } 242 id, err := common.StringToClientID(args[0]) 243 if err != nil { 244 log.Exitf("Unable to parse %s as client id: %v", args[0], err) 245 } 246 ctx := context.Background() 247 if _, err := c.BlacklistClient(ctx, &spb.BlacklistClientRequest{ClientId: id.Bytes()}); err != nil { 248 log.Exitf("BlacklistClient RPC failed: %v", err) 249 } 250 }