2024-11-22 16:10:05 +00:00
|
|
|
package diagnostic
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"net/http"
|
2024-11-25 19:24:51 +00:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strconv"
|
2024-11-22 16:10:05 +00:00
|
|
|
"time"
|
|
|
|
|
2024-11-25 18:43:32 +00:00
|
|
|
"github.com/google/uuid"
|
2024-11-22 16:10:05 +00:00
|
|
|
"github.com/rs/zerolog"
|
2024-11-25 19:24:51 +00:00
|
|
|
"github.com/urfave/cli/v2"
|
2024-11-25 18:43:32 +00:00
|
|
|
|
2024-11-25 19:24:51 +00:00
|
|
|
"github.com/cloudflare/cloudflared/logger"
|
2024-11-25 18:43:32 +00:00
|
|
|
"github.com/cloudflare/cloudflared/tunnelstate"
|
2024-11-22 16:10:05 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Handler struct {
|
2024-11-25 19:24:51 +00:00
|
|
|
log *zerolog.Logger
|
|
|
|
timeout time.Duration
|
|
|
|
systemCollector SystemCollector
|
|
|
|
tunnelID uuid.UUID
|
|
|
|
connectorID uuid.UUID
|
|
|
|
tracker *tunnelstate.ConnTracker
|
|
|
|
cli *cli.Context
|
|
|
|
flagInclusionList []string
|
2024-11-22 16:10:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewDiagnosticHandler(
|
|
|
|
log *zerolog.Logger,
|
|
|
|
timeout time.Duration,
|
|
|
|
systemCollector SystemCollector,
|
2024-11-25 18:43:32 +00:00
|
|
|
tunnelID uuid.UUID,
|
|
|
|
connectorID uuid.UUID,
|
|
|
|
tracker *tunnelstate.ConnTracker,
|
2024-11-25 19:24:51 +00:00
|
|
|
cli *cli.Context,
|
|
|
|
flagInclusionList []string,
|
2024-11-22 16:10:05 +00:00
|
|
|
) *Handler {
|
2024-11-25 18:43:32 +00:00
|
|
|
logger := log.With().Logger()
|
2024-11-22 16:10:05 +00:00
|
|
|
if timeout == 0 {
|
|
|
|
timeout = defaultCollectorTimeout
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Handler{
|
2024-11-25 19:24:51 +00:00
|
|
|
log: &logger,
|
|
|
|
timeout: timeout,
|
|
|
|
systemCollector: systemCollector,
|
|
|
|
tunnelID: tunnelID,
|
|
|
|
connectorID: connectorID,
|
|
|
|
tracker: tracker,
|
|
|
|
cli: cli,
|
|
|
|
flagInclusionList: flagInclusionList,
|
2024-11-22 16:10:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (handler *Handler) SystemHandler(writer http.ResponseWriter, request *http.Request) {
|
|
|
|
logger := handler.log.With().Str(collectorField, systemCollectorName).Logger()
|
|
|
|
logger.Info().Msg("Collection started")
|
|
|
|
|
2024-11-25 18:43:32 +00:00
|
|
|
defer logger.Info().Msg("Collection finished")
|
2024-11-22 16:10:05 +00:00
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(request.Context(), handler.timeout)
|
|
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
info, rawInfo, err := handler.systemCollector.Collect(ctx)
|
|
|
|
if err != nil {
|
|
|
|
logger.Error().Err(err).Msg("error occurred whilst collecting system information")
|
|
|
|
|
|
|
|
if rawInfo != "" {
|
|
|
|
logger.Info().Msg("using raw information fallback")
|
|
|
|
bytes := []byte(rawInfo)
|
|
|
|
writeResponse(writer, bytes, &logger)
|
|
|
|
} else {
|
|
|
|
logger.Error().Msg("no raw information available")
|
|
|
|
writer.WriteHeader(http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if info == nil {
|
|
|
|
logger.Error().Msgf("system information collection is nil")
|
|
|
|
writer.WriteHeader(http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
|
|
|
|
encoder := json.NewEncoder(writer)
|
|
|
|
|
|
|
|
err = encoder.Encode(info)
|
|
|
|
if err != nil {
|
|
|
|
logger.Error().Err(err).Msgf("error occurred whilst serializing information")
|
|
|
|
writer.WriteHeader(http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-25 18:43:32 +00:00
|
|
|
type tunnelStateResponse struct {
|
|
|
|
TunnelID uuid.UUID `json:"tunnelID,omitempty"`
|
|
|
|
ConnectorID uuid.UUID `json:"connectorID,omitempty"`
|
|
|
|
Connections []tunnelstate.IndexedConnectionInfo `json:"connections,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (handler *Handler) TunnelStateHandler(writer http.ResponseWriter, _ *http.Request) {
|
|
|
|
log := handler.log.With().Str(collectorField, tunnelStateCollectorName).Logger()
|
|
|
|
log.Info().Msg("Collection started")
|
|
|
|
|
|
|
|
defer log.Info().Msg("Collection finished")
|
|
|
|
|
|
|
|
body := tunnelStateResponse{
|
|
|
|
handler.tunnelID,
|
|
|
|
handler.connectorID,
|
|
|
|
handler.tracker.GetActiveConnections(),
|
|
|
|
}
|
|
|
|
encoder := json.NewEncoder(writer)
|
|
|
|
|
|
|
|
err := encoder.Encode(body)
|
|
|
|
if err != nil {
|
|
|
|
handler.log.Error().Err(err).Msgf("error occurred whilst serializing information")
|
|
|
|
writer.WriteHeader(http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-25 19:24:51 +00:00
|
|
|
func (handler *Handler) ConfigurationHandler(writer http.ResponseWriter, _ *http.Request) {
|
|
|
|
log := handler.log.With().Str(collectorField, configurationCollectorName).Logger()
|
|
|
|
log.Info().Msg("Collection started")
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
log.Info().Msg("Collection finished")
|
|
|
|
}()
|
|
|
|
|
|
|
|
flagsNames := handler.cli.FlagNames()
|
|
|
|
flags := make(map[string]string, len(flagsNames))
|
|
|
|
|
|
|
|
for _, flag := range flagsNames {
|
|
|
|
value := handler.cli.String(flag)
|
|
|
|
|
|
|
|
// empty values are not relevant
|
|
|
|
if value == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// exclude flags that are sensitive
|
|
|
|
isIncluded := handler.isFlagIncluded(flag)
|
|
|
|
if !isIncluded {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
switch flag {
|
|
|
|
case logger.LogDirectoryFlag:
|
|
|
|
case logger.LogFileFlag:
|
|
|
|
{
|
|
|
|
// the log directory may be relative to the instance thus it must be resolved
|
|
|
|
absolute, err := filepath.Abs(value)
|
|
|
|
if err != nil {
|
|
|
|
handler.log.Error().Err(err).Msgf("could not convert %s path to absolute", flag)
|
|
|
|
} else {
|
|
|
|
flags[flag] = absolute
|
|
|
|
}
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
flags[flag] = value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The UID is included to help the
|
|
|
|
// diagnostic tool to understand
|
|
|
|
// if this instance is managed or not.
|
|
|
|
flags[configurationKeyUid] = strconv.Itoa(os.Getuid())
|
|
|
|
encoder := json.NewEncoder(writer)
|
|
|
|
|
|
|
|
err := encoder.Encode(flags)
|
|
|
|
if err != nil {
|
|
|
|
handler.log.Error().Err(err).Msgf("error occurred whilst serializing response")
|
|
|
|
writer.WriteHeader(http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (handler *Handler) isFlagIncluded(flag string) bool {
|
|
|
|
isIncluded := false
|
|
|
|
|
|
|
|
for _, include := range handler.flagInclusionList {
|
|
|
|
if include == flag {
|
|
|
|
isIncluded = true
|
|
|
|
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return isIncluded
|
|
|
|
}
|
|
|
|
|
|
|
|
func writeResponse(w http.ResponseWriter, bytes []byte, logger *zerolog.Logger) {
|
|
|
|
bytesWritten, err := w.Write(bytes)
|
2024-11-22 16:10:05 +00:00
|
|
|
if err != nil {
|
|
|
|
logger.Error().Err(err).Msg("error occurred writing response")
|
|
|
|
} else if bytesWritten != len(bytes) {
|
|
|
|
logger.Error().Msgf("error incomplete write response %d/%d", bytesWritten, len(bytes))
|
|
|
|
}
|
|
|
|
}
|