github.com/webmeshproj/webmesh-cni@v0.0.27/internal/metadata/server.go (about) 1 /* 2 Copyright 2023 Avi Zimmerman <avi.zimmerman@gmail.com>. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package metadata contains the container metadata server. 18 package metadata 19 20 import ( 21 "context" 22 "encoding/base64" 23 "encoding/json" 24 "errors" 25 "fmt" 26 "net" 27 "net/http" 28 "net/netip" 29 "sort" 30 "strings" 31 32 "github.com/go-logr/logr" 33 "github.com/jmespath/go-jmespath" 34 "github.com/webmeshproj/storage-provider-k8s/provider" 35 "github.com/webmeshproj/webmesh/pkg/crypto" 36 "github.com/webmeshproj/webmesh/pkg/meshnet/netutil" 37 "github.com/webmeshproj/webmesh/pkg/storage/types" 38 ctrl "sigs.k8s.io/controller-runtime" 39 "sigs.k8s.io/controller-runtime/pkg/log" 40 41 "github.com/webmeshproj/webmesh-cni/internal/host" 42 ) 43 44 // DefaultServerAddress is the default address for the metadata server. 45 var DefaultServerAddress = netip.MustParseAddrPort("169.254.169.254:80") 46 47 // Config are the options for the container metadata server. 48 type Config struct { 49 // Address is the address to bind the metadata server to. 50 // Defaults to DefaultMetadataAddress. 51 Address netip.AddrPort 52 // Host is the host node to use for the metadata server. 53 Host host.Node 54 // Storage is the storage provider to use for the metadata server. 55 Storage *provider.Provider 56 // KeyResolver is the key resolver to use for the metadata server. 57 KeyResolver NodeKeyResolver 58 // EnableIDTokens is true if ID tokens should be enabled. 59 EnableIDTokens bool 60 } 61 62 // NodeKeyResolver is an interface that can retrieve the private key of 63 // a node hosted on this server. 64 type NodeKeyResolver interface { 65 LookupPrivateKey(nodeID types.NodeID) (crypto.PrivateKey, bool) 66 } 67 68 // Server is the container metadata server. 69 type Server struct { 70 Config 71 srv *http.Server 72 log logr.Logger 73 } 74 75 // NewServer creates a new container metadata server. 76 func NewServer(cfg Config) *Server { 77 addr := cfg.Address 78 if !addr.IsValid() { 79 addr = DefaultServerAddress 80 } 81 srv := &Server{ 82 Config: cfg, 83 log: ctrl.Log.WithName("metadata-server"), 84 } 85 mux := http.NewServeMux() 86 mux.Handle("/", srv) 87 if cfg.EnableIDTokens { 88 mux.Handle("/id-tokens/", &IDTokenServer{srv}) 89 } 90 srv.srv = &http.Server{ 91 Addr: addr.String(), 92 Handler: mux, 93 ConnContext: func(ctx context.Context, c net.Conn) context.Context { 94 return log.IntoContext(ctx, srv.log.WithValues("remoteAddr", c.RemoteAddr())) 95 }, 96 } 97 return srv 98 } 99 100 // ListenAndServe starts the container metadata server. It blocks until the server 101 // is shutdown. If addr is empty, the default address of 169.254.169.254:80 is used. 102 func (s *Server) ListenAndServe() error { 103 if err := s.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { 104 return fmt.Errorf("start metadata server: %w", err) 105 } 106 return nil 107 } 108 109 // Shutdown shuts down the container metadata server. 110 func (s *Server) Shutdown(ctx context.Context) error { 111 return s.srv.Shutdown(ctx) 112 } 113 114 // ServeHTTP implements the http.Handler interface and serves the container metadata 115 // based on the source IP address. 116 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 117 // Get the peer container based on the source IP address. 118 rlog := log.FromContext(r.Context()) 119 rlog.Info("Serving metadata request", "path", r.URL.Path) 120 peerInfo, err := s.getPeerInfoFromRequest(r) 121 if err != nil { 122 s.log.Error(err, "Failed to get peer from request") 123 s.returnError(w, err) 124 return 125 } 126 // We behave like a typical metadata server and return a response 127 // based on the path of the request. 128 // This is done by marshaling to JSON and treating the path like 129 // a jmespath. 130 data, err := peerInfo.Peer.MarshalProtoJSON() 131 if err != nil { 132 s.returnError(w, err) 133 return 134 } 135 switch r.URL.Path { 136 case "/": 137 // We return the available keys for the metadata server. 138 // This is a bit of a hack but we marshal the peer to JSON 139 // then back to a mapstructure to get the keys. 140 rlog.Info("Serving metadata keys") 141 var m map[string]any 142 if err := json.Unmarshal(data, &m); err != nil { 143 s.returnError(w, err) 144 return 145 } 146 var keys []string 147 for k := range m { 148 keys = append(keys, k) 149 } 150 // Append the privateKey key if the request is local or we have a key resolver. 151 havePrivKey := peerInfo.Local 152 if !havePrivKey && s.KeyResolver != nil { 153 // Check if this is a managed node. 154 _, havePrivKey = s.KeyResolver.LookupPrivateKey(peerInfo.Peer.NodeID()) 155 } 156 if havePrivKey { 157 keys = append(keys, "privateKey") 158 } 159 sort.Strings(keys) 160 for _, k := range keys { 161 fmt.Fprintln(w, k) 162 } 163 return 164 default: 165 path := strings.TrimPrefix(r.URL.Path, "/") 166 path = strings.Replace(path, "/", ".", -1) 167 if path == "privateKey" { 168 // Special case where if this is a self lookup from a local container. 169 // We should have their private key. 170 var privkey crypto.PrivateKey 171 if peerInfo.Local { 172 privkey = s.Host.Node().Key() 173 } else { 174 if s.KeyResolver == nil { 175 s.returnError(w, fmt.Errorf("no key resolver")) 176 return 177 } 178 var ok bool 179 privkey, ok = s.KeyResolver.LookupPrivateKey(peerInfo.Peer.NodeID()) 180 if !ok { 181 s.returnError(w, fmt.Errorf("no private key found for node %s", peerInfo.Peer.NodeID())) 182 return 183 } 184 } 185 encoded, err := privkey.Encode() 186 if err != nil { 187 s.returnError(w, err) 188 return 189 } 190 fmt.Fprintln(w, encoded) 191 return 192 } 193 // Treat it like a jmespath 194 var jsondata any 195 if err := json.Unmarshal(data, &jsondata); err != nil { 196 s.returnError(w, err) 197 return 198 } 199 result, err := jmespath.Search(path, jsondata) 200 if err != nil { 201 if errors.As(err, &jmespath.SyntaxError{}) { 202 // Return a not found error 203 http.Error(w, "null", http.StatusNotFound) 204 return 205 } 206 s.returnError(w, err) 207 return 208 } 209 switch v := result.(type) { 210 case string: 211 // Return as raw string 212 fmt.Fprintln(w, v) 213 case int, int32, int64, uint, uint32, uint64, float32, float64: 214 // Return as raw number 215 fmt.Fprintln(w, v) 216 case []byte: 217 // Return as base64 encoded string 218 fmt.Fprintln(w, base64.StdEncoding.EncodeToString(v)) 219 default: 220 // Return as JSON 221 s.returnJSON(w, result) 222 } 223 } 224 } 225 226 // PeerRequestInfo is the information about the peer that is requesting 227 // the metadata. 228 type PeerRequestInfo struct { 229 // Peer is the peer that is requesting the metadata. 230 Peer types.MeshNode 231 // Local is true if the request is from the local host node. 232 Local bool 233 } 234 235 // getPeerFromRequest returns the peer container based on the source IP address. 236 func (s *Server) getPeerInfoFromRequest(r *http.Request) (info PeerRequestInfo, err error) { 237 rlog := log.FromContext(r.Context()) 238 isLocal := strings.HasPrefix(r.RemoteAddr, s.Address.Addr().String()) 239 if isLocal { 240 rlog.V(1).Info("Request is for the local host node") 241 info.Local = true 242 info.Peer, err = s.Storage.MeshDB().Peers().Get(r.Context(), s.Host.ID()) 243 return 244 } 245 rlog.V(1).Info("Request is from a local container or connected node") 246 raddrport, err := netip.ParseAddrPort(r.RemoteAddr) 247 if err != nil { 248 return info, fmt.Errorf("parse remote address: %w", err) 249 } 250 raddr := raddrport.Addr() 251 switch { 252 case raddr.Is4(): 253 info.Peer, err = s.Storage.Datastore().GetPeerByIPv4Addr(r.Context(), netip.PrefixFrom(raddr, 32)) 254 case raddr.Is6(): 255 info.Peer, err = s.Storage.Datastore().GetPeerByIPv6Addr(r.Context(), netip.PrefixFrom(raddr, netutil.DefaultNodeBits)) 256 default: 257 err = fmt.Errorf("unknown IP address type: %s", raddr) 258 } 259 return 260 } 261 262 func (s *Server) returnJSON(w http.ResponseWriter, result any) { 263 out, err := json.MarshalIndent(result, "", " ") 264 if err != nil { 265 s.returnError(w, err) 266 return 267 } 268 fmt.Fprintln(w, string(out)) 269 } 270 271 func (s *Server) returnError(w http.ResponseWriter, err error) { 272 errmsg := map[string]string{ 273 "error": err.Error(), 274 } 275 out, err := json.MarshalIndent(errmsg, "", " ") 276 if err != nil { 277 s.log.Error(err, "Failed to marshal error message") 278 http.Error(w, err.Error(), http.StatusInternalServerError) 279 return 280 } 281 http.Error(w, string(out), http.StatusInternalServerError) 282 }