github.com/google/trillian-examples@v0.0.0-20240520080811-0d40d35cef0e/binary_transparency/firmware/cmd/ftmapserver/impl/ftmapserver.go (about) 1 // Copyright 2021 Google LLC. 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 impl is the implementation of the Firmware Transparency map server. 16 package impl 17 18 import ( 19 "context" 20 "encoding/base64" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "math" 25 "net/http" 26 "strconv" 27 28 "github.com/golang/glog" 29 "github.com/google/trillian-examples/binary_transparency/firmware/api" 30 "github.com/google/trillian-examples/binary_transparency/firmware/internal/ftmap" 31 "github.com/google/trillian/experimental/batchmap" 32 "github.com/google/trillian/types" 33 "github.com/transparency-dev/formats/log" 34 35 "github.com/gorilla/mux" 36 37 _ "github.com/mattn/go-sqlite3" // Load drivers for sqlite3 38 ) 39 40 // MapReader is an interface that allows a map to be read from storage. 41 type MapReader interface { 42 // LatestRevision gets the metadata for the last completed write. 43 LatestRevision() (rev int, logroot types.LogRootV1, count int64, err error) 44 45 // Tile gets the tile at the given path in the given revision of the map. 46 Tile(revision int, path []byte) (*batchmap.Tile, error) 47 48 // Aggregation gets the aggregation for the firmware at the given log index. 49 Aggregation(revision int, fwLogIndex uint64) (api.AggregatedFirmware, error) 50 } 51 52 // MapServerOpts encapsulates options for running an FT map server. 53 type MapServerOpts struct { 54 ListenAddr string 55 MapDBAddr string 56 } 57 58 // Main brings up an http server according to the given options. 59 func Main(ctx context.Context, opts MapServerOpts) error { 60 if len(opts.MapDBAddr) == 0 { 61 return errors.New("map DB is required") 62 } 63 mapDB, err := ftmap.NewMapDB(opts.MapDBAddr) 64 if err != nil { 65 return fmt.Errorf("failed to open map DB at %q: %v", opts.MapDBAddr, err) 66 } 67 68 glog.Infof("Starting FT map server...") 69 srv := Server{db: mapDB} 70 r := mux.NewRouter() 71 srv.RegisterHandlers(r) 72 hServer := &http.Server{ 73 Addr: opts.ListenAddr, 74 Handler: r, 75 } 76 e := make(chan error, 1) 77 go func() { 78 e <- hServer.ListenAndServe() 79 close(e) 80 }() 81 <-ctx.Done() 82 glog.Info("Server shutting down") 83 if err := hServer.Shutdown(ctx); err != nil { 84 glog.Errorf("server.Shutdown(): %v", err) 85 } 86 return <-e 87 } 88 89 // Server is the core state & handler implementation of the FT personality. 90 type Server struct { 91 db MapReader 92 } 93 94 // getCheckpoint returns a recent MapCheckpoint. 95 func (s *Server) getCheckpoint(w http.ResponseWriter, r *http.Request) { 96 rev, logRootV1, count, err := s.db.LatestRevision() 97 if err != nil { 98 http.Error(w, err.Error(), http.StatusInternalServerError) 99 return 100 } 101 glog.V(1).Infof("Latest revision: %d %+v", rev, logRootV1) 102 tile, err := s.db.Tile(rev, []byte{}) 103 if err != nil { 104 http.Error(w, err.Error(), http.StatusInternalServerError) 105 return 106 } 107 108 lcp := api.LogCheckpoint{ 109 Checkpoint: log.Checkpoint{ 110 Origin: api.FTLogOrigin, 111 Size: logRootV1.TreeSize, 112 Hash: logRootV1.RootHash, 113 }, 114 TimestampNanos: logRootV1.TimestampNanos, 115 } 116 checkpoint := api.MapCheckpoint{ 117 LogCheckpoint: lcp.Marshal(), 118 LogSize: uint64(count), 119 Revision: uint64(rev), 120 RootHash: tile.RootHash, 121 } 122 js, err := json.Marshal(checkpoint) 123 if err != nil { 124 http.Error(w, err.Error(), http.StatusInternalServerError) 125 126 } 127 w.Header().Set("Content-Type", "application/json") 128 if _, err := w.Write(js); err != nil { 129 glog.Errorf("w.Write(): %v", err) 130 } 131 } 132 133 // getTile returns the tile at the given revision & path. 134 func (s *Server) getTile(w http.ResponseWriter, r *http.Request) { 135 rev, err := parseUintParam(r, "revision") 136 if err != nil { 137 http.Error(w, err.Error(), http.StatusBadRequest) 138 return 139 } 140 if rev > math.MaxInt { 141 // TODO(mhutchinson): Revision probably ought to be uint64 as negative revisions are weird. 142 http.Error(w, "revision is too large", http.StatusBadRequest) 143 return 144 } 145 path, err := parseBase64Param(r, "path") 146 if err != nil { 147 http.Error(w, err.Error(), http.StatusBadRequest) 148 return 149 } 150 bmt, err := s.db.Tile(int(rev), path) 151 if err != nil { 152 http.Error(w, err.Error(), http.StatusInternalServerError) 153 return 154 } 155 leaves := make([]api.MapTileLeaf, len(bmt.Leaves)) 156 for i, l := range bmt.Leaves { 157 leaves[i] = api.MapTileLeaf{ 158 Path: l.Path, 159 Hash: l.Hash, 160 } 161 } 162 tile := api.MapTile{ 163 Path: bmt.Path, 164 Leaves: leaves, 165 } 166 js, err := json.Marshal(tile) 167 if err != nil { 168 http.Error(w, err.Error(), http.StatusInternalServerError) 169 return 170 } 171 w.Header().Set("Content-Type", "application/json") 172 if _, err := w.Write(js); err != nil { 173 glog.Errorf("w.Write(): %v", err) 174 } 175 } 176 177 // getAggregation returns the aggregation for the firware at the given log index. 178 func (s *Server) getAggregation(w http.ResponseWriter, r *http.Request) { 179 rev, err := parseUintParam(r, "revision") 180 if err != nil { 181 http.Error(w, err.Error(), http.StatusBadRequest) 182 return 183 } 184 if rev > math.MaxInt { 185 // TODO(mhutchinson): Revision probably ought to be uint64 as negative revisions are weird. 186 http.Error(w, "revision is too large", http.StatusBadRequest) 187 return 188 } 189 fwIndex, err := parseUintParam(r, "fwIndex") 190 if err != nil { 191 http.Error(w, err.Error(), http.StatusBadRequest) 192 return 193 } 194 195 agg, err := s.db.Aggregation(int(rev), fwIndex) 196 if err != nil { 197 http.Error(w, err.Error(), http.StatusInternalServerError) 198 return 199 } 200 js, err := json.Marshal(agg) 201 if err != nil { 202 http.Error(w, err.Error(), http.StatusInternalServerError) 203 return 204 } 205 w.Header().Set("Content-Type", "application/json") 206 if _, err := w.Write(js); err != nil { 207 glog.Errorf("w.Write(): %v", err) 208 } 209 } 210 211 // RegisterHandlers registers HTTP handlers for the endpoints. 212 func (s *Server) RegisterHandlers(r *mux.Router) { 213 r.HandleFunc(fmt.Sprintf("/%s", api.MapHTTPGetCheckpoint), s.getCheckpoint).Methods("GET") 214 // Empty tile path is normal for requesting the root tile 215 r.HandleFunc(fmt.Sprintf("/%s/in-revision/{revision:[0-9]+}/at-path/", api.MapHTTPGetTile), s.getTile).Methods("GET") 216 r.HandleFunc(fmt.Sprintf("/%s/in-revision/{revision:[0-9]+}/at-path/{path}", api.MapHTTPGetTile), s.getTile).Methods("GET") 217 r.HandleFunc(fmt.Sprintf("/%s/in-revision/{revision:[0-9]+}/for-firmware-at-index/{fwIndex:[0-9]+}", api.MapHTTPGetAggregation), s.getAggregation).Methods("GET") 218 } 219 220 func parseBase64Param(r *http.Request, name string) ([]byte, error) { 221 v := mux.Vars(r) 222 b, err := base64.URLEncoding.DecodeString(v[name]) 223 if err != nil { 224 return nil, fmt.Errorf("%s should be URL-safe base64 (%q)", name, err) 225 } 226 return b, nil 227 } 228 229 func parseUintParam(r *http.Request, name string) (uint64, error) { 230 v := mux.Vars(r) 231 i, err := strconv.ParseUint(v[name], 0, 64) 232 if err != nil { 233 return 0, fmt.Errorf("%s should be an integer (%q)", name, err) 234 } 235 return i, nil 236 }