github.com/transparency-dev/armored-witness-applet@v0.1.1/trusted_applet/main.go (about) 1 // Copyright 2022 The Armored Witness Applet authors. All Rights Reserved. 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 // http://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 main 16 17 import ( 18 "context" 19 "flag" 20 "fmt" 21 "log" 22 "net" 23 "net/http" 24 "regexp" 25 "runtime" 26 "sync" 27 28 "strings" 29 "time" 30 31 prom "github.com/prometheus/client_golang/prometheus" 32 "github.com/prometheus/client_golang/prometheus/collectors" 33 "github.com/prometheus/client_golang/prometheus/promhttp" 34 35 "github.com/usbarmory/GoTEE/applet" 36 "github.com/usbarmory/GoTEE/syscall" 37 "github.com/usbarmory/tamago/soc/nxp/usdhc" 38 "google.golang.org/protobuf/proto" 39 "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" 40 41 "github.com/transparency-dev/armored-witness-applet/trusted_applet/internal/storage" 42 "github.com/transparency-dev/armored-witness-applet/trusted_applet/internal/storage/slots" 43 "github.com/transparency-dev/armored-witness-common/release/firmware/update" 44 "github.com/transparency-dev/armored-witness-os/api" 45 "github.com/transparency-dev/armored-witness-os/api/rpc" 46 "github.com/transparency-dev/formats/note" 47 48 "github.com/transparency-dev/witness/monitoring" 49 "github.com/transparency-dev/witness/monitoring/prometheus" 50 "github.com/transparency-dev/witness/omniwitness" 51 "k8s.io/klog/v2" 52 53 _ "golang.org/x/crypto/x509roots/fallback" 54 ) 55 56 const ( 57 // slotsPartitionStartBlock defines where our witness data storage partition starts. 58 // Changing this location is overwhelmingly likely to result in data loss. 59 slotsPartitionStartBlock = 0x400000 60 // slotsPartitionLengthBlocks specifies the size of the slots partition. 61 // Increasing this value is relatively safe, if you're sure there is no data 62 // stored in blocks which follow the current partition. 63 // 64 // We're starting with enough space for 4096 slots of 512KB each, which should be plenty. 65 slotsPartitionLengthBlocks = 0x400000 66 67 // slotSizeBytes is the size of each individual slot in the partition. 68 // Changing this is overwhelmingly likely to result in data loss. 69 slotSizeBytes = 512 << 10 70 71 // updateCheckInterval is the time between checking the FT Log for firmware 72 // updates. 73 updateCheckInterval = 5 * time.Minute 74 ) 75 76 var ( 77 Revision string 78 Version string 79 80 RestDistributorBaseURL string 81 82 cfg *api.Configuration 83 84 persistence *storage.SlotPersistence 85 ) 86 87 var ( 88 doOnce sync.Once 89 counterWitnessStarted monitoring.Counter 90 counterFirmwareUpdateAttempt monitoring.Counter 91 counterFirmwareUpdateSuccess monitoring.Counter 92 ) 93 94 func initMetrics() { 95 doOnce.Do(func() { 96 mf := monitoring.GetMetricFactory() 97 counterWitnessStarted = mf.NewCounter("witness_started", "Number of times the witness was started") 98 counterFirmwareUpdateAttempt = mf.NewCounter("firmware_update_attempt", "Number of times the updater ran to check if firmware could be updated") 99 counterFirmwareUpdateSuccess = mf.NewCounter("firmware_update_success", "Number of times the updater suceeded when checking if firmware could be updated. This does not mean that firmware was installed. It more closely resembles a NOOP for firmware update.") 100 // Unfortunately, the default prom gatherer has _some_ Go collectors, but not all, so we have to 101 // unregister it in order to be able to register the newer way with expanded coverage. 102 // error for dupes. 103 prom.Unregister(prom.NewGoCollector()) 104 prom.Register(collectors.NewGoCollector(collectors.WithGoCollectorRuntimeMetrics(collectors.GoRuntimeMetricsRule{Matcher: regexp.MustCompile("/.*")}))) 105 }) 106 } 107 108 func init() { 109 runtime.Exit = applet.Exit 110 } 111 112 func main() { 113 klog.InitFlags(nil) 114 flag.Set("vmodule", "journal=1,slots=1,storage=1") 115 flag.Set("logtostderr", "true") 116 flag.Parse() 117 118 ctx := context.Background() 119 defer applet.Exit() 120 121 mf := prometheus.MetricFactory{ 122 Prefix: "omniwitness_", 123 } 124 monitoring.SetMetricFactory(mf) 125 initMetrics() 126 127 klog.Infof("%s/%s (%s) • TEE user applet • %s", 128 runtime.GOOS, runtime.GOARCH, runtime.Version(), 129 Revision) 130 131 // Verify if we are allowed to run on this unit by sending version 132 // information for rollback protection check. 133 if err := syscall.Call("RPC.Version", strings.TrimPrefix(Version, "v"), nil); err != nil { 134 log.Fatalf("TA version check error for version %q: %v", Version, err) 135 } 136 // Set default configuration, the applet is reponsible of implementing 137 // its own configuration storage strategy. 138 cfg = &api.Configuration{ 139 DHCP: DHCP, 140 IP: IP, 141 Netmask: Netmask, 142 Gateway: Gateway, 143 Resolver: DefaultResolver, 144 NTPServer: DefaultNTP, 145 } 146 147 // Send network configuration to Trusted OS for network initialization. 148 // 149 // The same call can also be used to receive configuration updates from 150 // the Trusted OS, this gives the opportunity to check if the returned 151 // configuration is identical to the stored one or different (meaning 152 // that a configuration update has been requested through the control 153 // interface). 154 // 155 // In this example the sent configuration is always updated with the 156 // received one. 157 var cfgResp []byte 158 if err := syscall.Call("RPC.Config", cfg.Bytes(), &cfgResp); err != nil { 159 klog.Errorf("TA configuration error, %v", err) 160 } 161 162 if len(cfgResp) > 0 { 163 if err := proto.Unmarshal(cfgResp, cfg); err != nil { 164 log.Fatalf("TA configuration invalid: %v", err) 165 } 166 } 167 var status api.Status 168 169 if err := syscall.Call("RPC.Status", nil, &status); err != nil { 170 log.Fatalf("TA status error, %v", err) 171 } 172 173 for _, line := range strings.Split(status.Print(), "\n") { 174 klog.Info(line) 175 } 176 177 syscall.Call("RPC.LED", rpc.LEDStatus{Name: "blue", On: true}, nil) 178 defer syscall.Call("RPC.LED", rpc.LEDStatus{Name: "blue", On: false}, nil) 179 180 // (Re-)create our witness identity based on the device's internal secret key. 181 deriveIdentityKeys() 182 // Update our status in OS so custodian can inspect our signing identity even if there's no network. 183 syscall.Call("RPC.SetWitnessStatus", rpc.WitnessStatus{ 184 Identity: witnessPublicKey, 185 IDAttestPublicKey: attestPublicKey, 186 AttestedID: witnessPublicKeyAttestation, 187 }, nil) 188 189 klog.Infof("Attestation key:\n%s", attestPublicKey) 190 klog.Infof("Attested identity key:\n%s", witnessPublicKeyAttestation) 191 192 go func() { 193 l := true 194 for { 195 syscall.Call("RPC.LED", rpc.LEDStatus{Name: "blue", On: l}, nil) 196 l = !l 197 time.Sleep(500 * time.Millisecond) 198 } 199 }() 200 201 if err := startNetworking(); err != nil { 202 log.Fatalf("TA could not initialize networking, %v", err) 203 } 204 205 syscall.Call("RPC.Address", iface.NIC.MAC, nil) 206 207 // Register and run our RPC handler so we can receive ethernet frames. 208 go eventHandler() 209 210 klog.Infof("Opening storage...") 211 part := openStorage() 212 klog.Infof("Storage opened.") 213 214 // Set this to true to "wipe" the storage. 215 // Currently this simply force-writes an entry with zero bytes to 216 // each known slot. 217 // If the journal(s) become corrupt a larger hammer will be required. 218 reinit := false 219 if reinit { 220 for i := 10; i > 0; i-- { 221 klog.Infof("Erasing in %ds", i) 222 time.Sleep(time.Second) 223 } 224 if err := part.Erase(); err != nil { 225 klog.Exitf("Failed to erase partition: %v", err) 226 } 227 klog.Exit("Erase completed") 228 } 229 230 persistence = storage.NewSlotPersistence(part) 231 if err := persistence.Init(); err != nil { 232 klog.Exitf("Failed to create persistence layer: %v", err) 233 } 234 persistence.Init() 235 236 // Wait for a DHCP address to be assigned if that's what we're configured to do 237 if cfg.DHCP { 238 hostname := "armoredwitness" 239 if v, err := note.NewVerifier(witnessPublicKey); err == nil { 240 hostname = cleanForDNS(v.Name()) 241 } 242 runDHCP(ctx, nicID, fmt.Sprintf("AW-%s", status.Serial), hostname, runWithNetworking) 243 } else { 244 for { 245 if err := runWithNetworking(ctx); err != nil && err != context.Canceled { 246 klog.Warningf("runWithNetworking: %v", err) 247 } 248 } 249 } 250 251 // This forces the linker to keep the symbol present which is necessary for the inspect() 252 // function in the OS to work. 253 runtime.CallOnG0() 254 } 255 256 func cleanForDNS(s string) string { 257 return strings.Map(func(r rune) rune { 258 switch { 259 case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9', r == '-': 260 return r 261 default: 262 return '-' 263 } 264 }, s) 265 } 266 267 // runWithNetworking should only be called when we have an IP network configured. 268 // ctx should become Done if the network becomes unconfigured for any reason (e.g. 269 // DHCP lease expires). 270 // 271 // Everything which relies on IP networking being present should be started in 272 // here, and should gracefully stop when the passed-in context is Done. 273 func runWithNetworking(ctx context.Context) error { 274 addr, tcpErr := iface.Stack.GetMainNICAddress(nicID, ipv4.ProtocolNumber) 275 if tcpErr != nil { 276 return fmt.Errorf("runWithNetworking has no network configured: %v", tcpErr) 277 } 278 klog.Infof("TA Version:%s MAC:%s IP:%s GW:%s DNS:%s", Version, iface.NIC.MAC.String(), addr, iface.Stack.GetRouteTable(), net.DefaultNS) 279 // Update status with latest IP address too. 280 syscall.Call("RPC.SetWitnessStatus", rpc.WitnessStatus{ 281 Identity: witnessPublicKey, 282 IDAttestPublicKey: attestPublicKey, 283 AttestedID: witnessPublicKeyAttestation, 284 IP: addr.Address.String(), 285 }, nil) 286 287 // Avoid the situation where, at boot, we get a DHCP lease and then immediately update our 288 // local clock from 1970 to now, whereupon we consider the DHCP lease invalid and have to tear down 289 // the witness etc. below. 290 coldStart := time.Now().Before(time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC)) 291 292 select { 293 case <-runNTP(ctx): 294 if coldStart { 295 klog.Info("Large NTP date change detected, waiting for network to restart...") 296 // Give a bit of space so we don't spin while we wait for DHCP to do its thing. 297 time.Sleep(time.Second) 298 return nil 299 } 300 case <-ctx.Done(): 301 return ctx.Err() 302 } 303 304 triggerUpdate := updateChecker(ctx, updateCheckInterval) 305 306 listenCfg := &net.ListenConfig{} 307 308 adminListener, err := listenCfg.Listen(ctx, "tcp", ":8081") 309 if err != nil { 310 return fmt.Errorf("TA could not initialize admin listener, %v", err) 311 } 312 defer func() { 313 klog.Info("Closing admin port (8081)") 314 if err := adminListener.Close(); err != nil { 315 klog.Errorf("Error closing admin port: %v", err) 316 } 317 }() 318 go func() { 319 srvMux := http.NewServeMux() 320 srvMux.Handle("/metrics", promhttp.Handler()) 321 srvMux.Handle("/crashlog", &logHandler{RPC: "RPC.CrashLog"}) 322 srvMux.Handle("/consolelog", &logHandler{RPC: "RPC.ConsoleLog"}) 323 srvMux.HandleFunc("/updatecheck", func(w http.ResponseWriter, _ *http.Request) { 324 triggerUpdate <- struct{}{} 325 w.Header().Add("Content-Type", "text/plain") 326 w.Write([]byte("ok, check /consolelog!")) 327 }) 328 srv := &http.Server{ 329 ReadTimeout: 5 * time.Second, 330 WriteTimeout: 10 * time.Second, 331 Handler: srvMux, 332 } 333 if err := srv.Serve(adminListener); err != http.ErrServerClosed { 334 klog.Errorf("Error serving metrics: %v", err) 335 } 336 }() 337 338 // Set up and start omniwitness 339 opConfig := omniwitness.OperatorConfig{ 340 WitnessKey: witnessSigningKey, 341 RestDistributorBaseURL: RestDistributorBaseURL, 342 FeedInterval: 30 * time.Second, 343 DistributeInterval: 5 * time.Second, 344 } 345 mainListener, err := listenCfg.Listen(ctx, "tcp", ":80") 346 if err != nil { 347 return fmt.Errorf("could not initialize HTTP listener: %v", err) 348 } 349 defer func() { 350 if err := mainListener.Close(); err != nil { 351 klog.Errorf("mainListener: %v", err) 352 } 353 }() 354 355 klog.Info("Starting witness...") 356 klog.Infof("I am %q", witnessPublicKey) 357 counterWitnessStarted.Inc() 358 if err := omniwitness.Main(ctx, opConfig, persistence, mainListener, http.DefaultClient); err != nil { 359 return fmt.Errorf("omniwitness.Main failed: %v", err) 360 } 361 362 return ctx.Err() 363 } 364 365 func updateChecker(ctx context.Context, i time.Duration) chan<- struct{} { 366 var updateFetcher *update.Fetcher 367 var updateClient *update.Updater 368 var err error 369 370 trigger := make(chan struct{}, 1) 371 372 go func(ctx context.Context) { 373 t := time.NewTicker(i) 374 defer t.Stop() 375 for { 376 select { 377 case <-t.C: 378 trigger <- struct{}{} 379 case <-ctx.Done(): 380 close(trigger) 381 return 382 } 383 } 384 }(ctx) 385 386 go func(ctx context.Context) { 387 for { 388 select { 389 case _, ok := <-trigger: 390 if !ok { 391 return 392 } 393 if updateFetcher == nil || updateClient == nil { 394 updateFetcher, updateClient, err = updater(ctx) 395 if err != nil { 396 klog.Errorf("Failed to create updater: %v", err) 397 continue 398 } 399 } 400 counterFirmwareUpdateAttempt.Inc() 401 klog.V(1).Info("Scanning for available updates") 402 if err := updateFetcher.Scan(ctx); err != nil { 403 klog.Errorf("UpdateFetcher.Scan: %v", err) 404 continue 405 } 406 if err := updateClient.Update(ctx); err != nil { 407 klog.Errorf("Update: %v", err) 408 } 409 counterFirmwareUpdateSuccess.Inc() 410 case <-ctx.Done(): 411 return 412 } 413 } 414 }(ctx) 415 416 return trigger 417 } 418 419 func openStorage() *slots.Partition { 420 var info usdhc.CardInfo 421 if err := syscall.Call("RPC.CardInfo", nil, &info); err != nil { 422 klog.Exitf("Failed to get cardinfo: %v", err) 423 } 424 klog.Infof("CardInfo: %+v", info) 425 // dev is our access to the MMC storage. 426 dev := &storage.Device{CardInfo: &info} 427 bs := dev.BlockSize() 428 geo := slots.Geometry{ 429 Start: slotsPartitionStartBlock, 430 Length: slotsPartitionLengthBlocks, 431 } 432 sl := slotSizeBytes / bs 433 for i := uint(0); i < geo.Length; i += sl { 434 geo.SlotLengths = append(geo.SlotLengths, sl) 435 } 436 437 p, err := slots.OpenPartition(dev, geo) 438 if err != nil { 439 klog.Exitf("Failed to open partition: %v", err) 440 } 441 return p 442 } 443 444 type logHandler struct { 445 RPC string 446 } 447 448 func (c *logHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) { 449 var l []byte 450 if err := syscall.Call(c.RPC, nil, &l); err != nil { 451 klog.Errorf("Failed to fetch log from %v: %v", c.RPC, err) 452 res.WriteHeader(http.StatusInternalServerError) 453 return 454 } 455 res.Header().Add("Content-Type", "text/plain") 456 res.Write(l) 457 }