github.com/Azure/aad-pod-identity@v1.8.17/pkg/nmi/server/server.go (about)

     1  package server
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"net"
     9  	"net/http"
    10  	"os"
    11  	"os/signal"
    12  	"regexp"
    13  	"runtime"
    14  	"runtime/debug"
    15  	"strconv"
    16  	"strings"
    17  	"syscall"
    18  	"time"
    19  
    20  	"github.com/Azure/go-autorest/autorest/adal"
    21  	"github.com/gorilla/mux"
    22  	"k8s.io/klog/v2"
    23  
    24  	"github.com/Azure/aad-pod-identity/pkg/auth"
    25  	"github.com/Azure/aad-pod-identity/pkg/k8s"
    26  	"github.com/Azure/aad-pod-identity/pkg/metrics"
    27  	"github.com/Azure/aad-pod-identity/pkg/nmi"
    28  	"github.com/Azure/aad-pod-identity/pkg/nmi/conntrack"
    29  	"github.com/Azure/aad-pod-identity/pkg/nmi/iptables"
    30  	"github.com/Azure/aad-pod-identity/pkg/pod"
    31  )
    32  
    33  const (
    34  	localhost = "127.0.0.1"
    35  	// "/metadata" portion is case-insensitive in IMDS
    36  	tokenPathPrefix     = "/{type:(?i:metadata)}/identity/oauth2/token" // #nosec
    37  	hostTokenPathPrefix = "/host/token"
    38  	// "/metadata" portion is case-insensitive in IMDS
    39  	instancePathPrefix = "/{type:(?i:metadata)}/instance" // #nosec
    40  	headerRetryAfter   = "Retry-After"
    41  )
    42  
    43  var (
    44  	// invalidTokenPathMatcher matches the token path that is not supported by IMDS
    45  	// this handler is configured right after the token path handler to block requests with
    46  	// invalid token path instead of sending it to IMDS.
    47  	// we don't have to handle case sensitivity for "/identity/" as that's rejected by IMDS
    48  	invalidTokenPathMatcher = mux.MatcherFunc(func(req *http.Request, rm *mux.RouteMatch) bool {
    49  		r := regexp.MustCompile("/(?i:metadata)/identity(.*?)oauth2(.*?)token") // #nosec
    50  		return r.MatchString(req.URL.Path)
    51  	})
    52  )
    53  
    54  // Server encapsulates all of the parameters necessary for starting up
    55  // the server. These can be set via command line.
    56  type Server struct {
    57  	KubeClient                         k8s.Client
    58  	NMIHost                            string
    59  	NMIPort                            string
    60  	MetadataIP                         string
    61  	MetadataPort                       string
    62  	NodeName                           string
    63  	IPTableUpdateTimeIntervalInSeconds int
    64  	MICNamespace                       string
    65  	Initialized                        bool
    66  	BlockInstanceMetadata              bool
    67  	MetadataHeaderRequired             bool
    68  	SetRetryAfterHeader                bool
    69  	EnableConntrackDeletion            bool
    70  	// TokenClient is client that fetches identities and tokens
    71  	TokenClient nmi.TokenClient
    72  	Reporter    *metrics.Reporter
    73  }
    74  
    75  // NMIResponse is the response returned to caller
    76  type NMIResponse struct {
    77  	Token    msiResponse `json:"token"`
    78  	ClientID string      `json:"clientid"`
    79  }
    80  
    81  // MetadataResponse represents the error returned
    82  // to caller when metadata header is not specified.
    83  type MetadataResponse struct {
    84  	Error            string `json:"error"`
    85  	ErrorDescription string `json:"error_description"`
    86  }
    87  
    88  // NewServer will create a new Server with default values.
    89  func NewServer(micNamespace string, blockInstanceMetadata, metadataHeaderRequired, setRetryAfterHeader bool) *Server {
    90  	reporter, err := metrics.NewReporter()
    91  	if err != nil {
    92  		klog.Errorf("failed to create reporter for metrics, error: %+v", err)
    93  	} else {
    94  		// keeping this reference to be used in ServeHTTP, as server is not accessible in ServeHTTP
    95  		appHandlerReporter = reporter
    96  		auth.InitReporter(reporter)
    97  	}
    98  	return &Server{
    99  		MICNamespace:           micNamespace,
   100  		BlockInstanceMetadata:  blockInstanceMetadata,
   101  		MetadataHeaderRequired: metadataHeaderRequired,
   102  		Reporter:               reporter,
   103  		SetRetryAfterHeader:    setRetryAfterHeader,
   104  	}
   105  }
   106  
   107  // Run runs the specified Server.
   108  func (s *Server) Run() error {
   109  	go s.updateIPTableRules()
   110  
   111  	rtr := mux.NewRouter()
   112  	// Flow for the request is as follows:
   113  	// 1. If the request is for token, then it will be handled by tokenHandler post validation.
   114  	// 2. If the request is for token but the path is invalid, then it will be handled by invalidTokenPathHandler.
   115  	// 3. If the request is for host token, then it will be handled by hostTokenHandler.
   116  	// 4. If the request is for instance metadata
   117  	//    4.1 If blockInstanceMetadata is set to true, then it will be handled by blockInstanceMetadataHandler (deny access to instance metadata).
   118  	// 5. If the request is for any other path, it will be proxied to IMDS and the response will be returned to the caller.
   119  	rtr.PathPrefix(tokenPathPrefix).Handler(appHandler(s.msiHandler))
   120  	rtr.MatcherFunc(invalidTokenPathMatcher).HandlerFunc(invalidTokenPathHandler)
   121  	rtr.PathPrefix(hostTokenPathPrefix).Handler(appHandler(s.hostHandler))
   122  	if s.BlockInstanceMetadata {
   123  		rtr.PathPrefix(instancePathPrefix).HandlerFunc(forbiddenHandler)
   124  	}
   125  	rtr.PathPrefix("/").HandlerFunc(s.defaultPathHandler)
   126  
   127  	klog.Infof("listening on %s:%s", s.NMIHost, s.NMIPort)
   128  	if err := http.ListenAndServe(fmt.Sprintf("%s:%s", s.NMIHost, s.NMIPort), rtr); err != nil {
   129  		klog.Fatalf("error creating http server: %+v", err)
   130  	}
   131  	return nil
   132  }
   133  
   134  func (s *Server) updateIPTableRulesInternal() {
   135  	target := s.NMIHost
   136  	if target == "0.0.0.0" {
   137  		// if we're binding to all interfaces, we still want to add iptables rules for localhost only
   138  		target = localhost
   139  	}
   140  
   141  	klog.V(5).Infof("node(%s) ip(%s) metadata address(%s:%s) nmi port(%s)", s.NodeName, target, s.MetadataIP, s.MetadataPort, s.NMIPort)
   142  
   143  	if err := iptables.AddCustomChain(s.MetadataIP, s.MetadataPort, target, s.NMIPort); err != nil {
   144  		klog.Fatalf("%s", err)
   145  	}
   146  	if err := iptables.LogCustomChain(); err != nil {
   147  		klog.Fatalf("%s", err)
   148  	}
   149  }
   150  
   151  // try to delete pre-existing conntrack entries for metadata endpoint
   152  func (s *Server) deleteConntrackEntries() {
   153  	klog.Infof("deleting conntrack entries for %s:%s", s.MetadataIP, s.MetadataPort)
   154  
   155  	if err := conntrack.DeleteConntrackEntries(s.MetadataIP, s.MetadataPort); err != nil {
   156  		klog.Fatalf("failed to delete conntrack entries for metadata ip: %s", err)
   157  	}
   158  }
   159  
   160  // updateIPTableRules ensures the correct iptable rules are set
   161  // such that metadata requests are received by nmi assigned port
   162  // NOT originating from HostIP destined to metadata endpoint are
   163  // routed to NMI endpoint
   164  func (s *Server) updateIPTableRules() {
   165  	signalChan := make(chan os.Signal, 1)
   166  	signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT)
   167  
   168  	ticker := time.NewTicker(time.Second * time.Duration(s.IPTableUpdateTimeIntervalInSeconds))
   169  	defer ticker.Stop()
   170  	// Run once before the waiting on ticker for the rules to take effect
   171  	// immediately.
   172  	s.updateIPTableRulesInternal()
   173  	// delete conntrack entries for pre-existing connections to metadata endpoint
   174  	if s.EnableConntrackDeletion {
   175  		s.deleteConntrackEntries()
   176  	}
   177  	s.Initialized = true
   178  
   179  loop:
   180  	for {
   181  		select {
   182  		case <-signalChan:
   183  			handleTermination()
   184  			break loop
   185  
   186  		case <-ticker.C:
   187  			s.updateIPTableRulesInternal()
   188  		}
   189  	}
   190  }
   191  
   192  type appHandler func(http.ResponseWriter, *http.Request) string
   193  
   194  type responseWriter struct {
   195  	http.ResponseWriter
   196  	statusCode int
   197  }
   198  
   199  func (rw *responseWriter) WriteHeader(code int) {
   200  	rw.statusCode = code
   201  	rw.ResponseWriter.WriteHeader(code)
   202  }
   203  
   204  func newResponseWriter(w http.ResponseWriter) *responseWriter {
   205  	return &responseWriter{w, http.StatusOK}
   206  }
   207  
   208  var appHandlerReporter *metrics.Reporter
   209  
   210  // ServeHTTP implements the net/http server handler interface
   211  // and recovers from panics.
   212  func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   213  	tracker := fmt.Sprintf("req.method=%s reg.path=%s req.remote=%s", r.Method, r.URL.Path, parseRemoteAddr(r.RemoteAddr))
   214  
   215  	// Set the header in advance so that both success as well
   216  	// as error paths have it set as application/json content type.
   217  	w.Header().Set("Content-Type", "application/json")
   218  	start := time.Now()
   219  	defer func() {
   220  		var err error
   221  		if rec := recover(); rec != nil {
   222  			_, file, line, _ := runtime.Caller(3)
   223  			stack := string(debug.Stack())
   224  			switch t := rec.(type) {
   225  			case string:
   226  				err = errors.New(t)
   227  			case error:
   228  				err = t
   229  			default:
   230  				err = errors.New("unknown error")
   231  			}
   232  			klog.Errorf("panic processing request: %+v, file: %s, line: %d, stacktrace: '%s' %s res.status=%d", r, file, line, stack, tracker, http.StatusInternalServerError)
   233  			http.Error(w, err.Error(), http.StatusInternalServerError)
   234  		}
   235  	}()
   236  	rw := newResponseWriter(w)
   237  	ns := fn(rw, r)
   238  	latency := time.Since(start)
   239  	klog.Infof("status (%d) took %d ns for %s", rw.statusCode, latency.Nanoseconds(), tracker)
   240  
   241  	tokenRequest := parseTokenRequest(r)
   242  
   243  	if appHandlerReporter != nil {
   244  		err := appHandlerReporter.ReportOperationAndStatus(
   245  			r.URL.Path,
   246  			strconv.Itoa(rw.statusCode),
   247  			ns,
   248  			tokenRequest.Resource,
   249  			metrics.NMIOperationsDurationM.M(metrics.SinceInSeconds(start)))
   250  		if err != nil {
   251  			klog.Warningf("failed to report metrics, error: %+v", err)
   252  		}
   253  	}
   254  }
   255  
   256  func (s *Server) hostHandler(w http.ResponseWriter, r *http.Request) (ns string) {
   257  	hostIP := parseRemoteAddr(r.RemoteAddr)
   258  	tokenRequest := parseTokenRequest(r)
   259  
   260  	podns, podname := parsePodInfo(r)
   261  	if podns == "" || podname == "" {
   262  		klog.Error("missing podname and podns from request")
   263  		http.Error(w, "missing 'podname' and 'podns' from request header", http.StatusBadRequest)
   264  		return
   265  	}
   266  	// set the ns so it can be used for metrics
   267  	ns = podns
   268  	if hostIP != localhost {
   269  		klog.Errorf("request remote address is not from a host")
   270  		http.Error(w, "request remote address is not from a host", http.StatusInternalServerError)
   271  		return
   272  	}
   273  	if !tokenRequest.ValidateResourceParamExists() {
   274  		klog.Warning("parameter resource cannot be empty")
   275  		http.Error(w, "parameter resource cannot be empty", http.StatusBadRequest)
   276  		return
   277  	}
   278  
   279  	podID, err := s.TokenClient.GetIdentities(r.Context(), podns, podname, tokenRequest.ClientID, tokenRequest.ResourceID)
   280  	if err != nil {
   281  		klog.Errorf("failed to get identities, error: %+v", err)
   282  		http.Error(w, err.Error(), http.StatusNotFound)
   283  		return
   284  	}
   285  	tokens, err := s.TokenClient.GetTokens(r.Context(), tokenRequest.ClientID, tokenRequest.Resource, *podID)
   286  	if err != nil {
   287  		klog.Errorf("failed to get service principal token for pod:%s/%s, error: %+v", podns, podname, err)
   288  		httpErrorCode := http.StatusForbidden
   289  		if auth.IsHealthCheckError(err) {
   290  			// the adal library performs a health check prior to making the token request
   291  			// if the health check fails, we want to return a 503 instead of 403
   292  			// for health check failures, the error is not a token refresh error
   293  			httpErrorCode = http.StatusServiceUnavailable
   294  		}
   295  		http.Error(w, err.Error(), httpErrorCode)
   296  		return
   297  	}
   298  	nmiResp := NMIResponse{
   299  		Token:    newMSIResponse(*tokens[0]),
   300  		ClientID: podID.Spec.ClientID,
   301  	}
   302  	response, err := json.Marshal(nmiResp)
   303  	if err != nil {
   304  		klog.Errorf("failed to marshal service principal token and clientid for pod:%s/%s, error: %+v", podns, podname, err)
   305  		http.Error(w, err.Error(), http.StatusInternalServerError)
   306  		return
   307  	}
   308  	w.Header().Set("Content-Length", strconv.Itoa(len(response)))
   309  	_, _ = w.Write(response)
   310  	return
   311  }
   312  
   313  // msiResponse marshals in a format that matches the underlying
   314  // metadata endpoint more closely. This increases compatibility
   315  // with callers built on older versions of adal client libraries.
   316  type msiResponse struct {
   317  	AccessToken  string `json:"access_token"`
   318  	RefreshToken string `json:"refresh_token"`
   319  
   320  	ExpiresIn string `json:"expires_in"`
   321  	ExpiresOn string `json:"expires_on"`
   322  	NotBefore string `json:"not_before"`
   323  
   324  	Resource string `json:"resource"`
   325  	Type     string `json:"token_type"`
   326  }
   327  
   328  func newMSIResponse(token adal.Token) msiResponse {
   329  	return msiResponse{
   330  		AccessToken:  token.AccessToken,
   331  		RefreshToken: token.RefreshToken,
   332  		ExpiresIn:    token.ExpiresIn.String(),
   333  		ExpiresOn:    token.ExpiresOn.String(),
   334  		NotBefore:    token.NotBefore.String(),
   335  		Resource:     token.Resource,
   336  		Type:         token.Type,
   337  	}
   338  }
   339  
   340  func (s *Server) isMIC(podNS, rsName string) bool {
   341  	micRegEx := regexp.MustCompile(`^mic-*`)
   342  	if strings.EqualFold(podNS, s.MICNamespace) && micRegEx.MatchString(rsName) {
   343  		return true
   344  	}
   345  	return false
   346  }
   347  
   348  func (s *Server) getTokenForExceptedPod(rqClientID, rqResource string) ([]byte, int, error) {
   349  	var token *adal.Token
   350  	var err error
   351  	// ClientID is empty, so we are going to use System assigned MSI
   352  	if rqClientID == "" {
   353  		klog.Infof("fetching token for system assigned MSI")
   354  		token, err = auth.GetServicePrincipalTokenFromMSI(rqResource)
   355  	} else { // User assigned identity usage.
   356  		klog.Infof("fetching token for user assigned MSI for resource: %s", rqResource)
   357  		token, err = auth.GetServicePrincipalTokenFromMSIWithUserAssignedID(rqClientID, rqResource)
   358  	}
   359  	if err != nil {
   360  		// TODO: return the right status code based on the error we got from adal.
   361  		return nil, http.StatusForbidden, fmt.Errorf("failed to get service principal token, error: %+v", err)
   362  	}
   363  	response, err := json.Marshal(newMSIResponse(*token))
   364  	if err != nil {
   365  		return nil, http.StatusInternalServerError, fmt.Errorf("failed to marshal service principal token, error: %+v", err)
   366  	}
   367  	return response, http.StatusOK, nil
   368  }
   369  
   370  // msiHandler uses the remote address to identify the pod ip and uses it
   371  // to find a matching client id, and then returns the token sourced through
   372  // AAD using adal
   373  // if the requests contains client id it validates it against the admin
   374  // configured id.
   375  func (s *Server) msiHandler(w http.ResponseWriter, r *http.Request) (ns string) {
   376  	if s.MetadataHeaderRequired && parseMetadata(r) != "true" {
   377  		klog.Errorf("metadata header is not specified, req.method=%s reg.path=%s req.remote=%s", r.Method, r.URL.Path, parseRemoteAddr(r.RemoteAddr))
   378  		metadataNotSpecifiedError(w)
   379  		return
   380  	}
   381  
   382  	podIP := parseRemoteAddr(r.RemoteAddr)
   383  	tokenRequest := parseTokenRequest(r)
   384  
   385  	if podIP == "" {
   386  		klog.Error("request remote address is empty")
   387  		http.Error(w, "request remote address is empty", http.StatusInternalServerError)
   388  		return
   389  	}
   390  	if !tokenRequest.ValidateResourceParamExists() {
   391  		klog.Warning("parameter resource cannot be empty")
   392  		http.Error(w, "parameter resource cannot be empty", http.StatusBadRequest)
   393  		return
   394  	}
   395  
   396  	podns, podname, rsName, selectors, err := s.KubeClient.GetPodInfo(podIP)
   397  	if err != nil {
   398  		klog.Errorf("failed to get pod info from pod IP: %s, error: %+v", podIP, err)
   399  		http.Error(w, err.Error(), http.StatusInternalServerError)
   400  		return
   401  	}
   402  	// set ns for using in metrics
   403  	ns = podns
   404  	exceptionList, err := s.KubeClient.ListPodIdentityExceptions(podns)
   405  	if err != nil {
   406  		klog.Errorf("getting list of AzurePodIdentityException in %s namespace failed with error: %+v", podns, err)
   407  		http.Error(w, err.Error(), http.StatusInternalServerError)
   408  		return
   409  	}
   410  
   411  	// If its mic, then just directly get the token and pass back.
   412  	if pod.IsPodExcepted(selectors.MatchLabels, *exceptionList) || s.isMIC(podns, rsName) {
   413  		klog.Infof("exception pod %s/%s token handling", podns, podname)
   414  		response, errorCode, err := s.getTokenForExceptedPod(tokenRequest.ClientID, tokenRequest.Resource)
   415  		if err != nil {
   416  			klog.Errorf("failed to get service principal token for pod:%s/%s with error code %d, error: %+v", podns, podname, errorCode, err)
   417  			http.Error(w, err.Error(), errorCode)
   418  			return
   419  		}
   420  		w.Header().Set("Content-Length", strconv.Itoa(len(response)))
   421  		_, _ = w.Write(response)
   422  		return
   423  	}
   424  
   425  	podID, err := s.TokenClient.GetIdentities(r.Context(), podns, podname, tokenRequest.ClientID, tokenRequest.ResourceID)
   426  	if err != nil {
   427  		klog.Errorf("failed to get matching identities for pod: %s/%s, error: %+v", podns, podname, err)
   428  		httpErrorCode := http.StatusNotFound
   429  		if s.SetRetryAfterHeader {
   430  			httpErrorCode = http.StatusServiceUnavailable
   431  			// setting it to 20s to allow MIC to finish processing current cycle and pick up this
   432  			// pod in the next sync cycle
   433  			w.Header().Set(headerRetryAfter, "20")
   434  		}
   435  		http.Error(w, err.Error(), httpErrorCode)
   436  		return
   437  	}
   438  
   439  	tokens, err := s.TokenClient.GetTokens(r.Context(), tokenRequest.ClientID, tokenRequest.Resource, *podID)
   440  	if err != nil {
   441  		klog.Errorf("failed to get service principal token for pod: %s/%s, error: %+v", podns, podname, err)
   442  		httpErrorCode := http.StatusForbidden
   443  		if auth.IsHealthCheckError(err) {
   444  			// the adal library performs a health check prior to making the token request
   445  			// if the health check fails, we want to return a 503 instead of 403
   446  			// for health check failures, the error is not a token refresh error
   447  			httpErrorCode = http.StatusServiceUnavailable
   448  		}
   449  		http.Error(w, err.Error(), httpErrorCode)
   450  		return
   451  	}
   452  
   453  	var v interface{}
   454  	if len(tokens) == 1 {
   455  		v = newMSIResponse(*tokens[0])
   456  	} else {
   457  		var msiResp []msiResponse
   458  		for _, token := range tokens {
   459  			msiResp = append(msiResp, newMSIResponse(*token))
   460  		}
   461  		v = msiResp
   462  	}
   463  
   464  	response, err := json.Marshal(v)
   465  	if err != nil {
   466  		klog.Errorf("failed to marshal service principal token for pod: %s/%s, error: %+v", podns, podname, err)
   467  		http.Error(w, err.Error(), http.StatusInternalServerError)
   468  		return
   469  	}
   470  	w.Header().Set("Content-Length", strconv.Itoa(len(response)))
   471  	_, _ = w.Write(response)
   472  	return
   473  }
   474  
   475  // Error replies to the request without the specified metadata header.
   476  // It does not otherwise end the request; the caller should ensure no further
   477  // writes are done to w.
   478  func metadataNotSpecifiedError(w http.ResponseWriter) {
   479  	metadataResp := MetadataResponse{
   480  		Error:            "invalid_request",
   481  		ErrorDescription: "Required metadata header not specified",
   482  	}
   483  	response, err := json.Marshal(metadataResp)
   484  	if err != nil {
   485  		klog.Errorf("failed to marshal metadata response, %+v", err)
   486  		http.Error(w, err.Error(), http.StatusInternalServerError)
   487  		return
   488  	}
   489  
   490  	w.Header().Set("Content-Type", "application/json; charset=utf-8")
   491  	w.WriteHeader(http.StatusBadRequest)
   492  	fmt.Fprintln(w, string(response))
   493  }
   494  
   495  func parseMetadata(r *http.Request) (metadata string) {
   496  	return r.Header.Get("metadata")
   497  }
   498  
   499  func parsePodInfo(r *http.Request) (podns string, podname string) {
   500  	podns = r.Header.Get("podns")
   501  	podname = r.Header.Get("podname")
   502  
   503  	return podns, podname
   504  }
   505  
   506  func parseRemoteAddr(addr string) string {
   507  	n := strings.IndexByte(addr, ':')
   508  	if n <= 1 {
   509  		return ""
   510  	}
   511  	hostname := addr[0:n]
   512  	if net.ParseIP(hostname) == nil {
   513  		return ""
   514  	}
   515  	return hostname
   516  }
   517  
   518  // TokenRequest contains the client and resource ID token, as well as what resource the client is trying to access.
   519  type TokenRequest struct {
   520  	// ClientID identifies, by Azure AD client ID, a specific identity to use
   521  	// when authenticating to Azure AD. It is mutually exclusive with
   522  	// MsiResourceID.
   523  	// Example: 77788899-f67e-42e1-9a78-89985f6bff3e
   524  	ClientID string
   525  
   526  	// MsiResourceID identifies, by urlencoded ARM resource ID, a specific
   527  	// identity to use when authenticating to Azure AD. It is mutually exclusive
   528  	// with ClientID.
   529  	// Example: /subscriptions/<subid>/resourcegroups/<resourcegroup>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/<name>
   530  	ResourceID string
   531  
   532  	// Resource is the urlencoded URI of the resource for the requested AD token.
   533  	// Example: https://vault.azure.net.
   534  	Resource string
   535  }
   536  
   537  // ValidateResourceParamExists returns true if there exists a resource parameter from the request.
   538  func (r TokenRequest) ValidateResourceParamExists() bool {
   539  	// check if resource exists in the request
   540  	// if resource doesn't exist in the request, then adal libraries will return the same error
   541  	// IMDS also returns an error with 400 response code if resource parameter is empty
   542  	// this is done to emulate same behavior observed while requesting token from IMDS
   543  	return len(r.Resource) != 0
   544  }
   545  
   546  func parseTokenRequest(r *http.Request) (request TokenRequest) {
   547  	vals := r.URL.Query()
   548  	if vals != nil {
   549  		// These are mutually exclusive values (client_id, msi_resource_id)
   550  		request.ClientID = vals.Get("client_id")
   551  		request.ResourceID = vals.Get("msi_res_id")
   552  		if len(request.ResourceID) == 0 {
   553  			request.ResourceID = vals.Get("mi_res_id")
   554  		}
   555  
   556  		request.Resource = vals.Get("resource")
   557  	}
   558  	return request
   559  }
   560  
   561  // defaultPathHandler creates a new request and returns the response body and code
   562  func (s *Server) defaultPathHandler(w http.ResponseWriter, r *http.Request) {
   563  	if s.MetadataHeaderRequired && parseMetadata(r) != "true" {
   564  		klog.Errorf("metadata header is not specified, req.method=%s reg.path=%s req.remote=%s", r.Method, r.URL.Path, parseRemoteAddr(r.RemoteAddr))
   565  		metadataNotSpecifiedError(w)
   566  		return
   567  	}
   568  
   569  	client := &http.Client{}
   570  	req, err := http.NewRequest(r.Method, r.URL.String(), r.Body)
   571  	if err != nil || req == nil {
   572  		klog.Errorf("failed creating a new request for %s, error: %+v", r.URL.String(), err)
   573  		http.Error(w, err.Error(), http.StatusInternalServerError)
   574  		return
   575  	}
   576  	host := fmt.Sprintf("%s:%s", s.MetadataIP, s.MetadataPort)
   577  	req.Host = host
   578  	req.URL.Host = host
   579  	req.URL.Scheme = "http"
   580  	if r.Header != nil {
   581  		copyHeader(req.Header, r.Header)
   582  	}
   583  	resp, err := client.Do(req)
   584  	if err != nil {
   585  		klog.Errorf("failed executing request for %s, error: %+v", req.URL.String(), err)
   586  		http.Error(w, err.Error(), http.StatusInternalServerError)
   587  		return
   588  	}
   589  	defer resp.Body.Close()
   590  
   591  	body, err := io.ReadAll(resp.Body)
   592  	if err != nil {
   593  		klog.Errorf("failed to read response body for %s, error: %+v", req.URL.String(), err)
   594  		http.Error(w, err.Error(), http.StatusInternalServerError)
   595  	}
   596  	copyHeader(w.Header(), resp.Header)
   597  	w.WriteHeader(resp.StatusCode)
   598  	_, _ = w.Write(body)
   599  }
   600  
   601  // forbiddenHandler responds to any request with HTTP 403 Forbidden
   602  func forbiddenHandler(w http.ResponseWriter, r *http.Request) {
   603  	http.Error(w, "Request blocked by AAD Pod Identity NMI", http.StatusForbidden)
   604  }
   605  
   606  // invalidTokenPathHandler responds to invalid token requests with HTTP 400 Bad Request
   607  func invalidTokenPathHandler(w http.ResponseWriter, r *http.Request) {
   608  	http.Error(w, "Invalid request", http.StatusBadRequest)
   609  }
   610  
   611  func copyHeader(dst, src http.Header) {
   612  	for k, vv := range src {
   613  		for _, v := range vv {
   614  			dst.Add(k, v)
   615  		}
   616  	}
   617  }
   618  
   619  func handleTermination() {
   620  	klog.Info("received SIGTERM, shutting down")
   621  
   622  	exitCode := 0
   623  	// clean up iptables
   624  	if err := iptables.DeleteCustomChain(); err != nil {
   625  		klog.Errorf("failed to clean up during shutdown, error: %+v", err)
   626  		exitCode = 1
   627  	}
   628  
   629  	// wait for pod to delete
   630  	klog.Info("handled termination, awaiting pod deletion")
   631  	time.Sleep(10 * time.Second)
   632  
   633  	klog.Infof("exiting with %v", exitCode)
   634  	os.Exit(exitCode)
   635  }