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 }