blob: 6631dc14e3aee4c37bd507696f8a69d46aae76f4 [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package xds
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
)
import (
discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
any "google.golang.org/protobuf/types/known/anypb"
)
import (
"github.com/apache/dubbo-go-pixiu/pilot/pkg/model"
)
const (
// TypeDebug requests debug info from istio, a secured implementation for istio debug interface
TypeDebug = "istio.io/debug"
)
var activeNamespaceDebuggers = map[string]struct{}{
"config_dump": {},
"ndsz": {},
"edsz": {},
}
// DebugGen is a Generator for istio debug info
type DebugGen struct {
Server *DiscoveryServer
SystemNamespace string
DebugMux *http.ServeMux
}
type ResponseCapture struct {
body *bytes.Buffer
header map[string]string
wroteHeader bool
}
func (r ResponseCapture) Header() http.Header {
header := make(http.Header)
for k, v := range r.header {
header.Set(k, v)
}
return header
}
func (r ResponseCapture) Write(i []byte) (int, error) {
return r.body.Write(i)
}
func (r ResponseCapture) WriteHeader(statusCode int) {
r.header["statusCode"] = strconv.Itoa(statusCode)
}
func NewResponseCapture() *ResponseCapture {
return &ResponseCapture{
header: make(map[string]string),
body: new(bytes.Buffer),
wroteHeader: false,
}
}
func NewDebugGen(s *DiscoveryServer, systemNamespace string) *DebugGen {
return &DebugGen{
Server: s,
SystemNamespace: systemNamespace,
}
}
// Generate XDS debug responses according to the incoming debug request
func (dg *DebugGen) Generate(proxy *model.Proxy, w *model.WatchedResource, req *model.PushRequest) (model.Resources, model.XdsLogDetails, error) {
res := model.Resources{}
var buffer bytes.Buffer
if proxy.VerifiedIdentity == nil {
log.Warnf("proxy %s is not authorized to receive debug. Ensure you are connecting over TLS port and are authenticated.", proxy.ID)
return nil, model.DefaultXdsLogDetails, fmt.Errorf("authentication required")
}
if w.ResourceNames == nil {
return res, model.DefaultXdsLogDetails, fmt.Errorf("debug type is required")
}
if len(w.ResourceNames) != 1 {
return res, model.DefaultXdsLogDetails, fmt.Errorf("only one debug request is allowed")
}
resourceName := w.ResourceNames[0]
u, _ := url.Parse(resourceName)
debugType := u.Path
identity := proxy.VerifiedIdentity
if identity.Namespace != dg.SystemNamespace {
shouldAllow := false
if _, ok := activeNamespaceDebuggers[debugType]; ok {
shouldAllow = true
}
if !shouldAllow {
return res, model.DefaultXdsLogDetails, fmt.Errorf("the debug info is not available for current identity: %q", identity)
}
}
debugURL := "/debug/" + resourceName
hreq, _ := http.NewRequest(http.MethodGet, debugURL, nil)
handler, _ := dg.DebugMux.Handler(hreq)
response := NewResponseCapture()
handler.ServeHTTP(response, hreq)
if response.wroteHeader && len(response.header) >= 1 {
header, _ := json.Marshal(response.header)
buffer.Write(header)
}
buffer.Write(response.body.Bytes())
res = append(res, &discovery.Resource{
Name: resourceName,
Resource: &any.Any{
TypeUrl: TypeDebug,
Value: buffer.Bytes(),
},
})
return res, model.DefaultXdsLogDetails, nil
}