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  }