istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/xds/debuggen.go (about)

     1  // Copyright Istio Authors
     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 xds
    16  
    17  import (
    18  	"bytes"
    19  	"encoding/json"
    20  	"net/http"
    21  	"net/url"
    22  	"strconv"
    23  
    24  	discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
    25  	"google.golang.org/grpc/codes"
    26  	"google.golang.org/grpc/status"
    27  	anypb "google.golang.org/protobuf/types/known/anypb"
    28  
    29  	"istio.io/istio/pilot/pkg/model"
    30  	v3 "istio.io/istio/pilot/pkg/xds/v3"
    31  )
    32  
    33  var activeNamespaceDebuggers = map[string]struct{}{
    34  	"config_dump": {},
    35  	"ndsz":        {},
    36  	"edsz":        {},
    37  }
    38  
    39  // DebugGen is a Generator for istio debug info
    40  type DebugGen struct {
    41  	Server          *DiscoveryServer
    42  	SystemNamespace string
    43  	DebugMux        *http.ServeMux
    44  }
    45  
    46  type ResponseCapture struct {
    47  	body        *bytes.Buffer
    48  	header      map[string]string
    49  	wroteHeader bool
    50  }
    51  
    52  func (r ResponseCapture) Header() http.Header {
    53  	header := make(http.Header)
    54  	for k, v := range r.header {
    55  		header.Set(k, v)
    56  	}
    57  	return header
    58  }
    59  
    60  func (r ResponseCapture) Write(i []byte) (int, error) {
    61  	return r.body.Write(i)
    62  }
    63  
    64  func (r ResponseCapture) WriteHeader(statusCode int) {
    65  	r.header["statusCode"] = strconv.Itoa(statusCode)
    66  }
    67  
    68  func NewResponseCapture() *ResponseCapture {
    69  	return &ResponseCapture{
    70  		header:      make(map[string]string),
    71  		body:        new(bytes.Buffer),
    72  		wroteHeader: false,
    73  	}
    74  }
    75  
    76  func NewDebugGen(s *DiscoveryServer, systemNamespace string, debugMux *http.ServeMux) *DebugGen {
    77  	return &DebugGen{
    78  		Server:          s,
    79  		SystemNamespace: systemNamespace,
    80  		DebugMux:        debugMux,
    81  	}
    82  }
    83  
    84  // Generate XDS debug responses according to the incoming debug request
    85  func (dg *DebugGen) Generate(proxy *model.Proxy, w *model.WatchedResource, req *model.PushRequest) (model.Resources, model.XdsLogDetails, error) {
    86  	if err := validateProxyAuthentication(proxy, w); err != nil {
    87  		return nil, model.DefaultXdsLogDetails, err
    88  	}
    89  
    90  	resourceName, err := parseAndValidateDebugRequest(proxy, w, dg)
    91  	if err != nil {
    92  		return nil, model.DefaultXdsLogDetails, err
    93  	}
    94  
    95  	buffer := processDebugRequest(dg, resourceName)
    96  
    97  	res := model.Resources{&discovery.Resource{
    98  		Name: resourceName,
    99  		Resource: &anypb.Any{
   100  			TypeUrl: v3.DebugType,
   101  			Value:   buffer.Bytes(),
   102  		},
   103  	}}
   104  	return res, model.DefaultXdsLogDetails, nil
   105  }
   106  
   107  // GenerateDeltas XDS debug responses according to the incoming debug request
   108  func (dg *DebugGen) GenerateDeltas(
   109  	proxy *model.Proxy,
   110  	req *model.PushRequest,
   111  	w *model.WatchedResource,
   112  ) (model.Resources, model.DeletedResources, model.XdsLogDetails, bool, error) {
   113  	if err := validateProxyAuthentication(proxy, w); err != nil {
   114  		return nil, nil, model.DefaultXdsLogDetails, true, err
   115  	}
   116  
   117  	resourceName, err := parseAndValidateDebugRequest(proxy, w, dg)
   118  	if err != nil {
   119  		return nil, nil, model.DefaultXdsLogDetails, true, err
   120  	}
   121  
   122  	buffer := processDebugRequest(dg, resourceName)
   123  
   124  	res := model.Resources{&discovery.Resource{
   125  		Name: resourceName,
   126  		Resource: &anypb.Any{
   127  			TypeUrl: v3.DebugType,
   128  			Value:   buffer.Bytes(),
   129  		},
   130  	}}
   131  	return res, nil, model.DefaultXdsLogDetails, true, nil
   132  }
   133  
   134  func validateProxyAuthentication(proxy *model.Proxy, w *model.WatchedResource) error {
   135  	if proxy.VerifiedIdentity == nil {
   136  		log.Warnf("proxy %s is not authorized to receive debug. Ensure you are connecting over TLS port and are authenticated.", proxy.ID)
   137  		return status.Error(codes.Unauthenticated, "authentication required")
   138  	}
   139  	if w.ResourceNames == nil || len(w.ResourceNames) != 1 {
   140  		return status.Error(codes.InvalidArgument, "exactly one debug request is required")
   141  	}
   142  	return nil
   143  }
   144  
   145  func parseAndValidateDebugRequest(proxy *model.Proxy, w *model.WatchedResource, dg *DebugGen) (string, error) {
   146  	resourceName := w.ResourceNames[0]
   147  	u, _ := url.Parse(resourceName)
   148  	debugType := u.Path
   149  	identity := proxy.VerifiedIdentity
   150  	if identity.Namespace != dg.SystemNamespace {
   151  		if _, ok := activeNamespaceDebuggers[debugType]; !ok {
   152  			return "", status.Errorf(codes.PermissionDenied, "the debug info is not available for current identity: %q", identity)
   153  		}
   154  	}
   155  	return resourceName, nil
   156  }
   157  
   158  func processDebugRequest(dg *DebugGen, resourceName string) bytes.Buffer {
   159  	var buffer bytes.Buffer
   160  	debugURL := "/debug/" + resourceName
   161  	hreq, _ := http.NewRequest(http.MethodGet, debugURL, nil)
   162  	handler, _ := dg.DebugMux.Handler(hreq)
   163  	response := NewResponseCapture()
   164  	handler.ServeHTTP(response, hreq)
   165  	if response.wroteHeader && len(response.header) >= 1 {
   166  		header, _ := json.Marshal(response.header)
   167  		buffer.Write(header)
   168  	}
   169  	buffer.Write(response.body.Bytes())
   170  	return buffer
   171  }