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  }