Merge pull request #326 from up9inc/develop

Develop -> Main
This commit is contained in:
Igor Gov 2021-10-07 12:28:21 +03:00 committed by GitHub
commit 8b47dba05d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 1748 additions and 437 deletions

View File

@ -762,6 +762,116 @@ func TestTapRegexMasking(t *testing.T) {
} }
} }
func TestTapIgnoredUserAgents(t *testing.T) {
if testing.Short() {
t.Skip("ignored acceptance test")
}
cliPath, cliPathErr := getCliPath()
if cliPathErr != nil {
t.Errorf("failed to get cli path, err: %v", cliPathErr)
return
}
tapCmdArgs := getDefaultTapCommandArgs()
tapNamespace := getDefaultTapNamespace()
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
ignoredUserAgentValue := "ignore"
tapCmdArgs = append(tapCmdArgs, "--set", fmt.Sprintf("tap.ignored-user-agents=%v", ignoredUserAgentValue))
tapCmd := exec.Command(cliPath, tapCmdArgs...)
t.Logf("running command: %v", tapCmd.String())
t.Cleanup(func() {
if err := cleanupCommand(tapCmd); err != nil {
t.Logf("failed to cleanup tap command, err: %v", err)
}
})
if err := tapCmd.Start(); err != nil {
t.Errorf("failed to start tap command, err: %v", err)
return
}
apiServerUrl := getApiServerUrl(defaultApiServerPort)
if err := waitTapPodsReady(apiServerUrl); err != nil {
t.Errorf("failed to start tap pods on time, err: %v", err)
return
}
proxyUrl := getProxyUrl(defaultNamespaceName, defaultServiceName)
ignoredUserAgentCustomHeader := "Ignored-User-Agent"
headers := map[string]string {"User-Agent": ignoredUserAgentValue, ignoredUserAgentCustomHeader: ""}
for i := 0; i < defaultEntriesCount; i++ {
if _, requestErr := executeHttpGetRequestWithHeaders(fmt.Sprintf("%v/get", proxyUrl), headers); requestErr != nil {
t.Errorf("failed to send proxy request, err: %v", requestErr)
return
}
}
for i := 0; i < defaultEntriesCount; i++ {
if _, requestErr := executeHttpGetRequest(fmt.Sprintf("%v/get", proxyUrl)); requestErr != nil {
t.Errorf("failed to send proxy request, err: %v", requestErr)
return
}
}
ignoredUserAgentsCheckFunc := func() error {
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
entriesUrl := fmt.Sprintf("%v/api/entries?limit=%v&operator=lt&timestamp=%v", apiServerUrl, defaultEntriesCount * 2, timestamp)
requestResult, requestErr := executeHttpGetRequest(entriesUrl)
if requestErr != nil {
return fmt.Errorf("failed to get entries, err: %v", requestErr)
}
entries := requestResult.([]interface{})
if len(entries) == 0 {
return fmt.Errorf("unexpected entries result - Expected more than 0 entries")
}
for _, entryInterface := range entries {
entryUrl := fmt.Sprintf("%v/api/entries/%v", apiServerUrl, entryInterface.(map[string]interface{})["id"])
requestResult, requestErr = executeHttpGetRequest(entryUrl)
if requestErr != nil {
return fmt.Errorf("failed to get entry, err: %v", requestErr)
}
data := requestResult.(map[string]interface{})["data"].(map[string]interface{})
entryJson := data["entry"].(string)
var entry map[string]interface{}
if parseErr := json.Unmarshal([]byte(entryJson), &entry); parseErr != nil {
return fmt.Errorf("failed to parse entry, err: %v", parseErr)
}
entryRequest := entry["request"].(map[string]interface{})
entryPayload := entryRequest["payload"].(map[string]interface{})
entryDetails := entryPayload["details"].(map[string]interface{})
entryHeaders := entryDetails["headers"].([]interface{})
for _, headerInterface := range entryHeaders {
header := headerInterface.(map[string]interface{})
if header["name"].(string) != ignoredUserAgentCustomHeader {
continue
}
return fmt.Errorf("unexpected result - user agent is not ignored")
}
}
return nil
}
if err := retriesExecute(shortRetriesCount, ignoredUserAgentsCheckFunc); err != nil {
t.Errorf("%v", err)
return
}
}
func TestTapDumpLogs(t *testing.T) { func TestTapDumpLogs(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("ignored acceptance test") t.Skip("ignored acceptance test")

View File

@ -172,6 +172,21 @@ func executeHttpRequest(response *http.Response, requestErr error) (interface{},
return jsonBytesToInterface(data) return jsonBytesToInterface(data)
} }
func executeHttpGetRequestWithHeaders(url string, headers map[string]string) (interface{}, error) {
request, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
for headerKey, headerValue := range headers {
request.Header.Add(headerKey, headerValue)
}
client := &http.Client{}
response, requestErr := client.Do(request)
return executeHttpRequest(response, requestErr)
}
func executeHttpGetRequest(url string) (interface{}, error) { func executeHttpGetRequest(url string) (interface{}, error) {
response, requestErr := http.Get(url) response, requestErr := http.Get(url)
return executeHttpRequest(response, requestErr) return executeHttpRequest(response, requestErr)

View File

@ -4,6 +4,13 @@ import (
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
"github.com/gin-contrib/static"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/romana/rlog"
"github.com/up9inc/mizu/shared"
"github.com/up9inc/mizu/tap"
tapApi "github.com/up9inc/mizu/tap/api"
"io/ioutil" "io/ioutil"
"log" "log"
"mizuserver/pkg/api" "mizuserver/pkg/api"
@ -18,15 +25,6 @@ import (
"path/filepath" "path/filepath"
"plugin" "plugin"
"sort" "sort"
"strings"
"github.com/gin-contrib/static"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/romana/rlog"
"github.com/up9inc/mizu/shared"
"github.com/up9inc/mizu/tap"
tapApi "github.com/up9inc/mizu/tap/api"
) )
var tapperMode = flag.Bool("tap", false, "Run in tapper mode without API") var tapperMode = flag.Bool("tap", false, "Run in tapper mode without API")
@ -59,7 +57,7 @@ func main() {
filteredOutputItemsChannel := make(chan *tapApi.OutputChannelItem) filteredOutputItemsChannel := make(chan *tapApi.OutputChannelItem)
tap.StartPassiveTapper(tapOpts, outputItemsChannel, extensions, filteringOptions) tap.StartPassiveTapper(tapOpts, outputItemsChannel, extensions, filteringOptions)
go filterItems(outputItemsChannel, filteredOutputItemsChannel, filteringOptions) go filterItems(outputItemsChannel, filteredOutputItemsChannel)
go api.StartReadingEntries(filteredOutputItemsChannel, nil, extensionsMap) go api.StartReadingEntries(filteredOutputItemsChannel, nil, extensionsMap)
hostApi(nil) hostApi(nil)
@ -77,7 +75,7 @@ func main() {
filteredOutputItemsChannel := make(chan *tapApi.OutputChannelItem) filteredOutputItemsChannel := make(chan *tapApi.OutputChannelItem)
tap.StartPassiveTapper(tapOpts, filteredOutputItemsChannel, extensions, filteringOptions) tap.StartPassiveTapper(tapOpts, filteredOutputItemsChannel, extensions, filteringOptions)
socketConnection, err := utils.ConnectToSocketServer(*apiServerAddress) socketConnection, _, err := websocket.DefaultDialer.Dial(*apiServerAddress, nil)
if err != nil { if err != nil {
panic(fmt.Sprintf("Error connecting to socket server at %s %v", *apiServerAddress, err)) panic(fmt.Sprintf("Error connecting to socket server at %s %v", *apiServerAddress, err))
} }
@ -90,7 +88,7 @@ func main() {
outputItemsChannel := make(chan *tapApi.OutputChannelItem) outputItemsChannel := make(chan *tapApi.OutputChannelItem)
filteredOutputItemsChannel := make(chan *tapApi.OutputChannelItem) filteredOutputItemsChannel := make(chan *tapApi.OutputChannelItem)
go filterItems(outputItemsChannel, filteredOutputItemsChannel, filteringOptions) go filterItems(outputItemsChannel, filteredOutputItemsChannel)
go api.StartReadingEntries(filteredOutputItemsChannel, nil, extensionsMap) go api.StartReadingEntries(filteredOutputItemsChannel, nil, extensionsMap)
hostApi(outputItemsChannel) hostApi(outputItemsChannel)
@ -98,7 +96,7 @@ func main() {
outputItemsChannel := make(chan *tapApi.OutputChannelItem, 1000) outputItemsChannel := make(chan *tapApi.OutputChannelItem, 1000)
filteredHarChannel := make(chan *tapApi.OutputChannelItem) filteredHarChannel := make(chan *tapApi.OutputChannelItem)
go filterItems(outputItemsChannel, filteredHarChannel, filteringOptions) go filterItems(outputItemsChannel, filteredHarChannel)
go api.StartReadingEntries(filteredHarChannel, harsDir, extensionsMap) go api.StartReadingEntries(filteredHarChannel, harsDir, extensionsMap)
hostApi(nil) hostApi(nil)
} }
@ -230,7 +228,7 @@ func getTrafficFilteringOptions() *tapApi.TrafficFilteringOptions {
filteringOptionsJson := os.Getenv(shared.MizuFilteringOptionsEnvVar) filteringOptionsJson := os.Getenv(shared.MizuFilteringOptionsEnvVar)
if filteringOptionsJson == "" { if filteringOptionsJson == "" {
return &tapApi.TrafficFilteringOptions{ return &tapApi.TrafficFilteringOptions{
HealthChecksUserAgentHeaders: []string{}, IgnoredUserAgents: []string{},
} }
} }
var filteringOptions tapApi.TrafficFilteringOptions var filteringOptions tapApi.TrafficFilteringOptions
@ -242,42 +240,16 @@ func getTrafficFilteringOptions() *tapApi.TrafficFilteringOptions {
return &filteringOptions return &filteringOptions
} }
func filterItems(inChannel <-chan *tapApi.OutputChannelItem, outChannel chan *tapApi.OutputChannelItem, filterOptions *tapApi.TrafficFilteringOptions) { func filterItems(inChannel <-chan *tapApi.OutputChannelItem, outChannel chan *tapApi.OutputChannelItem) {
for message := range inChannel { for message := range inChannel {
if message.ConnectionInfo.IsOutgoing && api.CheckIsServiceIP(message.ConnectionInfo.ServerIP) { if message.ConnectionInfo.IsOutgoing && api.CheckIsServiceIP(message.ConnectionInfo.ServerIP) {
continue continue
} }
// TODO: move this to tappers https://up9.atlassian.net/browse/TRA-3441
if isHealthCheckByUserAgent(message, filterOptions.HealthChecksUserAgentHeaders) {
continue
}
outChannel <- message outChannel <- message
} }
} }
func isHealthCheckByUserAgent(item *tapApi.OutputChannelItem, userAgentsToIgnore []string) bool {
if item.Protocol.Name != "http" {
return false
}
request := item.Pair.Request.Payload.(map[string]interface{})
reqDetails := request["details"].(map[string]interface{})
for _, header := range reqDetails["headers"].([]interface{}) {
h := header.(map[string]interface{})
if strings.ToLower(h["name"].(string)) == "user-agent" {
for _, userAgent := range userAgentsToIgnore {
if strings.Contains(strings.ToLower(h["value"].(string)), strings.ToLower(userAgent)) {
return true
}
}
return false
}
}
return false
}
func pipeTapChannelToSocket(connection *websocket.Conn, messageDataChannel <-chan *tapApi.OutputChannelItem) { func pipeTapChannelToSocket(connection *websocket.Conn, messageDataChannel <-chan *tapApi.OutputChannelItem) {
if connection == nil { if connection == nil {
panic("Websocket connection is nil") panic("Websocket connection is nil")

View File

@ -113,9 +113,8 @@ func startReadingChannel(outputItems <-chan *tapApi.OutputChannelItem, extension
json.Unmarshal([]byte(mizuEntry.Entry), &pair) json.Unmarshal([]byte(mizuEntry.Entry), &pair)
harEntry, err := utils.NewEntry(&pair) harEntry, err := utils.NewEntry(&pair)
if err == nil { if err == nil {
rules, _ := models.RunValidationRulesState(*harEntry, mizuEntry.Service) rules, _, _ := models.RunValidationRulesState(*harEntry, mizuEntry.Service)
baseEntry.Rules = rules baseEntry.Rules = rules
baseEntry.Latency = mizuEntry.ElapsedTime
} }
} }

View File

@ -143,11 +143,13 @@ func GetEntry(c *gin.Context) {
protocol, representation, bodySize, _ := extension.Dissector.Represent(&entryData) protocol, representation, bodySize, _ := extension.Dissector.Represent(&entryData)
var rules []map[string]interface{} var rules []map[string]interface{}
var isRulesEnabled bool
if entryData.ProtocolName == "http" { if entryData.ProtocolName == "http" {
var pair tapApi.RequestResponsePair var pair tapApi.RequestResponsePair
json.Unmarshal([]byte(entryData.Entry), &pair) json.Unmarshal([]byte(entryData.Entry), &pair)
harEntry, _ := utils.NewEntry(&pair) harEntry, _ := utils.NewEntry(&pair)
_, rulesMatched := models.RunValidationRulesState(*harEntry, entryData.Service) _, rulesMatched, _isRulesEnabled := models.RunValidationRulesState(*harEntry, entryData.Service)
isRulesEnabled = _isRulesEnabled
inrec, _ := json.Marshal(rulesMatched) inrec, _ := json.Marshal(rulesMatched)
json.Unmarshal(inrec, &rules) json.Unmarshal(inrec, &rules)
} }
@ -158,6 +160,7 @@ func GetEntry(c *gin.Context) {
BodySize: bodySize, BodySize: bodySize,
Data: entryData, Data: entryData,
Rules: rules, Rules: rules,
IsRulesEnabled: isRulesEnabled,
}) })
} }

View File

@ -97,8 +97,8 @@ type ExtendedCreator struct {
Source *string `json:"_source"` Source *string `json:"_source"`
} }
func RunValidationRulesState(harEntry har.Entry, service string) (tapApi.ApplicableRules, []rules.RulesMatched) { func RunValidationRulesState(harEntry har.Entry, service string) (tapApi.ApplicableRules, []rules.RulesMatched, bool) {
resultPolicyToSend := rules.MatchRequestPolicy(harEntry, service) resultPolicyToSend, isEnabled := rules.MatchRequestPolicy(harEntry, service)
statusPolicyToSend, latency, numberOfRules := rules.PassedValidationRules(resultPolicyToSend) statusPolicyToSend, latency, numberOfRules := rules.PassedValidationRules(resultPolicyToSend)
return tapApi.ApplicableRules{Status: statusPolicyToSend, Latency: latency, NumberOfRules: numberOfRules}, resultPolicyToSend return tapApi.ApplicableRules{Status: statusPolicyToSend, Latency: latency, NumberOfRules: numberOfRules}, resultPolicyToSend, isEnabled
} }

View File

@ -4,11 +4,12 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/romana/rlog"
"reflect" "reflect"
"regexp" "regexp"
"strings" "strings"
"github.com/romana/rlog"
"github.com/google/martian/har" "github.com/google/martian/har"
"github.com/up9inc/mizu/shared" "github.com/up9inc/mizu/shared"
jsonpath "github.com/yalp/jsonpath" jsonpath "github.com/yalp/jsonpath"
@ -43,9 +44,11 @@ func ValidateService(serviceFromRule string, service string) bool {
return true return true
} }
func MatchRequestPolicy(harEntry har.Entry, service string) []RulesMatched { func MatchRequestPolicy(harEntry har.Entry, service string) (resultPolicyToSend []RulesMatched, isEnabled bool) {
enforcePolicy, _ := shared.DecodeEnforcePolicy(fmt.Sprintf("%s/%s", shared.RulePolicyPath, shared.RulePolicyFileName)) enforcePolicy, err := shared.DecodeEnforcePolicy(fmt.Sprintf("%s/%s", shared.RulePolicyPath, shared.RulePolicyFileName))
var resultPolicyToSend []RulesMatched if err == nil {
isEnabled = true
}
for _, rule := range enforcePolicy.Rules { for _, rule := range enforcePolicy.Rules {
if !ValidatePath(rule.Path, harEntry.Request.URL) || !ValidateService(rule.Service, service) { if !ValidatePath(rule.Path, harEntry.Request.URL) || !ValidateService(rule.Service, service) {
continue continue
@ -93,12 +96,12 @@ func MatchRequestPolicy(harEntry har.Entry, service string) []RulesMatched {
resultPolicyToSend = appendRulesMatched(resultPolicyToSend, true, rule) resultPolicyToSend = appendRulesMatched(resultPolicyToSend, true, rule)
} }
} }
return resultPolicyToSend return
} }
func PassedValidationRules(rulesMatched []RulesMatched) (bool, int64, int) { func PassedValidationRules(rulesMatched []RulesMatched) (bool, int64, int) {
var numberOfRulesMatched = len(rulesMatched) var numberOfRulesMatched = len(rulesMatched)
var latency int64 = -1 var responseTime int64 = -1
if numberOfRulesMatched == 0 { if numberOfRulesMatched == 0 {
return false, 0, numberOfRulesMatched return false, 0, numberOfRulesMatched
@ -106,15 +109,15 @@ func PassedValidationRules(rulesMatched []RulesMatched) (bool, int64, int) {
for _, rule := range rulesMatched { for _, rule := range rulesMatched {
if rule.Matched == false { if rule.Matched == false {
return false, latency, numberOfRulesMatched return false, responseTime, numberOfRulesMatched
} else { } else {
if strings.ToLower(rule.Rule.Type) == "latency" { if strings.ToLower(rule.Rule.Type) == "responseTime" {
if rule.Rule.Latency < latency || latency == -1 { if rule.Rule.ResponseTime < responseTime || responseTime == -1 {
latency = rule.Rule.Latency responseTime = rule.Rule.ResponseTime
} }
} }
} }
} }
return true, latency, numberOfRulesMatched return true, responseTime, numberOfRulesMatched
} }

View File

@ -1,38 +0,0 @@
package utils
import (
"github.com/gorilla/websocket"
"github.com/romana/rlog"
"time"
)
const (
DEFAULT_SOCKET_RETRIES = 3
DEFAULT_SOCKET_RETRY_SLEEP_TIME = time.Second * 10
)
func ConnectToSocketServer(address string) (*websocket.Conn, error) {
var err error
var connection *websocket.Conn
try := 0
// Connection to server fails if client pod is up before server.
// Retries solve this issue.
for try < DEFAULT_SOCKET_RETRIES {
rlog.Infof("Trying to connect to websocket: %s, attempt: %v/%v", address, try, DEFAULT_SOCKET_RETRIES)
connection, _, err = websocket.DefaultDialer.Dial(address, nil)
if err != nil {
rlog.Warnf("Failed connecting to websocket: %s, attempt: %v/%v, err: %s, (%v,%+v)", address, try, DEFAULT_SOCKET_RETRIES, err, err, err)
try++
} else {
break
}
time.Sleep(DEFAULT_SOCKET_RETRY_SLEEP_TIME)
}
if err != nil {
return nil, err
}
return connection, nil
}

View File

@ -32,7 +32,7 @@ var logsCmd = &cobra.Command{
logger.Log.Debugf("Using file path %s", config.Config.Logs.FilePath()) logger.Log.Debugf("Using file path %s", config.Config.Logs.FilePath())
if dumpLogsErr := fsUtils.DumpLogs(kubernetesProvider, ctx, config.Config.Logs.FilePath()); dumpLogsErr != nil { if dumpLogsErr := fsUtils.DumpLogs(ctx, kubernetesProvider, config.Config.Logs.FilePath()); dumpLogsErr != nil {
logger.Log.Errorf("Failed dump logs %v", dumpLogsErr) logger.Log.Errorf("Failed dump logs %v", dumpLogsErr)
} }

View File

@ -2,7 +2,6 @@ package cmd
import ( import (
"errors" "errors"
"fmt"
"os" "os"
"github.com/up9inc/mizu/cli/config" "github.com/up9inc/mizu/cli/config"
@ -68,7 +67,4 @@ func init() {
tapCmd.Flags().String(configStructs.HumanMaxEntriesDBSizeTapName, defaultTapConfig.HumanMaxEntriesDBSize, "Override the default max entries db size") tapCmd.Flags().String(configStructs.HumanMaxEntriesDBSizeTapName, defaultTapConfig.HumanMaxEntriesDBSize, "Override the default max entries db size")
tapCmd.Flags().Bool(configStructs.DryRunTapName, defaultTapConfig.DryRun, "Preview of all pods matching the regex, without tapping them") tapCmd.Flags().Bool(configStructs.DryRunTapName, defaultTapConfig.DryRun, "Preview of all pods matching the regex, without tapping them")
tapCmd.Flags().String(configStructs.EnforcePolicyFile, defaultTapConfig.EnforcePolicyFile, "Yaml file path with policy rules") tapCmd.Flags().String(configStructs.EnforcePolicyFile, defaultTapConfig.EnforcePolicyFile, "Yaml file path with policy rules")
tapCmd.Flags().String(configStructs.EnforcePolicyFileDeprecated, defaultTapConfig.EnforcePolicyFileDeprecated, "Yaml file with policy rules")
tapCmd.Flags().MarkDeprecated(configStructs.EnforcePolicyFileDeprecated, fmt.Sprintf("Use --%s instead", configStructs.EnforcePolicyFile))
} }

View File

@ -8,6 +8,10 @@ import (
"strings" "strings"
"time" "time"
"gopkg.in/yaml.v3"
core "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/wait"
"github.com/up9inc/mizu/cli/apiserver" "github.com/up9inc/mizu/cli/apiserver"
"github.com/up9inc/mizu/cli/config" "github.com/up9inc/mizu/cli/config"
"github.com/up9inc/mizu/cli/config/configStructs" "github.com/up9inc/mizu/cli/config/configStructs"
@ -22,9 +26,6 @@ import (
"github.com/up9inc/mizu/shared" "github.com/up9inc/mizu/shared"
"github.com/up9inc/mizu/shared/debounce" "github.com/up9inc/mizu/shared/debounce"
"github.com/up9inc/mizu/tap/api" "github.com/up9inc/mizu/tap/api"
yaml "gopkg.in/yaml.v3"
core "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/wait"
) )
const ( const (
@ -36,7 +37,6 @@ type tapState struct {
apiServerService *core.Service apiServerService *core.Service
currentlyTappedPods []core.Pod currentlyTappedPods []core.Pod
mizuServiceAccountExists bool mizuServiceAccountExists bool
doNotRemoveConfigMap bool
} }
var state tapState var state tapState
@ -49,15 +49,8 @@ func RunMizuTap() {
} }
var mizuValidationRules string var mizuValidationRules string
if config.Config.Tap.EnforcePolicyFile != "" || config.Config.Tap.EnforcePolicyFileDeprecated != "" { if config.Config.Tap.EnforcePolicyFile != "" {
var trafficValidation string mizuValidationRules, err = readValidationRules(config.Config.Tap.EnforcePolicyFile)
if config.Config.Tap.EnforcePolicyFile != "" {
trafficValidation = config.Config.Tap.EnforcePolicyFile
} else {
trafficValidation = config.Config.Tap.EnforcePolicyFileDeprecated
}
mizuValidationRules, err = readValidationRules(trafficValidation)
if err != nil { if err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error reading policy file: %v", errormessage.FormatError(err))) logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error reading policy file: %v", errormessage.FormatError(err)))
return return
@ -76,7 +69,7 @@ func RunMizuTap() {
targetNamespaces := getNamespaces(kubernetesProvider) targetNamespaces := getNamespaces(kubernetesProvider)
if config.Config.IsNsRestrictedMode() { if config.Config.IsNsRestrictedMode() {
if len(targetNamespaces) != 1 || !mizu.Contains(targetNamespaces, config.Config.MizuResourcesNamespace) { if len(targetNamespaces) != 1 || !shared.Contains(targetNamespaces, config.Config.MizuResourcesNamespace) {
logger.Log.Errorf("Not supported mode. Mizu can't resolve IPs in other namespaces when running in namespace restricted mode.\n"+ logger.Log.Errorf("Not supported mode. Mizu can't resolve IPs in other namespaces when running in namespace restricted mode.\n"+
"You can use the same namespace for --%s and --%s", configStructs.NamespacesTapName, config.MizuResourcesNamespaceConfigName) "You can use the same namespace for --%s and --%s", configStructs.NamespacesTapName, config.MizuResourcesNamespaceConfigName)
return return
@ -84,7 +77,7 @@ func RunMizuTap() {
} }
var namespacesStr string var namespacesStr string
if !mizu.Contains(targetNamespaces, mizu.K8sAllNamespaces) { if !shared.Contains(targetNamespaces, mizu.K8sAllNamespaces) {
namespacesStr = fmt.Sprintf("namespaces \"%s\"", strings.Join(targetNamespaces, "\", \"")) namespacesStr = fmt.Sprintf("namespaces \"%s\"", strings.Join(targetNamespaces, "\", \""))
} else { } else {
namespacesStr = "all namespaces" namespacesStr = "all namespaces"
@ -99,7 +92,7 @@ func RunMizuTap() {
if len(state.currentlyTappedPods) == 0 { if len(state.currentlyTappedPods) == 0 {
var suggestionStr string var suggestionStr string
if !mizu.Contains(targetNamespaces, mizu.K8sAllNamespaces) { if !shared.Contains(targetNamespaces, mizu.K8sAllNamespaces) {
suggestionStr = ". Select a different namespace with -n or tap all namespaces with -A" suggestionStr = ". Select a different namespace with -n or tap all namespaces with -A"
} }
logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Did not find any pods matching the regex argument%s", suggestionStr)) logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Did not find any pods matching the regex argument%s", suggestionStr))
@ -109,18 +102,17 @@ func RunMizuTap() {
return return
} }
nodeToTappedPodIPMap := getNodeHostToTappedPodIpsMap(state.currentlyTappedPods)
defer finishMizuExecution(kubernetesProvider) defer finishMizuExecution(kubernetesProvider)
if err := createMizuResources(ctx, kubernetesProvider, nodeToTappedPodIPMap, mizuApiFilteringOptions, mizuValidationRules); err != nil { if err := createMizuResources(ctx, kubernetesProvider, mizuApiFilteringOptions, mizuValidationRules); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error creating resources: %v", errormessage.FormatError(err))) logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error creating resources: %v", errormessage.FormatError(err)))
return return
} }
go goUtils.HandleExcWrapper(watchApiServerPod, ctx, kubernetesProvider, cancel) go goUtils.HandleExcWrapper(watchApiServerPod, ctx, kubernetesProvider, cancel, mizuApiFilteringOptions)
go goUtils.HandleExcWrapper(watchTapperPod, ctx, kubernetesProvider, cancel)
go goUtils.HandleExcWrapper(watchPodsForTapping, ctx, kubernetesProvider, targetNamespaces, cancel, mizuApiFilteringOptions) go goUtils.HandleExcWrapper(watchPodsForTapping, ctx, kubernetesProvider, targetNamespaces, cancel, mizuApiFilteringOptions)
//block until exit signal or error // block until exit signal or error
waitForFinish(ctx, cancel) waitForFinish(ctx, cancel)
} }
@ -133,7 +125,7 @@ func readValidationRules(file string) (string, error) {
return string(newContent), nil return string(newContent), nil
} }
func createMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string, mizuApiFilteringOptions *api.TrafficFilteringOptions, mizuValidationRules string) error { func createMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, mizuApiFilteringOptions *api.TrafficFilteringOptions, mizuValidationRules string) error {
if !config.Config.IsNsRestrictedMode() { if !config.Config.IsNsRestrictedMode() {
if err := createMizuNamespace(ctx, kubernetesProvider); err != nil { if err := createMizuNamespace(ctx, kubernetesProvider); err != nil {
return err return err
@ -144,15 +136,8 @@ func createMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Pro
return err return err
} }
if err := updateMizuTappers(ctx, kubernetesProvider, nodeToTappedPodIPMap, mizuApiFilteringOptions); err != nil {
return err
}
if err := createMizuConfigmap(ctx, kubernetesProvider, mizuValidationRules); err != nil { if err := createMizuConfigmap(ctx, kubernetesProvider, mizuValidationRules); err != nil {
logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Failed to create resources required for policy validation. Mizu will not validate policy rules. error: %v\n", errormessage.FormatError(err))) logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Failed to create resources required for policy validation. Mizu will not validate policy rules. error: %v\n", errormessage.FormatError(err)))
state.doNotRemoveConfigMap = true
} else if mizuValidationRules == "" {
state.doNotRemoveConfigMap = true
} }
return nil return nil
@ -224,13 +209,15 @@ func getMizuApiFilteringOptions() (*api.TrafficFilteringOptions, error) {
} }
return &api.TrafficFilteringOptions{ return &api.TrafficFilteringOptions{
PlainTextMaskingRegexes: compiledRegexSlice, PlainTextMaskingRegexes: compiledRegexSlice,
HealthChecksUserAgentHeaders: config.Config.Tap.HealthChecksUserAgentHeaders, IgnoredUserAgents: config.Config.Tap.IgnoredUserAgents,
DisableRedaction: config.Config.Tap.DisableRedaction, DisableRedaction: config.Config.Tap.DisableRedaction,
}, nil }, nil
} }
func updateMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string, mizuApiFilteringOptions *api.TrafficFilteringOptions) error { func updateMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provider, mizuApiFilteringOptions *api.TrafficFilteringOptions) error {
nodeToTappedPodIPMap := getNodeHostToTappedPodIpsMap(state.currentlyTappedPods)
if len(nodeToTappedPodIPMap) > 0 { if len(nodeToTappedPodIPMap) > 0 {
var serviceAccountName string var serviceAccountName string
if state.mizuServiceAccountExists { if state.mizuServiceAccountExists {
@ -268,75 +255,108 @@ func finishMizuExecution(kubernetesProvider *kubernetes.Provider) {
telemetry.ReportAPICalls() telemetry.ReportAPICalls()
removalCtx, cancel := context.WithTimeout(context.Background(), cleanupTimeout) removalCtx, cancel := context.WithTimeout(context.Background(), cleanupTimeout)
defer cancel() defer cancel()
dumpLogsIfNeeded(kubernetesProvider, removalCtx) dumpLogsIfNeeded(removalCtx, kubernetesProvider)
cleanUpMizuResources(kubernetesProvider, removalCtx, cancel) cleanUpMizuResources(removalCtx, cancel, kubernetesProvider)
} }
func dumpLogsIfNeeded(kubernetesProvider *kubernetes.Provider, removalCtx context.Context) { func dumpLogsIfNeeded(ctx context.Context, kubernetesProvider *kubernetes.Provider) {
if !config.Config.DumpLogs { if !config.Config.DumpLogs {
return return
} }
mizuDir := mizu.GetMizuFolderPath() mizuDir := mizu.GetMizuFolderPath()
filePath := path.Join(mizuDir, fmt.Sprintf("mizu_logs_%s.zip", time.Now().Format("2006_01_02__15_04_05"))) filePath := path.Join(mizuDir, fmt.Sprintf("mizu_logs_%s.zip", time.Now().Format("2006_01_02__15_04_05")))
if err := fsUtils.DumpLogs(kubernetesProvider, removalCtx, filePath); err != nil { if err := fsUtils.DumpLogs(ctx, kubernetesProvider, filePath); err != nil {
logger.Log.Errorf("Failed dump logs %v", err) logger.Log.Errorf("Failed dump logs %v", err)
} }
} }
func cleanUpMizuResources(kubernetesProvider *kubernetes.Provider, removalCtx context.Context, cancel context.CancelFunc) { func cleanUpMizuResources(ctx context.Context, cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider) {
logger.Log.Infof("\nRemoving mizu resources\n") logger.Log.Infof("\nRemoving mizu resources\n")
if !config.Config.IsNsRestrictedMode() { var leftoverResources []string
if err := kubernetesProvider.RemoveNamespace(removalCtx, config.Config.MizuResourcesNamespace); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Namespace %s: %v", config.Config.MizuResourcesNamespace, errormessage.FormatError(err))) if config.Config.IsNsRestrictedMode() {
return leftoverResources = cleanUpRestrictedMode(ctx, kubernetesProvider)
}
} else { } else {
if err := kubernetesProvider.RemovePod(removalCtx, config.Config.MizuResourcesNamespace, mizu.ApiServerPodName); err != nil { leftoverResources = cleanUpNonRestrictedMode(ctx, cancel, kubernetesProvider)
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Pod %s in namespace %s: %v", mizu.ApiServerPodName, config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
}
if err := kubernetesProvider.RemoveService(removalCtx, config.Config.MizuResourcesNamespace, mizu.ApiServerPodName); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Service %s in namespace %s: %v", mizu.ApiServerPodName, config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
}
if err := kubernetesProvider.RemoveDaemonSet(removalCtx, config.Config.MizuResourcesNamespace, mizu.TapperDaemonSetName); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing DaemonSet %s in namespace %s: %v", mizu.TapperDaemonSetName, config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
}
if !state.doNotRemoveConfigMap {
if err := kubernetesProvider.RemoveConfigMap(removalCtx, config.Config.MizuResourcesNamespace, mizu.ConfigMapName); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing ConfigMap %s in namespace %s: %v", mizu.ConfigMapName, config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
}
}
} }
if state.mizuServiceAccountExists { if len(leftoverResources) > 0 {
if !config.Config.IsNsRestrictedMode() { errMsg := fmt.Sprintf("Failed to remove the following resources, for more info check logs at %s:", logger.GetLogFilePath())
if err := kubernetesProvider.RemoveNonNamespacedResources(removalCtx, mizu.ClusterRoleName, mizu.ClusterRoleBindingName); err != nil { for _, resource := range leftoverResources {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing non-namespaced resources: %v", errormessage.FormatError(err))) errMsg += "\n- " + resource
return
}
} else {
if err := kubernetesProvider.RemoveServicAccount(removalCtx, config.Config.MizuResourcesNamespace, mizu.ServiceAccountName); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Service Account %s in namespace %s: %v", mizu.ServiceAccountName, config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
return
}
if err := kubernetesProvider.RemoveRole(removalCtx, config.Config.MizuResourcesNamespace, mizu.RoleName); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing Role %s in namespace %s: %v", mizu.RoleName, config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
}
if err := kubernetesProvider.RemoveRoleBinding(removalCtx, config.Config.MizuResourcesNamespace, mizu.RoleBindingName); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error removing RoleBinding %s in namespace %s: %v", mizu.RoleBindingName, config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
}
} }
logger.Log.Errorf(uiUtils.Error, errMsg)
}
}
func cleanUpRestrictedMode(ctx context.Context, kubernetesProvider *kubernetes.Provider) []string {
leftoverResources := make([]string, 0)
if err := kubernetesProvider.RemovePod(ctx, config.Config.MizuResourcesNamespace, mizu.ApiServerPodName); err != nil {
resourceDesc := fmt.Sprintf("Pod %s in namespace %s", mizu.ApiServerPodName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
} }
if !config.Config.IsNsRestrictedMode() { if err := kubernetesProvider.RemoveService(ctx, config.Config.MizuResourcesNamespace, mizu.ApiServerPodName); err != nil {
waitUntilNamespaceDeleted(removalCtx, cancel, kubernetesProvider) resourceDesc := fmt.Sprintf("Service %s in namespace %s", mizu.ApiServerPodName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
} }
if err := kubernetesProvider.RemoveDaemonSet(ctx, config.Config.MizuResourcesNamespace, mizu.TapperDaemonSetName); err != nil {
resourceDesc := fmt.Sprintf("DaemonSet %s in namespace %s", mizu.TapperDaemonSetName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveConfigMap(ctx, config.Config.MizuResourcesNamespace, mizu.ConfigMapName); err != nil {
resourceDesc := fmt.Sprintf("ConfigMap %s in namespace %s", mizu.ConfigMapName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveServicAccount(ctx, config.Config.MizuResourcesNamespace, mizu.ServiceAccountName); err != nil {
resourceDesc := fmt.Sprintf("Service Account %s in namespace %s", mizu.ServiceAccountName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveRole(ctx, config.Config.MizuResourcesNamespace, mizu.RoleName); err != nil {
resourceDesc := fmt.Sprintf("Role %s in namespace %s", mizu.RoleName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveRoleBinding(ctx, config.Config.MizuResourcesNamespace, mizu.RoleBindingName); err != nil {
resourceDesc := fmt.Sprintf("RoleBinding %s in namespace %s", mizu.RoleBindingName, config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
return leftoverResources
}
func cleanUpNonRestrictedMode(ctx context.Context, cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider) []string {
leftoverResources := make([]string, 0)
if err := kubernetesProvider.RemoveNamespace(ctx, config.Config.MizuResourcesNamespace); err != nil {
resourceDesc := fmt.Sprintf("Namespace %s", config.Config.MizuResourcesNamespace)
handleDeletionError(err, resourceDesc, &leftoverResources)
} else {
defer waitUntilNamespaceDeleted(ctx, cancel, kubernetesProvider)
}
if err := kubernetesProvider.RemoveClusterRole(ctx, mizu.ClusterRoleName); err != nil {
resourceDesc := fmt.Sprintf("ClusterRole %s", mizu.ClusterRoleName)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
if err := kubernetesProvider.RemoveClusterRoleBinding(ctx, mizu.ClusterRoleBindingName); err != nil {
resourceDesc := fmt.Sprintf("ClusterRoleBinding %s", mizu.ClusterRoleBindingName)
handleDeletionError(err, resourceDesc, &leftoverResources)
}
return leftoverResources
}
func handleDeletionError(err error, resourceDesc string, leftoverResources *[]string) {
logger.Log.Debugf("Error removing %s: %v", resourceDesc, errormessage.FormatError(err))
*leftoverResources = append(*leftoverResources, resourceDesc)
} }
func waitUntilNamespaceDeleted(ctx context.Context, cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider) { func waitUntilNamespaceDeleted(ctx context.Context, cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider) {
@ -376,13 +396,8 @@ func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Pro
logger.Log.Debugf("[Error] failed update tapped pods %v", err) logger.Log.Debugf("[Error] failed update tapped pods %v", err)
} }
nodeToTappedPodIPMap := getNodeHostToTappedPodIpsMap(state.currentlyTappedPods) if err := updateMizuTappers(ctx, kubernetesProvider, mizuApiFilteringOptions); err != nil {
if err != nil { logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error updating tappers: %v", errormessage.FormatError(err)))
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error building node to ips map: %v", errormessage.FormatError(err)))
cancel()
}
if err := updateMizuTappers(ctx, kubernetesProvider, nodeToTappedPodIPMap, mizuApiFilteringOptions); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error updating daemonset: %v", errormessage.FormatError(err)))
cancel() cancel()
} }
} }
@ -500,7 +515,7 @@ func getMissingPods(pods1 []core.Pod, pods2 []core.Pod) []core.Pod {
return missingPods return missingPods
} }
func watchApiServerPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) { func watchApiServerPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc, mizuApiFilteringOptions *api.TrafficFilteringOptions) {
podExactRegex := regexp.MustCompile(fmt.Sprintf("^%s$", mizu.ApiServerPodName)) podExactRegex := regexp.MustCompile(fmt.Sprintf("^%s$", mizu.ApiServerPodName))
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider, []string{config.Config.MizuResourcesNamespace}, podExactRegex) added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider, []string{config.Config.MizuResourcesNamespace}, podExactRegex)
isPodReady := false isPodReady := false
@ -530,16 +545,38 @@ func watchApiServerPod(ctx context.Context, kubernetesProvider *kubernetes.Provi
} }
logger.Log.Debugf("Watching API Server pod loop, modified: %v", modifiedPod.Status.Phase) logger.Log.Debugf("Watching API Server pod loop, modified: %v", modifiedPod.Status.Phase)
if modifiedPod.Status.Phase == core.PodPending {
if modifiedPod.Status.Conditions[0].Type == core.PodScheduled && modifiedPod.Status.Conditions[0].Status != core.ConditionTrue {
logger.Log.Debugf("Wasn't able to deploy the API server. Reason: \"%s\"", modifiedPod.Status.Conditions[0].Message)
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Wasn't able to deploy the API server, for more info check logs at %s", logger.GetLogFilePath()))
cancel()
break
}
if len(modifiedPod.Status.ContainerStatuses) > 0 && modifiedPod.Status.ContainerStatuses[0].State.Waiting != nil && modifiedPod.Status.ContainerStatuses[0].State.Waiting.Reason == "ErrImagePull" {
logger.Log.Debugf("Wasn't able to deploy the API server. (ErrImagePull) Reason: \"%s\"", modifiedPod.Status.ContainerStatuses[0].State.Waiting.Message)
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Wasn't able to deploy the API server: failed to pull the image, for more info check logs at %v", logger.GetLogFilePath()))
cancel()
break
}
}
if modifiedPod.Status.Phase == core.PodRunning && !isPodReady { if modifiedPod.Status.Phase == core.PodRunning && !isPodReady {
isPodReady = true isPodReady = true
go startProxyReportErrorIfAny(kubernetesProvider, cancel) go startProxyReportErrorIfAny(kubernetesProvider, cancel)
url := GetApiServerUrl() url := GetApiServerUrl()
if err := apiserver.Provider.InitAndTestConnection(url); err != nil { if err := apiserver.Provider.InitAndTestConnection(url); err != nil {
logger.Log.Errorf(uiUtils.Error, "Couldn't connect to API server, check logs") logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Couldn't connect to API server, for more info check logs at %s", logger.GetLogFilePath()))
cancel() cancel()
break break
} }
if err := updateMizuTappers(ctx, kubernetesProvider, mizuApiFilteringOptions); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error updating tappers: %v", errormessage.FormatError(err)))
cancel()
}
logger.Log.Infof("Mizu is available at %s\n", url) logger.Log.Infof("Mizu is available at %s\n", url)
openBrowser(url) openBrowser(url)
requestForAnalysisIfNeeded() requestForAnalysisIfNeeded()
@ -547,13 +584,13 @@ func watchApiServerPod(ctx context.Context, kubernetesProvider *kubernetes.Provi
logger.Log.Debugf("[Error] failed update tapped pods %v", err) logger.Log.Debugf("[Error] failed update tapped pods %v", err)
} }
} }
case _, ok := <-errorChan: case err, ok := <-errorChan:
if !ok { if !ok {
errorChan = nil errorChan = nil
continue continue
} }
logger.Log.Debugf("[ERROR] Agent creation, watching %v namespace", config.Config.MizuResourcesNamespace) logger.Log.Debugf("[ERROR] Agent creation, watching %v namespace, error: %v", config.Config.MizuResourcesNamespace, err)
cancel() cancel()
case <-timeAfter: case <-timeAfter:
@ -568,6 +605,72 @@ func watchApiServerPod(ctx context.Context, kubernetesProvider *kubernetes.Provi
} }
} }
func watchTapperPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
podExactRegex := regexp.MustCompile(fmt.Sprintf("^%s.*", mizu.TapperDaemonSetName))
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider, []string{config.Config.MizuResourcesNamespace}, podExactRegex)
var prevPodPhase core.PodPhase
for {
select {
case addedPod, ok := <-added:
if !ok {
added = nil
continue
}
logger.Log.Debugf("Tapper is created [%s]", addedPod.Name)
case removedPod, ok := <-removed:
if !ok {
removed = nil
continue
}
logger.Log.Debugf("Tapper is removed [%s]", removedPod.Name)
case modifiedPod, ok := <-modified:
if !ok {
modified = nil
continue
}
if modifiedPod.Status.Phase == core.PodPending && modifiedPod.Status.Conditions[0].Type == core.PodScheduled && modifiedPod.Status.Conditions[0].Status != core.ConditionTrue {
logger.Log.Infof(uiUtils.Red, "Wasn't able to deploy the tapper %s. Reason: \"%s\"", modifiedPod.Name, modifiedPod.Status.Conditions[0].Message)
cancel()
break
}
podStatus := modifiedPod.Status
if podStatus.Phase == core.PodPending && prevPodPhase == podStatus.Phase {
logger.Log.Debugf("Tapper %s is %s", modifiedPod.Name, strings.ToLower(string(podStatus.Phase)))
continue
}
prevPodPhase = podStatus.Phase
if podStatus.Phase == core.PodRunning {
state := podStatus.ContainerStatuses[0].State
if state.Terminated != nil {
switch state.Terminated.Reason {
case "OOMKilled":
logger.Log.Infof(uiUtils.Red, "Tapper %s was terminated (reason: OOMKilled). You should consider increasing machine resources.", modifiedPod.Name)
}
}
}
logger.Log.Debugf("Tapper %s is %s", modifiedPod.Name, strings.ToLower(string(podStatus.Phase)))
case err, ok := <-errorChan:
if !ok {
errorChan = nil
continue
}
logger.Log.Debugf("[Error] Error in mizu tapper watch, err: %v", err)
cancel()
case <-ctx.Done():
logger.Log.Debugf("Watching tapper pod loop, ctx done")
return
}
}
}
func requestForAnalysisIfNeeded() { func requestForAnalysisIfNeeded() {
if !config.Config.Tap.Analysis { if !config.Config.Tap.Analysis {
return return
@ -609,7 +712,7 @@ func getNamespaces(kubernetesProvider *kubernetes.Provider) []string {
if config.Config.Tap.AllNamespaces { if config.Config.Tap.AllNamespaces {
return []string{mizu.K8sAllNamespaces} return []string{mizu.K8sAllNamespaces}
} else if len(config.Config.Tap.Namespaces) > 0 { } else if len(config.Config.Tap.Namespaces) > 0 {
return mizu.Unique(config.Config.Tap.Namespaces) return shared.Unique(config.Config.Tap.Namespaces)
} else { } else {
return []string{kubernetesProvider.CurrentNamespace()} return []string{kubernetesProvider.CurrentNamespace()}
} }

View File

@ -25,4 +25,7 @@ func init() {
defaults.Set(&defaultViewConfig) defaults.Set(&defaultViewConfig)
viewCmd.Flags().Uint16P(configStructs.GuiPortViewName, "p", defaultViewConfig.GuiPort, "Provide a custom port for the web interface webserver") viewCmd.Flags().Uint16P(configStructs.GuiPortViewName, "p", defaultViewConfig.GuiPort, "Provide a custom port for the web interface webserver")
viewCmd.Flags().StringP(configStructs.UrlViewName, "u", defaultViewConfig.Url, "Provide a custom host")
viewCmd.Flags().MarkHidden(configStructs.UrlViewName)
} }

View File

@ -24,34 +24,39 @@ func runMizuView() {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
exists, err := kubernetesProvider.DoesServicesExist(ctx, config.Config.MizuResourcesNamespace, mizu.ApiServerPodName) url := config.Config.View.Url
if err != nil {
logger.Log.Errorf("Failed to found mizu service %v", err)
cancel()
return
}
if !exists {
logger.Log.Infof("%s service not found, you should run `mizu tap` command first", mizu.ApiServerPodName)
cancel()
return
}
url := GetApiServerUrl() if url == "" {
exists, err := kubernetesProvider.DoesServicesExist(ctx, config.Config.MizuResourcesNamespace, mizu.ApiServerPodName)
if err != nil {
logger.Log.Errorf("Failed to found mizu service %v", err)
cancel()
return
}
if !exists {
logger.Log.Infof("%s service not found, you should run `mizu tap` command first", mizu.ApiServerPodName)
cancel()
return
}
response, err := http.Get(fmt.Sprintf("%s/", url)) url = GetApiServerUrl()
if err == nil && response.StatusCode == 200 {
logger.Log.Infof("Found a running service %s and open port %d", mizu.ApiServerPodName, config.Config.View.GuiPort)
return
}
logger.Log.Infof("Establishing connection to k8s cluster...")
go startProxyReportErrorIfAny(kubernetesProvider, cancel)
if err := apiserver.Provider.InitAndTestConnection(GetApiServerUrl()); err != nil { response, err := http.Get(fmt.Sprintf("%s/", url))
logger.Log.Errorf(uiUtils.Error, "Couldn't connect to API server, check logs") if err == nil && response.StatusCode == 200 {
return logger.Log.Infof("Found a running service %s and open port %d", mizu.ApiServerPodName, config.Config.View.GuiPort)
return
}
logger.Log.Infof("Establishing connection to k8s cluster...")
go startProxyReportErrorIfAny(kubernetesProvider, cancel)
if err := apiserver.Provider.InitAndTestConnection(GetApiServerUrl()); err != nil {
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Couldn't connect to API server, for more info check logs at %s", logger.GetLogFilePath()))
return
}
} }
logger.Log.Infof("Mizu is available at %s\n", url) logger.Log.Infof("Mizu is available at %s\n", url)
openBrowser(url) openBrowser(url)
if isCompatible, err := version.CheckVersionCompatibility(); err != nil { if isCompatible, err := version.CheckVersionCompatibility(); err != nil {
logger.Log.Errorf("Failed to check versions compatibility %v", err) logger.Log.Errorf("Failed to check versions compatibility %v", err)

View File

@ -4,7 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/up9inc/mizu/cli/logger" "github.com/up9inc/mizu/cli/logger"
"github.com/up9inc/mizu/cli/mizu" "github.com/up9inc/mizu/shared"
"io/ioutil" "io/ioutil"
"os" "os"
"reflect" "reflect"
@ -89,7 +89,7 @@ func initFlag(f *pflag.Flag) {
configElemValue := reflect.ValueOf(&Config).Elem() configElemValue := reflect.ValueOf(&Config).Elem()
var flagPath []string var flagPath []string
if mizu.Contains([]string{ConfigFilePathCommandName}, f.Name) { if shared.Contains([]string{ConfigFilePathCommandName}, f.Name) {
flagPath = []string{f.Name} flagPath = []string{f.Name}
} else { } else {
flagPath = []string{cmdName, f.Name} flagPath = []string{cmdName, f.Name}

View File

@ -17,26 +17,24 @@ const (
HumanMaxEntriesDBSizeTapName = "max-entries-db-size" HumanMaxEntriesDBSizeTapName = "max-entries-db-size"
DryRunTapName = "dry-run" DryRunTapName = "dry-run"
EnforcePolicyFile = "traffic-validation-file" EnforcePolicyFile = "traffic-validation-file"
EnforcePolicyFileDeprecated = "test-rules"
) )
type TapConfig struct { type TapConfig struct {
AnalysisDestination string `yaml:"dest" default:"up9.app"` AnalysisDestination string `yaml:"dest" default:"up9.app"`
SleepIntervalSec int `yaml:"upload-interval" default:"10"` SleepIntervalSec int `yaml:"upload-interval" default:"10"`
PodRegexStr string `yaml:"regex" default:".*"` PodRegexStr string `yaml:"regex" default:".*"`
GuiPort uint16 `yaml:"gui-port" default:"8899"` GuiPort uint16 `yaml:"gui-port" default:"8899"`
Namespaces []string `yaml:"namespaces"` Namespaces []string `yaml:"namespaces"`
Analysis bool `yaml:"analysis" default:"false"` Analysis bool `yaml:"analysis" default:"false"`
AllNamespaces bool `yaml:"all-namespaces" default:"false"` AllNamespaces bool `yaml:"all-namespaces" default:"false"`
PlainTextFilterRegexes []string `yaml:"regex-masking"` PlainTextFilterRegexes []string `yaml:"regex-masking"`
HealthChecksUserAgentHeaders []string `yaml:"ignored-user-agents"` IgnoredUserAgents []string `yaml:"ignored-user-agents"`
DisableRedaction bool `yaml:"no-redact" default:"false"` DisableRedaction bool `yaml:"no-redact" default:"false"`
HumanMaxEntriesDBSize string `yaml:"max-entries-db-size" default:"200MB"` HumanMaxEntriesDBSize string `yaml:"max-entries-db-size" default:"200MB"`
DryRun bool `yaml:"dry-run" default:"false"` DryRun bool `yaml:"dry-run" default:"false"`
EnforcePolicyFile string `yaml:"traffic-validation-file"` EnforcePolicyFile string `yaml:"traffic-validation-file"`
EnforcePolicyFileDeprecated string `yaml:"test-rules"` ApiServerResources Resources `yaml:"api-server-resources"`
ApiServerResources Resources `yaml:"api-server-resources"` TapperResources Resources `yaml:"tapper-resources"`
TapperResources Resources `yaml:"tapper-resources"`
} }
type Resources struct { type Resources struct {

View File

@ -2,8 +2,10 @@ package configStructs
const ( const (
GuiPortViewName = "gui-port" GuiPortViewName = "gui-port"
UrlViewName = "url"
) )
type ViewConfig struct { type ViewConfig struct {
GuiPort uint16 `yaml:"gui-port" default:"8899"` GuiPort uint16 `yaml:"gui-port" default:"8899"`
Url string `yaml:"url,omitempty" readonly:""`
} }

View File

@ -268,67 +268,21 @@ func (provider *Provider) CreateService(ctx context.Context, namespace string, s
return provider.clientSet.CoreV1().Services(namespace).Create(ctx, &service, metav1.CreateOptions{}) return provider.clientSet.CoreV1().Services(namespace).Create(ctx, &service, metav1.CreateOptions{})
} }
func (provider *Provider) DoesServiceAccountExist(ctx context.Context, namespace string, serviceAccountName string) (bool, error) {
serviceAccount, err := provider.clientSet.CoreV1().ServiceAccounts(namespace).Get(ctx, serviceAccountName, metav1.GetOptions{})
return provider.doesResourceExist(serviceAccount, err)
}
func (provider *Provider) DoesConfigMapExist(ctx context.Context, namespace string, name string) (bool, error) {
resource, err := provider.clientSet.CoreV1().ConfigMaps(namespace).Get(ctx, name, metav1.GetOptions{})
return provider.doesResourceExist(resource, err)
}
func (provider *Provider) DoesServicesExist(ctx context.Context, namespace string, name string) (bool, error) { func (provider *Provider) DoesServicesExist(ctx context.Context, namespace string, name string) (bool, error) {
resource, err := provider.clientSet.CoreV1().Services(namespace).Get(ctx, name, metav1.GetOptions{}) resource, err := provider.clientSet.CoreV1().Services(namespace).Get(ctx, name, metav1.GetOptions{})
return provider.doesResourceExist(resource, err) return provider.doesResourceExist(resource, err)
} }
func (provider *Provider) DoesNamespaceExist(ctx context.Context, name string) (bool, error) {
resource, err := provider.clientSet.CoreV1().Namespaces().Get(ctx, name, metav1.GetOptions{})
return provider.doesResourceExist(resource, err)
}
func (provider *Provider) DoesClusterRoleExist(ctx context.Context, name string) (bool, error) {
resource, err := provider.clientSet.RbacV1().ClusterRoles().Get(ctx, name, metav1.GetOptions{})
return provider.doesResourceExist(resource, err)
}
func (provider *Provider) DoesClusterRoleBindingExist(ctx context.Context, name string) (bool, error) {
resource, err := provider.clientSet.RbacV1().ClusterRoleBindings().Get(ctx, name, metav1.GetOptions{})
return provider.doesResourceExist(resource, err)
}
func (provider *Provider) DoesRoleExist(ctx context.Context, namespace string, name string) (bool, error) {
resource, err := provider.clientSet.RbacV1().Roles(namespace).Get(ctx, name, metav1.GetOptions{})
return provider.doesResourceExist(resource, err)
}
func (provider *Provider) DoesRoleBindingExist(ctx context.Context, namespace string, name string) (bool, error) {
resource, err := provider.clientSet.RbacV1().RoleBindings(namespace).Get(ctx, name, metav1.GetOptions{})
return provider.doesResourceExist(resource, err)
}
func (provider *Provider) DoesPodExist(ctx context.Context, namespace string, name string) (bool, error) {
resource, err := provider.clientSet.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
return provider.doesResourceExist(resource, err)
}
func (provider *Provider) DoesDaemonSetExist(ctx context.Context, namespace string, name string) (bool, error) {
resource, err := provider.clientSet.AppsV1().DaemonSets(namespace).Get(ctx, name, metav1.GetOptions{})
return provider.doesResourceExist(resource, err)
}
func (provider *Provider) doesResourceExist(resource interface{}, err error) (bool, error) { func (provider *Provider) doesResourceExist(resource interface{}, err error) (bool, error) {
var statusError *k8serrors.StatusError // Getting NotFound error is the expected behavior when a resource does not exist.
if errors.As(err, &statusError) { if k8serrors.IsNotFound(err) {
// expected behavior when resource does not exist return false, nil
if statusError.ErrStatus.Reason == metav1.StatusReasonNotFound {
return false, nil
}
} }
if err != nil { if err != nil {
return false, err return false, err
} }
return resource != nil, nil return resource != nil, nil
} }
@ -441,115 +395,63 @@ func (provider *Provider) CreateMizuRBACNamespaceRestricted(ctx context.Context,
} }
func (provider *Provider) RemoveNamespace(ctx context.Context, name string) error { func (provider *Provider) RemoveNamespace(ctx context.Context, name string) error {
if isFound, err := provider.DoesNamespaceExist(ctx, name); err != nil { err := provider.clientSet.CoreV1().Namespaces().Delete(ctx, name, metav1.DeleteOptions{})
return err return provider.handleRemovalError(err)
} else if !isFound {
return nil
}
return provider.clientSet.CoreV1().Namespaces().Delete(ctx, name, metav1.DeleteOptions{})
}
func (provider *Provider) RemoveNonNamespacedResources(ctx context.Context, clusterRoleName string, clusterRoleBindingName string) error {
if err := provider.RemoveClusterRole(ctx, clusterRoleName); err != nil {
return err
}
if err := provider.RemoveClusterRoleBinding(ctx, clusterRoleBindingName); err != nil {
return err
}
return nil
} }
func (provider *Provider) RemoveClusterRole(ctx context.Context, name string) error { func (provider *Provider) RemoveClusterRole(ctx context.Context, name string) error {
if isFound, err := provider.DoesClusterRoleExist(ctx, name); err != nil { err := provider.clientSet.RbacV1().ClusterRoles().Delete(ctx, name, metav1.DeleteOptions{})
return err return provider.handleRemovalError(err)
} else if !isFound {
return nil
}
return provider.clientSet.RbacV1().ClusterRoles().Delete(ctx, name, metav1.DeleteOptions{})
} }
func (provider *Provider) RemoveClusterRoleBinding(ctx context.Context, name string) error { func (provider *Provider) RemoveClusterRoleBinding(ctx context.Context, name string) error {
if isFound, err := provider.DoesClusterRoleBindingExist(ctx, name); err != nil { err := provider.clientSet.RbacV1().ClusterRoleBindings().Delete(ctx, name, metav1.DeleteOptions{})
return err return provider.handleRemovalError(err)
} else if !isFound {
return nil
}
return provider.clientSet.RbacV1().ClusterRoleBindings().Delete(ctx, name, metav1.DeleteOptions{})
} }
func (provider *Provider) RemoveRoleBinding(ctx context.Context, namespace string, name string) error { func (provider *Provider) RemoveRoleBinding(ctx context.Context, namespace string, name string) error {
if isFound, err := provider.DoesRoleBindingExist(ctx, namespace, name); err != nil { err := provider.clientSet.RbacV1().RoleBindings(namespace).Delete(ctx, name, metav1.DeleteOptions{})
return err return provider.handleRemovalError(err)
} else if !isFound {
return nil
}
return provider.clientSet.RbacV1().RoleBindings(namespace).Delete(ctx, name, metav1.DeleteOptions{})
} }
func (provider *Provider) RemoveRole(ctx context.Context, namespace string, name string) error { func (provider *Provider) RemoveRole(ctx context.Context, namespace string, name string) error {
if isFound, err := provider.DoesRoleExist(ctx, namespace, name); err != nil { err := provider.clientSet.RbacV1().Roles(namespace).Delete(ctx, name, metav1.DeleteOptions{})
return err return provider.handleRemovalError(err)
} else if !isFound {
return nil
}
return provider.clientSet.RbacV1().Roles(namespace).Delete(ctx, name, metav1.DeleteOptions{})
} }
func (provider *Provider) RemoveServicAccount(ctx context.Context, namespace string, name string) error { func (provider *Provider) RemoveServicAccount(ctx context.Context, namespace string, name string) error {
if isFound, err := provider.DoesServiceAccountExist(ctx, namespace, name); err != nil { err := provider.clientSet.CoreV1().ServiceAccounts(namespace).Delete(ctx, name, metav1.DeleteOptions{})
return err return provider.handleRemovalError(err)
} else if !isFound {
return nil
}
return provider.clientSet.CoreV1().ServiceAccounts(namespace).Delete(ctx, name, metav1.DeleteOptions{})
} }
func (provider *Provider) RemovePod(ctx context.Context, namespace string, podName string) error { func (provider *Provider) RemovePod(ctx context.Context, namespace string, podName string) error {
if isFound, err := provider.DoesPodExist(ctx, namespace, podName); err != nil { err := provider.clientSet.CoreV1().Pods(namespace).Delete(ctx, podName, metav1.DeleteOptions{})
return err return provider.handleRemovalError(err)
} else if !isFound {
return nil
}
return provider.clientSet.CoreV1().Pods(namespace).Delete(ctx, podName, metav1.DeleteOptions{})
} }
func (provider *Provider) RemoveConfigMap(ctx context.Context, namespace string, configMapName string) error { func (provider *Provider) RemoveConfigMap(ctx context.Context, namespace string, configMapName string) error {
if isFound, err := provider.DoesConfigMapExist(ctx, namespace, configMapName); err != nil { err := provider.clientSet.CoreV1().ConfigMaps(namespace).Delete(ctx, configMapName, metav1.DeleteOptions{})
return err return provider.handleRemovalError(err)
} else if !isFound {
return nil
}
return provider.clientSet.CoreV1().ConfigMaps(namespace).Delete(ctx, configMapName, metav1.DeleteOptions{})
} }
func (provider *Provider) RemoveService(ctx context.Context, namespace string, serviceName string) error { func (provider *Provider) RemoveService(ctx context.Context, namespace string, serviceName string) error {
if isFound, err := provider.DoesServicesExist(ctx, namespace, serviceName); err != nil { err := provider.clientSet.CoreV1().Services(namespace).Delete(ctx, serviceName, metav1.DeleteOptions{})
return err return provider.handleRemovalError(err)
} else if !isFound {
return nil
}
return provider.clientSet.CoreV1().Services(namespace).Delete(ctx, serviceName, metav1.DeleteOptions{})
} }
func (provider *Provider) RemoveDaemonSet(ctx context.Context, namespace string, daemonSetName string) error { func (provider *Provider) RemoveDaemonSet(ctx context.Context, namespace string, daemonSetName string) error {
if isFound, err := provider.DoesDaemonSetExist(ctx, namespace, daemonSetName); err != nil { err := provider.clientSet.AppsV1().DaemonSets(namespace).Delete(ctx, daemonSetName, metav1.DeleteOptions{})
return err return provider.handleRemovalError(err)
} else if !isFound { }
func (provider *Provider) handleRemovalError(err error) error {
// Ignore NotFound - There is nothing to delete.
// Ignore Forbidden - Assume that a user could not have created the resource in the first place.
if k8serrors.IsNotFound(err) || k8serrors.IsForbidden(err) {
return nil return nil
} }
return provider.clientSet.AppsV1().DaemonSets(namespace).Delete(ctx, daemonSetName, metav1.DeleteOptions{}) return err
} }
func (provider *Provider) CreateConfigMap(ctx context.Context, namespace string, configMapName string, data string) error { func (provider *Provider) CreateConfigMap(ctx context.Context, namespace string, configMapName string, data string) error {
@ -731,7 +633,7 @@ func (provider *Provider) ListAllRunningPodsMatchingRegex(ctx context.Context, r
return matchingPods, nil return matchingPods, nil
} }
func (provider *Provider) GetPodLogs(namespace string, podName string, ctx context.Context) (string, error) { func (provider *Provider) GetPodLogs(ctx context.Context, namespace string, podName string) (string, error) {
podLogOpts := core.PodLogOptions{} podLogOpts := core.PodLogOptions{}
req := provider.clientSet.CoreV1().Pods(namespace).GetLogs(podName, &podLogOpts) req := provider.clientSet.CoreV1().Pods(namespace).GetLogs(podName, &podLogOpts)
podLogs, err := req.Stream(ctx) podLogs, err := req.Stream(ctx)
@ -747,7 +649,7 @@ func (provider *Provider) GetPodLogs(namespace string, podName string, ctx conte
return str, nil return str, nil
} }
func (provider *Provider) GetNamespaceEvents(namespace string, ctx context.Context) (string, error) { func (provider *Provider) GetNamespaceEvents(ctx context.Context, namespace string) (string, error) {
eventsOpts := metav1.ListOptions{} eventsOpts := metav1.ListOptions{}
eventList, err := provider.clientSet.CoreV1().Events(namespace).List(ctx, eventsOpts) eventList, err := provider.clientSet.CoreV1().Events(namespace).List(ctx, eventsOpts)
if err != nil { if err != nil {

View File

@ -33,7 +33,10 @@ func FilteredWatch(ctx context.Context, kubernetesProvider *Provider, targetName
return return
} }
pod := e.Object.(*corev1.Pod) pod, ok := e.Object.(*corev1.Pod)
if !ok {
continue
}
if !podFilter.MatchString(pod.Name) { if !podFilter.MatchString(pod.Name) {
continue continue

View File

@ -12,7 +12,7 @@ import (
"regexp" "regexp"
) )
func DumpLogs(provider *kubernetes.Provider, ctx context.Context, filePath string) error { func DumpLogs(ctx context.Context, provider *kubernetes.Provider, filePath string) error {
podExactRegex := regexp.MustCompile("^" + mizu.MizuResourcesPrefix) podExactRegex := regexp.MustCompile("^" + mizu.MizuResourcesPrefix)
pods, err := provider.ListAllPodsMatchingRegex(ctx, podExactRegex, []string{config.Config.MizuResourcesNamespace}) pods, err := provider.ListAllPodsMatchingRegex(ctx, podExactRegex, []string{config.Config.MizuResourcesNamespace})
if err != nil { if err != nil {
@ -32,7 +32,7 @@ func DumpLogs(provider *kubernetes.Provider, ctx context.Context, filePath strin
defer zipWriter.Close() defer zipWriter.Close()
for _, pod := range pods { for _, pod := range pods {
logs, err := provider.GetPodLogs(pod.Namespace, pod.Name, ctx) logs, err := provider.GetPodLogs(ctx, pod.Namespace, pod.Name)
if err != nil { if err != nil {
logger.Log.Errorf("Failed to get logs, %v", err) logger.Log.Errorf("Failed to get logs, %v", err)
continue continue
@ -47,7 +47,7 @@ func DumpLogs(provider *kubernetes.Provider, ctx context.Context, filePath strin
} }
} }
events, err := provider.GetNamespaceEvents(config.Config.MizuResourcesNamespace, ctx) events, err := provider.GetNamespaceEvents(ctx, config.Config.MizuResourcesNamespace)
if err != nil { if err != nil {
logger.Log.Debugf("Failed to get k8b events, %v", err) logger.Log.Debugf("Failed to get k8b events, %v", err)
} else { } else {

View File

@ -31,12 +31,12 @@ mizu tap --traffic-validation-file rules.yaml
The structure of the traffic-validation-file is: The structure of the traffic-validation-file is:
* `name`: string, name of the rule * `name`: string, name of the rule
* `type`: string, type of the rule, must be `json` or `header` or `latency` * `type`: string, type of the rule, must be `json` or `header` or `slo`
* `key`: string, [jsonpath](https://code.google.com/archive/p/jsonpath/wikis/Javascript.wiki) used only in `json` or `header` type * `key`: string, [jsonpath](https://code.google.com/archive/p/jsonpath/wikis/Javascript.wiki) used only in `json` or `header` type
* `value`: string, [regex](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) used only in `json` or `header` type * `value`: string, [regex](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) used only in `json` or `header` type
* `service`: string, [regex](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) service name to filter * `service`: string, [regex](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) service name to filter
* `path`: string, [regex](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) URL path to filter * `path`: string, [regex](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) URL path to filter
* `latency`: integer, time in ms of the expected latency. * `response-time`: integer, time in ms of the expected latency.
### For example: ### For example:
@ -54,8 +54,8 @@ rules:
key: "Content-Le.*" key: "Content-Le.*"
value: "(\\d+(?:\\.\\d+)?)" value: "(\\d+(?:\\.\\d+)?)"
- name: latency-test - name: latency-test
type: latency type: slo
latency: 1 response-time: 1
service: "carts.*" service: "carts.*"
``` ```

View File

@ -1,8 +1,8 @@
package shared package shared
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"log"
"strings" "strings"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
@ -83,14 +83,14 @@ type RulesPolicy struct {
} }
type RulePolicy struct { type RulePolicy struct {
Type string `yaml:"type"` Type string `yaml:"type"`
Service string `yaml:"service"` Service string `yaml:"service"`
Path string `yaml:"path"` Path string `yaml:"path"`
Method string `yaml:"method"` Method string `yaml:"method"`
Key string `yaml:"key"` Key string `yaml:"key"`
Value string `yaml:"value"` Value string `yaml:"value"`
Latency int64 `yaml:"latency"` ResponseTime int64 `yaml:"response-time"`
Name string `yaml:"name"` Name string `yaml:"name"`
} }
type RulesMatched struct { type RulesMatched struct {
@ -99,14 +99,17 @@ type RulesMatched struct {
} }
func (r *RulePolicy) validateType() bool { func (r *RulePolicy) validateType() bool {
permitedTypes := []string{"json", "header", "latency"} permitedTypes := []string{"json", "header", "slo"}
_, found := Find(permitedTypes, r.Type) _, found := Find(permitedTypes, r.Type)
if !found { if !found {
fmt.Printf("\nRule with name %s will be ignored. Err: only json, header and latency types are supported on rule definition.\n", r.Name) log.Printf("Error: %s. ", r.Name)
log.Printf("Only json, header and slo types are supported on rule definition. This rule will be ignored\n")
found = false
} }
if strings.ToLower(r.Type) == "latency" { if strings.ToLower(r.Type) == "slo" {
if r.Latency == 0 { if r.ResponseTime <= 0 {
fmt.Printf("\nRule with name %s will be ignored. Err: when type=latency, the field Latency should be specified and have a value >= 1\n\n", r.Name) log.Printf("Error: %s. ", r.Name)
log.Printf("When type=slo, the field response-time should be specified and have a value >= 1\n\n")
found = false found = false
} }
} }
@ -124,10 +127,6 @@ func (rules *RulesPolicy) ValidateRulesPolicy() []int {
return invalidIndex return invalidIndex
} }
func (rules *RulesPolicy) RemoveRule(idx int) {
rules.Rules = append(rules.Rules[:idx], rules.Rules[idx+1:]...)
}
func Find(slice []string, val string) (int, bool) { func Find(slice []string, val string) (int, bool) {
for i, item := range slice { for i, item := range slice {
if item == val { if item == val {
@ -148,10 +147,15 @@ func DecodeEnforcePolicy(path string) (RulesPolicy, error) {
return enforcePolicy, err return enforcePolicy, err
} }
invalidIndex := enforcePolicy.ValidateRulesPolicy() invalidIndex := enforcePolicy.ValidateRulesPolicy()
var k = 0
if len(invalidIndex) != 0 { if len(invalidIndex) != 0 {
for i := range invalidIndex { for i, rule := range enforcePolicy.Rules {
enforcePolicy.RemoveRule(invalidIndex[i]) if !ContainsInt(invalidIndex, i) {
enforcePolicy.Rules[k] = rule
k++
}
} }
enforcePolicy.Rules = enforcePolicy.Rules[:k]
} }
return enforcePolicy, nil return enforcePolicy, nil
} }

View File

@ -1,4 +1,4 @@
package mizu package shared
func Contains(slice []string, containsValue string) bool { func Contains(slice []string, containsValue string) bool {
for _, sliceValue := range slice { for _, sliceValue := range slice {
@ -10,6 +10,16 @@ func Contains(slice []string, containsValue string) bool {
return false return false
} }
func ContainsInt(slice []int, containsValue int) bool {
for _, sliceValue := range slice {
if sliceValue == containsValue {
return true
}
}
return false
}
func Unique(slice []string) []string { func Unique(slice []string) []string {
keys := make(map[string]bool) keys := make(map[string]bool)
var list []string var list []string

View File

@ -1,8 +1,8 @@
package mizu_test package shared_test
import ( import (
"fmt" "fmt"
"github.com/up9inc/mizu/cli/mizu" "github.com/up9inc/mizu/shared"
"reflect" "reflect"
"testing" "testing"
) )
@ -21,7 +21,7 @@ func TestContainsExists(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.ContainsValue, func(t *testing.T) { t.Run(test.ContainsValue, func(t *testing.T) {
actual := mizu.Contains(test.Slice, test.ContainsValue) actual := shared.Contains(test.Slice, test.ContainsValue)
if actual != test.Expected { if actual != test.Expected {
t.Errorf("unexpected result - Expected: %v, actual: %v", test.Expected, actual) t.Errorf("unexpected result - Expected: %v, actual: %v", test.Expected, actual)
} }
@ -43,7 +43,7 @@ func TestContainsNotExists(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.ContainsValue, func(t *testing.T) { t.Run(test.ContainsValue, func(t *testing.T) {
actual := mizu.Contains(test.Slice, test.ContainsValue) actual := shared.Contains(test.Slice, test.ContainsValue)
if actual != test.Expected { if actual != test.Expected {
t.Errorf("unexpected result - Expected: %v, actual: %v", test.Expected, actual) t.Errorf("unexpected result - Expected: %v, actual: %v", test.Expected, actual)
} }
@ -63,7 +63,7 @@ func TestContainsEmptySlice(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.ContainsValue, func(t *testing.T) { t.Run(test.ContainsValue, func(t *testing.T) {
actual := mizu.Contains(test.Slice, test.ContainsValue) actual := shared.Contains(test.Slice, test.ContainsValue)
if actual != test.Expected { if actual != test.Expected {
t.Errorf("unexpected result - Expected: %v, actual: %v", test.Expected, actual) t.Errorf("unexpected result - Expected: %v, actual: %v", test.Expected, actual)
} }
@ -83,7 +83,7 @@ func TestContainsNilSlice(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.ContainsValue, func(t *testing.T) { t.Run(test.ContainsValue, func(t *testing.T) {
actual := mizu.Contains(test.Slice, test.ContainsValue) actual := shared.Contains(test.Slice, test.ContainsValue)
if actual != test.Expected { if actual != test.Expected {
t.Errorf("unexpected result - Expected: %v, actual: %v", test.Expected, actual) t.Errorf("unexpected result - Expected: %v, actual: %v", test.Expected, actual)
} }
@ -102,7 +102,7 @@ func TestUniqueNoDuplicateValues(t *testing.T) {
for index, test := range tests { for index, test := range tests {
t.Run(fmt.Sprintf("%v", index), func(t *testing.T) { t.Run(fmt.Sprintf("%v", index), func(t *testing.T) {
actual := mizu.Unique(test.Slice) actual := shared.Unique(test.Slice)
if !reflect.DeepEqual(test.Expected, actual) { if !reflect.DeepEqual(test.Expected, actual) {
t.Errorf("unexpected result - Expected: %v, actual: %v", test.Expected, actual) t.Errorf("unexpected result - Expected: %v, actual: %v", test.Expected, actual)
} }
@ -121,7 +121,7 @@ func TestUniqueDuplicateValues(t *testing.T) {
for index, test := range tests { for index, test := range tests {
t.Run(fmt.Sprintf("%v", index), func(t *testing.T) { t.Run(fmt.Sprintf("%v", index), func(t *testing.T) {
actual := mizu.Unique(test.Slice) actual := shared.Unique(test.Slice)
if !reflect.DeepEqual(test.Expected, actual) { if !reflect.DeepEqual(test.Expected, actual) {
t.Errorf("unexpected result - Expected: %v, actual: %v", test.Expected, actual) t.Errorf("unexpected result - Expected: %v, actual: %v", test.Expected, actual)
} }

View File

@ -106,7 +106,7 @@ type MizuEntry struct {
UpdatedAt time.Time UpdatedAt time.Time
ProtocolName string `json:"protocolName" gorm:"column:protocolName"` ProtocolName string `json:"protocolName" gorm:"column:protocolName"`
ProtocolLongName string `json:"protocolLongName" gorm:"column:protocolLongName"` ProtocolLongName string `json:"protocolLongName" gorm:"column:protocolLongName"`
ProtocolAbbreviation string `json:"protocolAbbreviation" gorm:"column:protocolVersion"` ProtocolAbbreviation string `json:"protocolAbbreviation" gorm:"column:protocolAbbreviation"`
ProtocolVersion string `json:"protocolVersion" gorm:"column:protocolVersion"` ProtocolVersion string `json:"protocolVersion" gorm:"column:protocolVersion"`
ProtocolBackgroundColor string `json:"protocolBackgroundColor" gorm:"column:protocolBackgroundColor"` ProtocolBackgroundColor string `json:"protocolBackgroundColor" gorm:"column:protocolBackgroundColor"`
ProtocolForegroundColor string `json:"protocolForegroundColor" gorm:"column:protocolForegroundColor"` ProtocolForegroundColor string `json:"protocolForegroundColor" gorm:"column:protocolForegroundColor"`
@ -138,6 +138,7 @@ type MizuEntryWrapper struct {
BodySize int64 `json:"bodySize"` BodySize int64 `json:"bodySize"`
Data MizuEntry `json:"data"` Data MizuEntry `json:"data"`
Rules []map[string]interface{} `json:"rulesMatched,omitempty"` Rules []map[string]interface{} `json:"rulesMatched,omitempty"`
IsRulesEnabled bool `json:"isRulesEnabled"`
} }
type BaseEntryDetails struct { type BaseEntryDetails struct {
@ -156,7 +157,7 @@ type BaseEntryDetails struct {
SourcePort string `json:"sourcePort,omitempty"` SourcePort string `json:"sourcePort,omitempty"`
DestinationPort string `json:"destinationPort,omitempty"` DestinationPort string `json:"destinationPort,omitempty"`
IsOutgoing bool `json:"isOutgoing,omitempty"` IsOutgoing bool `json:"isOutgoing,omitempty"`
Latency int64 `json:"latency,omitempty"` Latency int64 `json:"latency"`
Rules ApplicableRules `json:"rules,omitempty"` Rules ApplicableRules `json:"rules,omitempty"`
} }
@ -190,6 +191,7 @@ func (bed *BaseEntryDetails) UnmarshalData(entry *MizuEntry) error {
bed.Timestamp = entry.Timestamp bed.Timestamp = entry.Timestamp
bed.RequestSenderIp = entry.RequestSenderIp bed.RequestSenderIp = entry.RequestSenderIp
bed.IsOutgoing = entry.IsOutgoing bed.IsOutgoing = entry.IsOutgoing
bed.Latency = entry.ElapsedTime
return nil return nil
} }

View File

@ -1,7 +1,7 @@
package api package api
type TrafficFilteringOptions struct { type TrafficFilteringOptions struct {
HealthChecksUserAgentHeaders []string IgnoredUserAgents []string
PlainTextMaskingRegexes []*SerializableRegexp PlainTextMaskingRegexes []*SerializableRegexp
DisableRedaction bool DisableRedaction bool
} }

View File

@ -312,7 +312,7 @@ func (d dissecting) Summarize(entry *api.MizuEntry) *api.BaseEntryDetails {
SourcePort: entry.SourcePort, SourcePort: entry.SourcePort,
DestinationPort: entry.DestinationPort, DestinationPort: entry.DestinationPort,
IsOutgoing: entry.IsOutgoing, IsOutgoing: entry.IsOutgoing,
Latency: 0, Latency: entry.ElapsedTime,
Rules: api.ApplicableRules{ Rules: api.ApplicableRules{
Latency: 0, Latency: 0,
Status: false, Status: false,

View File

@ -14,9 +14,14 @@ import (
) )
func filterAndEmit(item *api.OutputChannelItem, emitter api.Emitter, options *api.TrafficFilteringOptions) { func filterAndEmit(item *api.OutputChannelItem, emitter api.Emitter, options *api.TrafficFilteringOptions) {
if IsIgnoredUserAgent(item, options) {
return
}
if !options.DisableRedaction { if !options.DisableRedaction {
FilterSensitiveData(item, options) FilterSensitiveData(item, options)
} }
emitter.Emit(item) emitter.Emit(item)
} }

View File

@ -223,7 +223,7 @@ func (d dissecting) Summarize(entry *api.MizuEntry) *api.BaseEntryDetails {
SourcePort: entry.SourcePort, SourcePort: entry.SourcePort,
DestinationPort: entry.DestinationPort, DestinationPort: entry.DestinationPort,
IsOutgoing: entry.IsOutgoing, IsOutgoing: entry.IsOutgoing,
Latency: 0, Latency: entry.ElapsedTime,
Rules: api.ApplicableRules{ Rules: api.ApplicableRules{
Latency: 0, Latency: 0,
Status: false, Status: false,

View File

@ -25,6 +25,30 @@ var personallyIdentifiableDataFields = []string{"token", "authorization", "authe
"zip", "zipcode", "address", "country", "firstname", "lastname", "zip", "zipcode", "address", "country", "firstname", "lastname",
"middlename", "fname", "lname", "birthdate"} "middlename", "fname", "lname", "birthdate"}
func IsIgnoredUserAgent(item *api.OutputChannelItem, options *api.TrafficFilteringOptions) bool {
if item.Protocol.Name != "http" {
return false
}
request := item.Pair.Request.Payload.(HTTPPayload).Data.(*http.Request)
for headerKey, headerValues := range request.Header {
if strings.ToLower(headerKey) == "user-agent" {
for _, userAgent := range options.IgnoredUserAgents {
for _, headerValue := range headerValues {
if strings.Contains(strings.ToLower(headerValue), strings.ToLower(userAgent)) {
return true
}
}
}
return false
}
}
return false
}
func FilterSensitiveData(item *api.OutputChannelItem, options *api.TrafficFilteringOptions) { func FilterSensitiveData(item *api.OutputChannelItem, options *api.TrafficFilteringOptions) {
request := item.Pair.Request.Payload.(HTTPPayload).Data.(*http.Request) request := item.Pair.Request.Payload.(HTTPPayload).Data.(*http.Request)
response := item.Pair.Response.Payload.(HTTPPayload).Data.(*http.Response) response := item.Pair.Response.Payload.(HTTPPayload).Data.(*http.Response)
@ -86,6 +110,10 @@ func getContentTypeHeaderValue(headers http.Header) string {
} }
func isFieldNameSensitive(fieldName string) bool { func isFieldNameSensitive(fieldName string) bool {
if fieldName == ":authority" {
return false
}
name := strings.ToLower(fieldName) name := strings.ToLower(fieldName)
name = strings.ReplaceAll(name, "_", "") name = strings.ReplaceAll(name, "_", "")
name = strings.ReplaceAll(name, "-", "") name = strings.ReplaceAll(name, "-", "")

View File

@ -186,7 +186,7 @@ func (d dissecting) Summarize(entry *api.MizuEntry) *api.BaseEntryDetails {
SourcePort: entry.SourcePort, SourcePort: entry.SourcePort,
DestinationPort: entry.DestinationPort, DestinationPort: entry.DestinationPort,
IsOutgoing: entry.IsOutgoing, IsOutgoing: entry.IsOutgoing,
Latency: 0, Latency: entry.ElapsedTime,
Rules: api.ApplicableRules{ Rules: api.ApplicableRules{
Latency: 0, Latency: 0,
Status: false, Status: false,

View File

@ -0,0 +1,14 @@
package main
//ConnectError redis connection error,such as io timeout
type ConnectError struct {
Message string
}
func newConnectError(message string) *ConnectError {
return &ConnectError{Message: message}
}
func (e *ConnectError) Error() string {
return e.Message
}

View File

@ -0,0 +1,9 @@
module github.com/up9inc/mizu/tap/extensions/redis
go 1.16
require (
github.com/up9inc/mizu/tap/api v0.0.0
)
replace github.com/up9inc/mizu/tap/api v0.0.0 => ../../api

View File

@ -0,0 +1,55 @@
package main
import (
"fmt"
"github.com/up9inc/mizu/tap/api"
)
func handleClientStream(tcpID *api.TcpID, counterPair *api.CounterPair, superTimer *api.SuperTimer, emitter api.Emitter, request *RedisPacket) error {
counterPair.Request++
ident := fmt.Sprintf(
"%s->%s %s->%s %d",
tcpID.SrcIP,
tcpID.DstIP,
tcpID.SrcPort,
tcpID.DstPort,
counterPair.Request,
)
item := reqResMatcher.registerRequest(ident, request, superTimer.CaptureTime)
if item != nil {
item.ConnectionInfo = &api.ConnectionInfo{
ClientIP: tcpID.SrcIP,
ClientPort: tcpID.SrcPort,
ServerIP: tcpID.DstIP,
ServerPort: tcpID.DstPort,
IsOutgoing: true,
}
emitter.Emit(item)
}
return nil
}
func handleServerStream(tcpID *api.TcpID, counterPair *api.CounterPair, superTimer *api.SuperTimer, emitter api.Emitter, response *RedisPacket) error {
counterPair.Response++
ident := fmt.Sprintf(
"%s->%s %s->%s %d",
tcpID.DstIP,
tcpID.SrcIP,
tcpID.DstPort,
tcpID.SrcPort,
counterPair.Response,
)
item := reqResMatcher.registerResponse(ident, response, superTimer.CaptureTime)
if item != nil {
item.ConnectionInfo = &api.ConnectionInfo{
ClientIP: tcpID.DstIP,
ClientPort: tcpID.DstPort,
ServerIP: tcpID.SrcIP,
ServerPort: tcpID.SrcPort,
IsOutgoing: false,
}
emitter.Emit(item)
}
return nil
}

View File

@ -0,0 +1,57 @@
package main
import (
"encoding/json"
"github.com/up9inc/mizu/tap/api"
)
type RedisPayload struct {
Data interface{}
}
type RedisPayloader interface {
MarshalJSON() ([]byte, error)
}
func (h RedisPayload) MarshalJSON() ([]byte, error) {
return json.Marshal(h.Data)
}
type RedisWrapper struct {
Method string `json:"method"`
Url string `json:"url"`
Details interface{} `json:"details"`
}
func representGeneric(generic map[string]interface{}) (representation []interface{}) {
details, _ := json.Marshal([]map[string]string{
{
"name": "Type",
"value": generic["type"].(string),
},
{
"name": "Command",
"value": generic["command"].(string),
},
{
"name": "Key",
"value": generic["key"].(string),
},
{
"name": "Value",
"value": generic["value"].(string),
},
{
"name": "Keyword",
"value": generic["keyword"].(string),
},
})
representation = append(representation, map[string]string{
"type": api.TABLE,
"title": "Details",
"data": string(details),
})
return
}

View File

@ -0,0 +1,154 @@
package main
import (
"bufio"
"encoding/json"
"fmt"
"log"
"github.com/up9inc/mizu/tap/api"
)
var protocol api.Protocol = api.Protocol{
Name: "redis",
LongName: "Redis Serialization Protocol",
Abbreviation: "REDIS",
Version: "3.x",
BackgroundColor: "#a41e11",
ForegroundColor: "#ffffff",
FontSize: 11,
ReferenceLink: "https://redis.io/topics/protocol",
Ports: []string{"6379"},
Priority: 3,
}
func init() {
log.Println("Initializing Redis extension...")
}
type dissecting string
func (d dissecting) Register(extension *api.Extension) {
extension.Protocol = &protocol
extension.MatcherMap = reqResMatcher.openMessagesMap
}
func (d dissecting) Ping() {
log.Printf("pong %s\n", protocol.Name)
}
func (d dissecting) Dissect(b *bufio.Reader, isClient bool, tcpID *api.TcpID, counterPair *api.CounterPair, superTimer *api.SuperTimer, superIdentifier *api.SuperIdentifier, emitter api.Emitter, options *api.TrafficFilteringOptions) error {
is := &RedisInputStream{
Reader: b,
Buf: make([]byte, 8192),
}
proto := NewProtocol(is)
for {
redisPacket, err := proto.Read()
if err != nil {
return err
}
if isClient {
handleClientStream(tcpID, counterPair, superTimer, emitter, redisPacket)
} else {
handleServerStream(tcpID, counterPair, superTimer, emitter, redisPacket)
}
}
}
func (d dissecting) Analyze(item *api.OutputChannelItem, entryId string, resolvedSource string, resolvedDestination string) *api.MizuEntry {
request := item.Pair.Request.Payload.(map[string]interface{})
reqDetails := request["details"].(map[string]interface{})
service := "redis"
if resolvedDestination != "" {
service = resolvedDestination
} else if resolvedSource != "" {
service = resolvedSource
}
method := ""
if reqDetails["command"] != nil {
method = reqDetails["command"].(string)
}
summary := ""
if reqDetails["key"] != nil {
summary = reqDetails["key"].(string)
}
request["url"] = summary
entryBytes, _ := json.Marshal(item.Pair)
return &api.MizuEntry{
ProtocolName: protocol.Name,
ProtocolLongName: protocol.LongName,
ProtocolAbbreviation: protocol.Abbreviation,
ProtocolVersion: protocol.Version,
ProtocolBackgroundColor: protocol.BackgroundColor,
ProtocolForegroundColor: protocol.ForegroundColor,
ProtocolFontSize: protocol.FontSize,
ProtocolReferenceLink: protocol.ReferenceLink,
EntryId: entryId,
Entry: string(entryBytes),
Url: fmt.Sprintf("%s%s", service, summary),
Method: method,
Status: 0,
RequestSenderIp: item.ConnectionInfo.ClientIP,
Service: service,
Timestamp: item.Timestamp,
ElapsedTime: 0,
Path: summary,
ResolvedSource: resolvedSource,
ResolvedDestination: resolvedDestination,
SourceIp: item.ConnectionInfo.ClientIP,
DestinationIp: item.ConnectionInfo.ServerIP,
SourcePort: item.ConnectionInfo.ClientPort,
DestinationPort: item.ConnectionInfo.ServerPort,
IsOutgoing: item.ConnectionInfo.IsOutgoing,
}
}
func (d dissecting) Summarize(entry *api.MizuEntry) *api.BaseEntryDetails {
return &api.BaseEntryDetails{
Id: entry.EntryId,
Protocol: protocol,
Url: entry.Url,
RequestSenderIp: entry.RequestSenderIp,
Service: entry.Service,
Summary: entry.Path,
StatusCode: entry.Status,
Method: entry.Method,
Timestamp: entry.Timestamp,
SourceIp: entry.SourceIp,
DestinationIp: entry.DestinationIp,
SourcePort: entry.SourcePort,
DestinationPort: entry.DestinationPort,
IsOutgoing: entry.IsOutgoing,
Latency: entry.ElapsedTime,
Rules: api.ApplicableRules{
Latency: 0,
Status: false,
},
}
}
func (d dissecting) Represent(entry *api.MizuEntry) (p api.Protocol, object []byte, bodySize int64, err error) {
p = protocol
bodySize = 0
var root map[string]interface{}
json.Unmarshal([]byte(entry.Entry), &root)
representation := make(map[string]interface{}, 0)
request := root["request"].(map[string]interface{})["payload"].(map[string]interface{})
response := root["response"].(map[string]interface{})["payload"].(map[string]interface{})
reqDetails := request["details"].(map[string]interface{})
resDetails := response["details"].(map[string]interface{})
repRequest := representGeneric(reqDetails)
repResponse := representGeneric(resDetails)
representation["request"] = repRequest
representation["response"] = repResponse
object, err = json.Marshal(representation)
return
}
var Dissector dissecting

View File

@ -0,0 +1,102 @@
package main
import (
"fmt"
"strings"
"sync"
"time"
"github.com/up9inc/mizu/tap/api"
)
var reqResMatcher = createResponseRequestMatcher() // global
// Key is {client_addr}:{client_port}->{dest_addr}:{dest_port}_{incremental_counter}
type requestResponseMatcher struct {
openMessagesMap *sync.Map
}
func createResponseRequestMatcher() requestResponseMatcher {
newMatcher := &requestResponseMatcher{openMessagesMap: &sync.Map{}}
return *newMatcher
}
func (matcher *requestResponseMatcher) registerRequest(ident string, request *RedisPacket, captureTime time.Time) *api.OutputChannelItem {
split := splitIdent(ident)
key := genKey(split)
requestRedisMessage := api.GenericMessage{
IsRequest: true,
CaptureTime: captureTime,
Payload: RedisPayload{
Data: &RedisWrapper{
Method: string(request.Command),
Url: "",
Details: request,
},
},
}
if response, found := matcher.openMessagesMap.LoadAndDelete(key); found {
// Type assertion always succeeds because all of the map's values are of api.GenericMessage type
responseRedisMessage := response.(*api.GenericMessage)
if responseRedisMessage.IsRequest {
return nil
}
return matcher.preparePair(&requestRedisMessage, responseRedisMessage)
}
matcher.openMessagesMap.Store(key, &requestRedisMessage)
return nil
}
func (matcher *requestResponseMatcher) registerResponse(ident string, response *RedisPacket, captureTime time.Time) *api.OutputChannelItem {
split := splitIdent(ident)
key := genKey(split)
responseRedisMessage := api.GenericMessage{
IsRequest: false,
CaptureTime: captureTime,
Payload: RedisPayload{
Data: &RedisWrapper{
Method: string(response.Command),
Url: "",
Details: response,
},
},
}
if request, found := matcher.openMessagesMap.LoadAndDelete(key); found {
// Type assertion always succeeds because all of the map's values are of api.GenericMessage type
requestRedisMessage := request.(*api.GenericMessage)
if !requestRedisMessage.IsRequest {
return nil
}
return matcher.preparePair(requestRedisMessage, &responseRedisMessage)
}
matcher.openMessagesMap.Store(key, &responseRedisMessage)
return nil
}
func (matcher *requestResponseMatcher) preparePair(requestRedisMessage *api.GenericMessage, responseRedisMessage *api.GenericMessage) *api.OutputChannelItem {
return &api.OutputChannelItem{
Protocol: protocol,
Timestamp: requestRedisMessage.CaptureTime.UnixNano() / int64(time.Millisecond),
ConnectionInfo: nil,
Pair: &api.RequestResponsePair{
Request: *requestRedisMessage,
Response: *responseRedisMessage,
},
}
}
func splitIdent(ident string) []string {
ident = strings.Replace(ident, "->", " ", -1)
return strings.Split(ident, " ")
}
func genKey(split []string) string {
key := fmt.Sprintf("%s:%s->%s:%s,%s", split[0], split[2], split[1], split[3], split[4])
return key
}

View File

@ -0,0 +1,474 @@
package main
import (
"bufio"
"errors"
"fmt"
"log"
"math"
"reflect"
"strconv"
"strings"
"time"
)
const (
askPrefix = "ASK "
movedPrefix = "MOVED "
clusterDownPrefix = "CLUSTERDOWN "
busyPrefix = "BUSY "
noscriptPrefix = "NOSCRIPT "
defaultHost = "localhost"
defaultPort = 6379
defaultSentinelPort = 26379
defaultTimeout = 5 * time.Second
defaultDatabase = 2 * time.Second
dollarByte = '$'
asteriskByte = '*'
plusByte = '+'
minusByte = '-'
colonByte = ':'
notApplicableByte = '0'
sentinelMasters = "masters"
sentinelGetMasterAddrByName = "get-master-addr-by-name"
sentinelReset = "reset"
sentinelSlaves = "slaves"
sentinelFailOver = "failover"
sentinelMonitor = "monitor"
sentinelRemove = "remove"
sentinelSet = "set"
clusterNodes = "nodes"
clusterMeet = "meet"
clusterReset = "reset"
clusterAddSlots = "addslots"
clusterDelSlots = "delslots"
clusterInfo = "info"
clusterGetKeysInSlot = "getkeysinslot"
clusterSetSlot = "setslot"
clusterSetSlotNode = "node"
clusterSetSlotMigrating = "migrating"
clusterSetSlotImporting = "importing"
clusterSetSlotStable = "stable"
clusterForget = "forget"
clusterFlushSlot = "flushslots"
clusterKeySlot = "keyslot"
clusterCountKeyInSlot = "countkeysinslot"
clusterSaveConfig = "saveconfig"
clusterReplicate = "replicate"
clusterSlaves = "slaves"
clusterFailOver = "failover"
clusterSlots = "slots"
pubSubChannels = "channels"
pubSubNumSub = "numsub"
pubSubNumPat = "numpat"
)
//intToByteArr convert int to byte array
func intToByteArr(a int) []byte {
buf := make([]byte, 0)
return strconv.AppendInt(buf, int64(a), 10)
}
var (
bytesTrue = intToByteArr(1)
bytesFalse = intToByteArr(0)
bytesTilde = []byte("~")
positiveInfinityBytes = []byte("+inf")
negativeInfinityBytes = []byte("-inf")
)
var (
sizeTable = []int{9, 99, 999, 9999, 99999, 999999, 9999999, 99999999,
999999999, math.MaxInt32}
digitTens = []byte{'0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1',
'1', '1', '1', '1', '1', '1', '1', '1', '1', '2', '2', '2', '2', '2', '2', '2', '2', '2',
'2', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '4', '4', '4', '4', '4', '4', '4',
'4', '4', '4', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '6', '6', '6', '6', '6',
'6', '6', '6', '6', '6', '7', '7', '7', '7', '7', '7', '7', '7', '7', '7', '8', '8', '8',
'8', '8', '8', '8', '8', '8', '8', '9', '9', '9', '9', '9', '9', '9', '9', '9', '9'}
digitOnes = []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4',
'5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2',
'3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
digits = []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a',
'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
't', 'u', 'v', 'w', 'x', 'y', 'z'}
)
// receive message from redis
type RedisInputStream struct {
*bufio.Reader
Buf []byte
count int
limit int
}
func (r *RedisInputStream) readByte() (byte, error) {
err := r.ensureFill()
if err != nil {
return 0, err
}
ret := r.Buf[r.count]
r.count++
return ret, nil
}
func (r *RedisInputStream) ensureFill() error {
if r.count < r.limit {
return nil
}
var err error
r.limit, err = r.Read(r.Buf)
if err != nil {
return newConnectError(err.Error())
}
r.count = 0
if r.limit == -1 {
return newConnectError("Unexpected end of stream")
}
return nil
}
func (r *RedisInputStream) readLine() (string, error) {
buf := ""
for {
err := r.ensureFill()
if err != nil {
return "", err
}
b := r.Buf[r.count]
r.count++
if b == '\r' {
err := r.ensureFill()
if err != nil {
return "", err
}
c := r.Buf[r.count]
r.count++
if c == '\n' {
break
}
buf += string(b)
buf += string(c)
} else {
buf += string(b)
}
}
if buf == "" {
return "", newConnectError("It seems like server has closed the connection.")
}
return buf, nil
}
func (r *RedisInputStream) readLineBytes() ([]byte, error) {
err := r.ensureFill()
if err != nil {
return nil, err
}
pos := r.count
buf := r.Buf
for {
if pos == r.limit {
return r.readLineBytesSlowly()
}
p := buf[pos]
pos++
if p == '\r' {
if pos == r.limit {
return r.readLineBytesSlowly()
}
p := buf[pos]
pos++
if p == '\n' {
break
}
}
}
N := pos - r.count - 2
line := make([]byte, N)
j := 0
for i := r.count; i <= N; i++ {
line[j] = buf[i]
j++
}
r.count = pos
return line, nil
}
func (r *RedisInputStream) readLineBytesSlowly() ([]byte, error) {
buf := make([]byte, 0)
for {
err := r.ensureFill()
if err != nil {
return nil, err
}
b := r.Buf[r.count]
r.count++
if b == 'r' {
err := r.ensureFill()
if err != nil {
return nil, err
}
c := r.Buf[r.count]
r.count++
if c == '\n' {
break
}
buf = append(buf, b)
buf = append(buf, c)
} else {
buf = append(buf, b)
}
}
return buf, nil
}
func (r *RedisInputStream) readIntCrLf() (int64, error) {
err := r.ensureFill()
if err != nil {
return 0, err
}
buf := r.Buf
isNeg := false
if buf[r.count] == '-' {
isNeg = true
}
if isNeg {
r.count++
}
value := int64(0)
for {
err := r.ensureFill()
if err != nil {
return 0, err
}
b := buf[r.count]
r.count++
if b == '\r' {
err := r.ensureFill()
if err != nil {
return 0, err
}
c := buf[r.count]
r.count++
if c != '\n' {
return 0, newConnectError("Unexpected character!")
}
break
} else {
value = value*10 + int64(b) - int64('0')
}
}
if isNeg {
return -value, nil
}
return value, nil
}
type RedisProtocol struct {
is *RedisInputStream
}
func NewProtocol(is *RedisInputStream) *RedisProtocol {
return &RedisProtocol{
is: is,
}
}
func (p *RedisProtocol) Read() (packet *RedisPacket, err error) {
x, r, err := p.process()
if err != nil {
return
}
packet = &RedisPacket{}
packet.Type = r
switch x.(type) {
case []interface{}:
array := x.([]interface{})
packet.Command = RedisCommand(strings.ToUpper(string(array[0].([]uint8))))
if len(array) > 1 {
packet.Key = string(array[1].([]uint8))
}
if len(array) > 2 {
packet.Value = string(array[2].([]uint8))
}
case []uint8:
val := string(x.([]uint8))
if packet.Type == types[plusByte] {
packet.Keyword = RedisKeyword(strings.ToUpper(val))
if !isValidRedisKeyword(keywords, packet.Keyword) {
err = errors.New(fmt.Sprintf("Unrecognized keyword: %s", string(packet.Command)))
return
}
} else {
packet.Value = val
}
case string:
packet.Value = x.(string)
default:
msg := fmt.Sprintf("Unrecognized Redis data type: %v\n", reflect.TypeOf(x))
log.Printf(msg)
err = errors.New(msg)
return
}
if packet.Command != "" {
if !isValidRedisCommand(commands, packet.Command) {
err = errors.New(fmt.Sprintf("Unrecognized command: %s", string(packet.Command)))
return
}
}
return
}
func (p *RedisProtocol) process() (v interface{}, r RedisType, err error) {
b, err := p.is.readByte()
if err != nil {
return nil, types[notApplicableByte], newConnectError(err.Error())
}
switch b {
case plusByte:
v, err = p.processSimpleString()
r = types[plusByte]
return
case dollarByte:
v, err = p.processBulkString()
r = types[dollarByte]
return
case asteriskByte:
v, err = p.processArray()
r = types[asteriskByte]
return
case colonByte:
v, err = p.processInteger()
r = types[colonByte]
return
case minusByte:
v, err = p.processError()
r = types[minusByte]
return
default:
return nil, types[notApplicableByte], newConnectError(fmt.Sprintf("Unknown reply: %b", b))
}
}
func (p *RedisProtocol) processSimpleString() ([]byte, error) {
return p.is.readLineBytes()
}
func (p *RedisProtocol) processBulkString() ([]byte, error) {
l, err := p.is.readIntCrLf()
if err != nil {
return nil, newConnectError(err.Error())
}
if l == -1 {
return nil, nil
}
line := make([]byte, 0)
for {
err := p.is.ensureFill()
if err != nil {
return nil, err
}
b := p.is.Buf[p.is.count]
p.is.count++
if b == '\r' {
err := p.is.ensureFill()
if err != nil {
return nil, err
}
c := p.is.Buf[p.is.count]
p.is.count++
if c != '\n' {
return nil, newConnectError("Unexpected character!")
}
break
} else {
line = append(line, b)
}
}
return line, nil
}
func (p *RedisProtocol) processArray() ([]interface{}, error) {
l, err := p.is.readIntCrLf()
if err != nil {
return nil, newConnectError(err.Error())
}
if l == -1 {
return nil, nil
}
ret := make([]interface{}, 0)
for i := 0; i < int(l); i++ {
if obj, _, err := p.process(); err != nil {
ret = append(ret, err)
} else {
ret = append(ret, obj)
}
}
return ret, nil
}
func (p *RedisProtocol) processInteger() (int64, error) {
return p.is.readIntCrLf()
}
func (p *RedisProtocol) processError() (interface{}, error) {
msg, err := p.is.readLine()
if err != nil {
return nil, newConnectError(err.Error())
}
if strings.HasPrefix(msg, movedPrefix) {
host, port, slot, err := p.parseTargetHostAndSlot(msg)
if err != nil {
return nil, err
}
return fmt.Sprintf("MovedDataError: %s host: %s port: %d slot: %d", msg, host, port, slot), nil
} else if strings.HasPrefix(msg, askPrefix) {
host, port, slot, err := p.parseTargetHostAndSlot(msg)
if err != nil {
return nil, err
}
return fmt.Sprintf("AskDataError: %s host: %s port: %d slot: %d", msg, host, port, slot), nil
} else if strings.HasPrefix(msg, clusterDownPrefix) {
return fmt.Sprintf("ClusterError: %s", msg), nil
} else if strings.HasPrefix(msg, busyPrefix) {
return fmt.Sprintf("BusyError: %s", msg), nil
} else if strings.HasPrefix(msg, noscriptPrefix) {
return fmt.Sprintf("NoScriptError: %s", msg), nil
}
return fmt.Sprintf("DataError: %s", msg), nil
}
func (p *RedisProtocol) parseTargetHostAndSlot(clusterRedirectResponse string) (host string, po int, slot int, err error) {
arr := strings.Split(clusterRedirectResponse, " ")
host, port := p.extractParts(arr[2])
slot, err = strconv.Atoi(arr[1])
po, err = strconv.Atoi(port)
return
}
func (p *RedisProtocol) extractParts(from string) (string, string) {
idx := strings.LastIndex(from, ":")
host := from
if idx != -1 {
host = from[0:idx]
}
port := ""
if idx != -1 {
port = from[idx+1:]
}
return host, port
}

View File

@ -0,0 +1,290 @@
package main
type RedisType string
type RedisCommand string
type RedisKeyword string
var types map[rune]RedisType = map[rune]RedisType{
plusByte: "Simple String",
dollarByte: "Bulk String",
asteriskByte: "Array",
colonByte: "Integer",
minusByte: "Error",
notApplicableByte: "N/A",
}
var commands []RedisCommand = []RedisCommand{
"PING",
"SET",
"GET",
"QUIT",
"EXISTS",
"DEL",
"UNLINK",
"TYPE",
"FLUSHDB",
"KEYS",
"RANDOMKEY",
"RENAME",
"RENAMENX",
"RENAMEX",
"DBSIZE",
"EXPIRE",
"EXPIREAT",
"TTL",
"SELECT",
"MOVE",
"FLUSHALL",
"GETSET",
"MGET",
"SETNX",
"SETEX",
"MSET",
"MSETNX",
"DECRBY",
"DECR",
"INCRBY",
"INCR",
"APPEND",
"SUBSTR",
"HSET",
"HGET",
"HSETNX",
"HMSET",
"HMGET",
"HINCRBY",
"HEXISTS",
"HDEL",
"HLEN",
"HKEYS",
"HVALS",
"HGETALL",
"RPUSH",
"LPUSH",
"LLEN",
"LRANGE",
"LTRIM",
"LINDEX",
"LSET",
"LREM",
"LPOP",
"RPOP",
"RPOPLPUSH",
"SADD",
"SMEMBERS",
"SREM",
"SPOP",
"SMOVE",
"SCARD",
"SISMEMBER",
"SINTER",
"SINTERSTORE",
"SUNION",
"SUNIONSTORE",
"SDIFF",
"SDIFFSTORE",
"SRANDMEMBER",
"ZADD",
"ZRANGE",
"ZREM",
"ZINCRBY",
"ZRANK",
"ZREVRANK",
"ZREVRANGE",
"ZCARD",
"ZSCORE",
"MULTI",
"DISCARD",
"EXEC",
"WATCH",
"UNWATCH",
"SORT",
"BLPOP",
"BRPOP",
"AUTH",
"SUBSCRIBE",
"PUBLISH",
"UNSUBSCRIBE",
"PSUBSCRIBE",
"PUNSUBSCRIBE",
"PUBSUB",
"ZCOUNT",
"ZRANGEBYSCORE",
"ZREVRANGEBYSCORE",
"ZREMRANGEBYRANK",
"ZREMRANGEBYSCORE",
"ZUNIONSTORE",
"ZINTERSTORE",
"ZLEXCOUNT",
"ZRANGEBYLEX",
"ZREVRANGEBYLEX",
"ZREMRANGEBYLEX",
"SAVE",
"BGSAVE",
"BGREWRITEAOF",
"LASTSAVE",
"SHUTDOWN",
"INFO",
"MONITOR",
"SLAVEOF",
"CONFIG",
"STRLEN",
"SYNC",
"LPUSHX",
"PERSIST",
"RPUSHX",
"ECHO",
"LINSERT",
"DEBUG",
"BRPOPLPUSH",
"SETBIT",
"GETBIT",
"BITPOS",
"SETRANGE",
"GETRANGE",
"EVAL",
"EVALSHA",
"SCRIPT",
"SLOWLOG",
"OBJECT",
"BITCOUNT",
"BITOP",
"SENTINEL",
"DUMP",
"RESTORE",
"PEXPIRE",
"PEXPIREAT",
"PTTL",
"INCRBYFLOAT",
"PSETEX",
"CLIENT",
"TIME",
"MIGRATE",
"HINCRBYFLOAT",
"SCAN",
"HSCAN",
"SSCAN",
"ZSCAN",
"WAIT",
"CLUSTER",
"ASKING",
"PFADD",
"PFCOUNT",
"PFMERGE",
"READONLY",
"GEOADD",
"GEODIST",
"GEOHASH",
"GEOPOS",
"GEORADIUS",
"GEORADIUS_RO",
"GEORADIUSBYMEMBER",
"GEORADIUSBYMEMBER_RO",
"MODULE",
"BITFIELD",
"HSTRLEN",
"TOUCH",
"SWAPDB",
"MEMORY",
"XADD",
"XLEN",
"XDEL",
"XTRIM",
"XRANGE",
"XREVRANGE",
"XREAD",
"XACK",
"XGROUP",
"XREADGROUP",
"XPENDING",
"XCLAIM",
}
var keywords []RedisKeyword = []RedisKeyword{
"AGGREGATE",
"ALPHA",
"ASC",
"BY",
"DESC",
"GET",
"LIMIT",
"MESSAGE",
"NO",
"NOSORT",
"PMESSAGE",
"PSUBSCRIBE",
"PUNSUBSCRIBE",
"OK",
"ONE",
"QUEUED",
"SET",
"STORE",
"SUBSCRIBE",
"UNSUBSCRIBE",
"WEIGHTS",
"WITHSCORES",
"RESETSTAT",
"REWRITE",
"RESET",
"FLUSH",
"EXISTS",
"LOAD",
"KILL",
"LEN",
"REFCOUNT",
"ENCODING",
"IDLETIME",
"GETNAME",
"SETNAME",
"LIST",
"MATCH",
"COUNT",
"PING",
"PONG",
"UNLOAD",
"REPLACE",
"KEYS",
"PAUSE",
"DOCTOR",
"BLOCK",
"NOACK",
"STREAMS",
"KEY",
"CREATE",
"MKSTREAM",
"SETID",
"DESTROY",
"DELCONSUMER",
"MAXLEN",
"GROUP",
"IDLE",
"TIME",
"RETRYCOUNT",
"FORCE",
}
type RedisPacket struct {
Type RedisType `json:"type"`
Command RedisCommand `json:"command"`
Key string `json:"key"`
Value string `json:"value"`
Keyword RedisKeyword `json:"keyword"`
}
func isValidRedisCommand(s []RedisCommand, c RedisCommand) bool {
for _, v := range s {
if v == c {
return true
}
}
return false
}
func isValidRedisKeyword(s []RedisKeyword, c RedisKeyword) bool {
for _, v := range s {
if v == c {
return true
}
}
return false
}

6
ui/package-lock.json generated
View File

@ -13454,9 +13454,9 @@
} }
}, },
"react-scrollable-feed-virtualized": { "react-scrollable-feed-virtualized": {
"version": "1.4.2", "version": "1.4.3",
"resolved": "https://registry.npmjs.org/react-scrollable-feed-virtualized/-/react-scrollable-feed-virtualized-1.4.2.tgz", "resolved": "https://registry.npmjs.org/react-scrollable-feed-virtualized/-/react-scrollable-feed-virtualized-1.4.3.tgz",
"integrity": "sha512-j6M80ETqhSRBlygWe491gMPqCiAkVUQsLd/JR7DCKsZF44IYlJiunZWjdWe3//gxddTL68pjqq8pMvd1YN1bgw==" "integrity": "sha512-M9WgJKr57jCyWKNCksc3oi+xhtO0YbL9d7Ll8Sdc5ZWOIstNvdNbNX0k4Nq6kXUVaHCJ9qE8omdSI/CxT3MLAQ=="
}, },
"react-syntax-highlighter": { "react-syntax-highlighter": {
"version": "15.4.3", "version": "15.4.3",

View File

@ -21,7 +21,7 @@
"react-copy-to-clipboard": "^5.0.3", "react-copy-to-clipboard": "^5.0.3",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-scripts": "4.0.3", "react-scripts": "4.0.3",
"react-scrollable-feed-virtualized": "^1.4.2", "react-scrollable-feed-virtualized": "^1.4.3",
"react-syntax-highlighter": "^15.4.3", "react-syntax-highlighter": "^15.4.3",
"typescript": "^4.2.4", "typescript": "^4.2.4",
"web-vitals": "^1.1.1" "web-vitals": "^1.1.1"

View File

@ -19,7 +19,8 @@ interface EntriesListProps {
setNoMoreDataBottom: (flag: boolean) => void; setNoMoreDataBottom: (flag: boolean) => void;
methodsFilter: Array<string>; methodsFilter: Array<string>;
statusFilter: Array<string>; statusFilter: Array<string>;
pathFilter: string pathFilter: string;
serviceFilter: string;
listEntryREF: any; listEntryREF: any;
onScrollEvent: (isAtBottom:boolean) => void; onScrollEvent: (isAtBottom:boolean) => void;
scrollableList: boolean; scrollableList: boolean;
@ -32,7 +33,7 @@ enum FetchOperator {
const api = new Api(); const api = new Api();
export const EntriesList: React.FC<EntriesListProps> = ({entries, setEntries, focusedEntryId, setFocusedEntryId, connectionOpen, noMoreDataTop, setNoMoreDataTop, noMoreDataBottom, setNoMoreDataBottom, methodsFilter, statusFilter, pathFilter, listEntryREF, onScrollEvent, scrollableList}) => { export const EntriesList: React.FC<EntriesListProps> = ({entries, setEntries, focusedEntryId, setFocusedEntryId, connectionOpen, noMoreDataTop, setNoMoreDataTop, noMoreDataBottom, setNoMoreDataBottom, methodsFilter, statusFilter, pathFilter, serviceFilter, listEntryREF, onScrollEvent, scrollableList}) => {
const [loadMoreTop, setLoadMoreTop] = useState(false); const [loadMoreTop, setLoadMoreTop] = useState(false);
const [isLoadingTop, setIsLoadingTop] = useState(false); const [isLoadingTop, setIsLoadingTop] = useState(false);
@ -54,10 +55,11 @@ export const EntriesList: React.FC<EntriesListProps> = ({entries, setEntries, fo
const filterEntries = useCallback((entry) => { const filterEntries = useCallback((entry) => {
if(methodsFilter.length > 0 && !methodsFilter.includes(entry.method.toLowerCase())) return; if(methodsFilter.length > 0 && !methodsFilter.includes(entry.method.toLowerCase())) return;
if(pathFilter && entry.path?.toLowerCase()?.indexOf(pathFilter) === -1) return; if(pathFilter && entry.path?.toLowerCase()?.indexOf(pathFilter) === -1) return;
if(serviceFilter && entry.service?.toLowerCase()?.indexOf(serviceFilter) === -1) return;
if(statusFilter.includes(StatusType.SUCCESS) && entry.statusCode >= 400) return; if(statusFilter.includes(StatusType.SUCCESS) && entry.statusCode >= 400) return;
if(statusFilter.includes(StatusType.ERROR) && entry.statusCode < 400) return; if(statusFilter.includes(StatusType.ERROR) && entry.statusCode < 400) return;
return entry; return entry;
},[methodsFilter, pathFilter, statusFilter]) },[methodsFilter, pathFilter, statusFilter, serviceFilter])
const filteredEntries = useMemo(() => { const filteredEntries = useMemo(() => {
return entries.filter(filterEntries); return entries.filter(filterEntries);

View File

@ -41,7 +41,7 @@ const EntryTitle: React.FC<any> = ({protocol, data, bodySize, elapsedTime}) => {
<Protocol protocol={protocol} horizontal={true}/> <Protocol protocol={protocol} horizontal={true}/>
<div style={{right: "30px", position: "absolute", display: "flex"}}> <div style={{right: "30px", position: "absolute", display: "flex"}}>
{response.payload && <div style={{margin: "0 18px", opacity: 0.5}}>{formatSize(bodySize)}</div>} {response.payload && <div style={{margin: "0 18px", opacity: 0.5}}>{formatSize(bodySize)}</div>}
<div style={{marginRight: 18, opacity: 0.5}}>{Math.round(elapsedTime)}ms</div> {response.payload && <div style={{marginRight: 18, opacity: 0.5}}>{Math.round(elapsedTime)}ms</div>}
</div> </div>
</div>; </div>;
}; };
@ -71,7 +71,7 @@ export const EntryDetailed: React.FC<EntryDetailedProps> = ({entryData}) => {
/> />
{entryData.data && <EntrySummary data={entryData.data}/>} {entryData.data && <EntrySummary data={entryData.data}/>}
<> <>
{entryData.data && <EntryViewer representation={entryData.representation} rulesMatched={entryData.rulesMatched} elapsedTime={entryData.data.elapsedTime} color={entryData.protocol.backgroundColor}/>} {entryData.data && <EntryViewer representation={entryData.representation} isRulesEnabled={entryData.isRulesEnabled} rulesMatched={entryData.rulesMatched} elapsedTime={entryData.data.elapsedTime} color={entryData.protocol.backgroundColor}/>}
</> </>
</> </>
}; };

View File

@ -153,10 +153,8 @@ export const EntryTableSection: React.FC<EntrySectionProps> = ({title, color, ar
interface EntryPolicySectionProps { interface EntryPolicySectionProps {
service: string,
title: string, title: string,
color: string, color: string,
response: any,
latency?: number, latency?: number,
arrayToIterate: any[], arrayToIterate: any[],
} }
@ -200,7 +198,7 @@ export const EntryPolicySectionContainer: React.FC<EntryPolicySectionContainerPr
</CollapsibleContainer> </CollapsibleContainer>
} }
export const EntryTablePolicySection: React.FC<EntryPolicySectionProps> = ({service, title, color, response, latency, arrayToIterate}) => { export const EntryTablePolicySection: React.FC<EntryPolicySectionProps> = ({title, color, latency, arrayToIterate}) => {
return <React.Fragment> return <React.Fragment>
{ {
arrayToIterate && arrayToIterate.length > 0 ? arrayToIterate && arrayToIterate.length > 0 ?

View File

@ -33,8 +33,8 @@ const SectionsRepresentation: React.FC<any> = ({data, color}) => {
return <>{sections}</>; return <>{sections}</>;
} }
const AutoRepresentation: React.FC<any> = ({representation, rulesMatched, elapsedTime, color}) => { const AutoRepresentation: React.FC<any> = ({representation, isRulesEnabled, rulesMatched, elapsedTime, color}) => {
const TABS = [ var TABS = [
{ {
tab: 'request' tab: 'request'
}, },
@ -54,6 +54,14 @@ const AutoRepresentation: React.FC<any> = ({representation, rulesMatched, elapse
const {request, response} = JSON.parse(representation); const {request, response} = JSON.parse(representation);
if (!response) {
TABS[1]['hidden'] = true;
}
if (!isRulesEnabled) {
TABS.pop()
}
return <div className={styles.Entry}> return <div className={styles.Entry}>
{<div className={styles.body}> {<div className={styles.body}>
<div className={styles.bodyHeader}> <div className={styles.bodyHeader}>
@ -63,11 +71,11 @@ const AutoRepresentation: React.FC<any> = ({representation, rulesMatched, elapse
{currentTab === TABS[0].tab && <React.Fragment> {currentTab === TABS[0].tab && <React.Fragment>
<SectionsRepresentation data={request} color={color}/> <SectionsRepresentation data={request} color={color}/>
</React.Fragment>} </React.Fragment>}
{currentTab === TABS[1].tab && <React.Fragment> {response && currentTab === TABS[1].tab && <React.Fragment>
<SectionsRepresentation data={response} color={color}/> <SectionsRepresentation data={response} color={color}/>
</React.Fragment>} </React.Fragment>}
{currentTab === TABS[2].tab && <React.Fragment> {TABS.length > 2 && currentTab === TABS[2].tab && <React.Fragment>
<EntryTablePolicySection service={representation.service} title={'Rule'} color={color} latency={elapsedTime} response={response} arrayToIterate={rulesMatched ? rulesMatched : []}/> <EntryTablePolicySection title={'Rule'} color={color} latency={elapsedTime} arrayToIterate={rulesMatched ? rulesMatched : []}/>
</React.Fragment>} </React.Fragment>}
</div>} </div>}
</div>; </div>;
@ -75,13 +83,14 @@ const AutoRepresentation: React.FC<any> = ({representation, rulesMatched, elapse
interface Props { interface Props {
representation: any; representation: any;
isRulesEnabled: boolean;
rulesMatched: any; rulesMatched: any;
color: string; color: string;
elapsedTime: number; elapsedTime: number;
} }
const EntryViewer: React.FC<Props> = ({representation, rulesMatched, elapsedTime, color}) => { const EntryViewer: React.FC<Props> = ({representation, isRulesEnabled, rulesMatched, elapsedTime, color}) => {
return <AutoRepresentation representation={representation} rulesMatched={rulesMatched} elapsedTime={elapsedTime} color={color}/> return <AutoRepresentation representation={representation} isRulesEnabled={isRulesEnabled} rulesMatched={rulesMatched} elapsedTime={elapsedTime} color={color}/>
}; };
export default EntryViewer; export default EntryViewer;

View File

@ -36,8 +36,8 @@
border-left: 5px $failure-color solid border-left: 5px $failure-color solid
.ruleNumberText .ruleNumberText
font-size: 12px; font-size: 12px
font-style: italic; font-weight: 600
.ruleNumberTextFailure .ruleNumberTextFailure
color: #DB2156 color: #DB2156

View File

@ -68,7 +68,7 @@ export const EntryItem: React.FC<EntryProps> = ({entry, setFocusedEntryId, isSel
let rule = 'latency' in entry.rules let rule = 'latency' in entry.rules
if (rule) { if (rule) {
if (entry.rules.latency !== -1) { if (entry.rules.latency !== -1) {
if (entry.rules.latency >= entry.latency) { if (entry.rules.latency >= entry.latency || !('latency' in entry)) {
additionalRulesProperties = styles.ruleSuccessRow additionalRulesProperties = styles.ruleSuccessRow
ruleSuccess = true ruleSuccess = true
} else { } else {

View File

@ -11,13 +11,16 @@ interface FiltersProps {
setStatusFilter: (methods: Array<string>) => void; setStatusFilter: (methods: Array<string>) => void;
pathFilter: string pathFilter: string
setPathFilter: (val: string) => void; setPathFilter: (val: string) => void;
serviceFilter: string
setServiceFilter: (val: string) => void;
} }
export const Filters: React.FC<FiltersProps> = ({methodsFilter, setMethodsFilter, statusFilter, setStatusFilter, pathFilter, setPathFilter}) => { export const Filters: React.FC<FiltersProps> = ({methodsFilter, setMethodsFilter, statusFilter, setStatusFilter, pathFilter, setPathFilter, serviceFilter, setServiceFilter}) => {
return <div className={styles.container}> return <div className={styles.container}>
<MethodFilter methodsFilter={methodsFilter} setMethodsFilter={setMethodsFilter}/> <MethodFilter methodsFilter={methodsFilter} setMethodsFilter={setMethodsFilter}/>
<StatusTypesFilter statusFilter={statusFilter} setStatusFilter={setStatusFilter}/> <StatusTypesFilter statusFilter={statusFilter} setStatusFilter={setStatusFilter}/>
<ServiceFilter serviceFilter={serviceFilter} setServiceFilter={setServiceFilter}/>
<PathFilter pathFilter={pathFilter} setPathFilter={setPathFilter}/> <PathFilter pathFilter={pathFilter} setPathFilter={setPathFilter}/>
</div>; </div>;
}; };
@ -117,3 +120,18 @@ const PathFilter: React.FC<PathFilterProps> = ({pathFilter, setPathFilter}) => {
</FilterContainer>; </FilterContainer>;
}; };
interface ServiceFilterProps {
serviceFilter: string;
setServiceFilter: (val: string) => void;
}
const ServiceFilter: React.FC<ServiceFilterProps> = ({serviceFilter, setServiceFilter}) => {
return <FilterContainer>
<div className={styles.filterLabel}>Service</div>
<div>
<TextField value={serviceFilter} variant="outlined" className={styles.filterText} style={{minWidth: '150px'}} onChange={(e: any) => setServiceFilter(e.target.value)}/>
</div>
</FilterContainer>;
};

View File

@ -58,6 +58,7 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLS
const [methodsFilter, setMethodsFilter] = useState([]); const [methodsFilter, setMethodsFilter] = useState([]);
const [statusFilter, setStatusFilter] = useState([]); const [statusFilter, setStatusFilter] = useState([]);
const [pathFilter, setPathFilter] = useState(""); const [pathFilter, setPathFilter] = useState("");
const [serviceFilter, setServiceFilter] = useState("");
const [tappingStatus, setTappingStatus] = useState(null); const [tappingStatus, setTappingStatus] = useState(null);
@ -192,6 +193,8 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLS
setStatusFilter={setStatusFilter} setStatusFilter={setStatusFilter}
pathFilter={pathFilter} pathFilter={pathFilter}
setPathFilter={setPathFilter} setPathFilter={setPathFilter}
serviceFilter={serviceFilter}
setServiceFilter={setServiceFilter}
/> />
<div className={styles.container}> <div className={styles.container}>
<EntriesList entries={entries} <EntriesList entries={entries}
@ -206,6 +209,7 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLS
methodsFilter={methodsFilter} methodsFilter={methodsFilter}
statusFilter={statusFilter} statusFilter={statusFilter}
pathFilter={pathFilter} pathFilter={pathFilter}
serviceFilter={serviceFilter}
listEntryREF={listEntry} listEntryREF={listEntry}
onScrollEvent={onScrollEvent} onScrollEvent={onScrollEvent}
scrollableList={disableScrollList} scrollableList={disableScrollList}