mirror of
https://github.com/kubeshark/kubeshark.git
synced 2026-02-21 13:30:25 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5392475486 | ||
|
|
65bb262652 | ||
|
|
842d95c836 | ||
|
|
9fa9b67328 | ||
|
|
6337b75f0e | ||
|
|
b9d2e671c7 | ||
|
|
0840642c98 |
@@ -39,7 +39,7 @@ RUN go build -ldflags="-s -w \
|
||||
-X 'mizuserver/pkg/version.BuildTimestamp=${BUILD_TIMESTAMP}' \
|
||||
-X 'mizuserver/pkg/version.SemVer=${SEM_VER}'" -o mizuagent .
|
||||
|
||||
COPY build_extensions.sh ..
|
||||
COPY devops/build_extensions.sh ..
|
||||
RUN cd .. && /bin/bash build_extensions.sh
|
||||
|
||||
FROM alpine:3.13.5
|
||||
|
||||
6
Makefile
6
Makefile
@@ -44,11 +44,11 @@ push: push-docker push-cli ## Build and publish agent docker image & CLI.
|
||||
|
||||
push-docker: ## Build and publish agent docker image.
|
||||
@echo "publishing Docker image .. "
|
||||
./build-push-featurebranch.sh
|
||||
devops/build-push-featurebranch.sh
|
||||
|
||||
build-docker-ci: ## Build agent docker image for CI.
|
||||
@echo "building docker image for ci"
|
||||
./build-agent-ci.sh
|
||||
devops/build-agent-ci.sh
|
||||
|
||||
push-cli: ## Build and publish CLI.
|
||||
@echo "publishing CLI .. "
|
||||
@@ -73,7 +73,7 @@ clean-docker:
|
||||
@(echo "DOCKER cleanup - NOT IMPLEMENTED YET " )
|
||||
|
||||
extensions:
|
||||
./build_extensions.sh
|
||||
devops/build_extensions.sh
|
||||
|
||||
test-cli:
|
||||
@echo "running cli tests"; cd cli && $(MAKE) test
|
||||
|
||||
@@ -46,7 +46,7 @@ While `mizu`most often works out of the box, you can influence its behavior:
|
||||
1. [OPTIONAL] Set `KUBECONFIG` environment variable to your Kubernetes configuration. If this is not set, Mizu assumes that configuration is at `${HOME}/.kube/config`
|
||||
2. `mizu` assumes user running the command has permissions to create resources (such as pods, services, namespaces) on your Kubernetes cluster (no worries - `mizu` resources are cleaned up upon termination)
|
||||
|
||||
For detailed list of k8s permissions see [PERMISSIONS](PERMISSIONS.md) document
|
||||
For detailed list of k8s permissions see [PERMISSIONS](docs/PERMISSIONS.md) document
|
||||
|
||||
|
||||
## How to Run
|
||||
|
||||
15
TESTING.md
15
TESTING.md
@@ -1,15 +0,0 @@
|
||||

|
||||
# TESTING
|
||||
Testing guidelines for Mizu project
|
||||
|
||||
## Unit-tests
|
||||
* TBD
|
||||
* TBD
|
||||
* TBD
|
||||
|
||||
|
||||
|
||||
## System tests
|
||||
* TBD
|
||||
* TBD
|
||||
* TBD
|
||||
@@ -502,10 +502,19 @@ func TestTapRedact(t *testing.T) {
|
||||
return fmt.Errorf("failed to get entry, err: %v", requestErr)
|
||||
}
|
||||
|
||||
entry := requestResult.(map[string]interface{})["entry"].(map[string]interface{})
|
||||
entryRequest := entry["request"].(map[string]interface{})
|
||||
data := requestResult.(map[string]interface{})["data"].(map[string]interface{})
|
||||
entryJson := data["entry"].(string)
|
||||
|
||||
headers := entryRequest["headers"].([]interface{})
|
||||
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{})
|
||||
|
||||
headers := entryDetails["headers"].([]interface{})
|
||||
for _, headerInterface := range headers {
|
||||
header := headerInterface.(map[string]interface{})
|
||||
if header["name"].(string) != "User-Agent" {
|
||||
@@ -518,8 +527,8 @@ func TestTapRedact(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
data := entryRequest["postData"].(map[string]interface{})
|
||||
textDataStr := data["text"].(string)
|
||||
postData := entryDetails["postData"].(map[string]interface{})
|
||||
textDataStr := postData["text"].(string)
|
||||
|
||||
var textData map[string]string
|
||||
if parseErr := json.Unmarshal([]byte(textDataStr), &textData); parseErr != nil {
|
||||
@@ -608,10 +617,19 @@ func TestTapNoRedact(t *testing.T) {
|
||||
return fmt.Errorf("failed to get entry, err: %v", requestErr)
|
||||
}
|
||||
|
||||
entry := requestResult.(map[string]interface{})["entry"].(map[string]interface{})
|
||||
entryRequest := entry["request"].(map[string]interface{})
|
||||
data := requestResult.(map[string]interface{})["data"].(map[string]interface{})
|
||||
entryJson := data["entry"].(string)
|
||||
|
||||
headers := entryRequest["headers"].([]interface{})
|
||||
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{})
|
||||
|
||||
headers := entryDetails["headers"].([]interface{})
|
||||
for _, headerInterface := range headers {
|
||||
header := headerInterface.(map[string]interface{})
|
||||
if header["name"].(string) != "User-Agent" {
|
||||
@@ -624,8 +642,8 @@ func TestTapNoRedact(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
data := entryRequest["postData"].(map[string]interface{})
|
||||
textDataStr := data["text"].(string)
|
||||
postData := entryDetails["postData"].(map[string]interface{})
|
||||
textDataStr := postData["text"].(string)
|
||||
|
||||
var textData map[string]string
|
||||
if parseErr := json.Unmarshal([]byte(textDataStr), &textData); parseErr != nil {
|
||||
@@ -714,11 +732,20 @@ func TestTapRegexMasking(t *testing.T) {
|
||||
return fmt.Errorf("failed to get entry, err: %v", requestErr)
|
||||
}
|
||||
|
||||
entry := requestResult.(map[string]interface{})["entry"].(map[string]interface{})
|
||||
entryRequest := entry["request"].(map[string]interface{})
|
||||
data := requestResult.(map[string]interface{})["data"].(map[string]interface{})
|
||||
entryJson := data["entry"].(string)
|
||||
|
||||
data := entryRequest["postData"].(map[string]interface{})
|
||||
textData := data["text"].(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{})
|
||||
|
||||
postData := entryDetails["postData"].(map[string]interface{})
|
||||
textData := postData["text"].(string)
|
||||
|
||||
if textData != "[REDACTED]" {
|
||||
return fmt.Errorf("unexpected result - body is not redacted")
|
||||
|
||||
@@ -50,14 +50,16 @@ func main() {
|
||||
panic("One of the flags --tap, --api or --standalone or --hars-read must be provided")
|
||||
}
|
||||
|
||||
filteringOptions := getTrafficFilteringOptions()
|
||||
|
||||
if *standaloneMode {
|
||||
api.StartResolving(*namespace)
|
||||
|
||||
outputItemsChannel := make(chan *tapApi.OutputChannelItem)
|
||||
filteredOutputItemsChannel := make(chan *tapApi.OutputChannelItem)
|
||||
tap.StartPassiveTapper(tapOpts, outputItemsChannel, extensions)
|
||||
tap.StartPassiveTapper(tapOpts, outputItemsChannel, extensions, filteringOptions)
|
||||
|
||||
go filterItems(outputItemsChannel, filteredOutputItemsChannel, getTrafficFilteringOptions())
|
||||
go filterItems(outputItemsChannel, filteredOutputItemsChannel, filteringOptions)
|
||||
go api.StartReadingEntries(filteredOutputItemsChannel, nil, extensionsMap)
|
||||
// go api.StartReadingOutbound(outboundLinkOutputChannel)
|
||||
|
||||
@@ -73,9 +75,8 @@ func main() {
|
||||
rlog.Infof("Filtering for the following authorities: %v", tap.GetFilterIPs())
|
||||
}
|
||||
|
||||
// harOutputChannel, outboundLinkOutputChannel := tap.StartPassiveTapper(tapOpts)
|
||||
filteredOutputItemsChannel := make(chan *tapApi.OutputChannelItem)
|
||||
tap.StartPassiveTapper(tapOpts, filteredOutputItemsChannel, extensions)
|
||||
tap.StartPassiveTapper(tapOpts, filteredOutputItemsChannel, extensions, filteringOptions)
|
||||
socketConnection, err := shared.ConnectToSocketServer(*apiServerAddress, shared.DEFAULT_SOCKET_RETRIES, shared.DEFAULT_SOCKET_RETRY_SLEEP_TIME, false)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Error connecting to socket server at %s %v", *apiServerAddress, err))
|
||||
@@ -89,7 +90,7 @@ func main() {
|
||||
outputItemsChannel := make(chan *tapApi.OutputChannelItem)
|
||||
filteredOutputItemsChannel := make(chan *tapApi.OutputChannelItem)
|
||||
|
||||
go filterItems(outputItemsChannel, filteredOutputItemsChannel, getTrafficFilteringOptions())
|
||||
go filterItems(outputItemsChannel, filteredOutputItemsChannel, filteringOptions)
|
||||
go api.StartReadingEntries(filteredOutputItemsChannel, nil, extensionsMap)
|
||||
|
||||
hostApi(outputItemsChannel)
|
||||
@@ -97,7 +98,7 @@ func main() {
|
||||
outputItemsChannel := make(chan *tapApi.OutputChannelItem, 1000)
|
||||
filteredHarChannel := make(chan *tapApi.OutputChannelItem)
|
||||
|
||||
go filterItems(outputItemsChannel, filteredHarChannel, getTrafficFilteringOptions())
|
||||
go filterItems(outputItemsChannel, filteredHarChannel, filteringOptions)
|
||||
go api.StartReadingEntries(filteredHarChannel, harsDir, extensionsMap)
|
||||
hostApi(nil)
|
||||
}
|
||||
@@ -225,23 +226,23 @@ func getTapTargets() []string {
|
||||
return tappedAddressesPerNodeDict[nodeName]
|
||||
}
|
||||
|
||||
func getTrafficFilteringOptions() *shared.TrafficFilteringOptions {
|
||||
func getTrafficFilteringOptions() *tapApi.TrafficFilteringOptions {
|
||||
filteringOptionsJson := os.Getenv(shared.MizuFilteringOptionsEnvVar)
|
||||
if filteringOptionsJson == "" {
|
||||
return &shared.TrafficFilteringOptions{
|
||||
return &tapApi.TrafficFilteringOptions{
|
||||
HealthChecksUserAgentHeaders: []string{},
|
||||
}
|
||||
}
|
||||
var filteringOptions shared.TrafficFilteringOptions
|
||||
var filteringOptions tapApi.TrafficFilteringOptions
|
||||
err := json.Unmarshal([]byte(filteringOptionsJson), &filteringOptions)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("env var %s's value of %s is invalid! json must match the shared.TrafficFilteringOptions struct %v", shared.MizuFilteringOptionsEnvVar, filteringOptionsJson, err))
|
||||
panic(fmt.Sprintf("env var %s's value of %s is invalid! json must match the api.TrafficFilteringOptions struct %v", shared.MizuFilteringOptionsEnvVar, filteringOptionsJson, err))
|
||||
}
|
||||
|
||||
return &filteringOptions
|
||||
}
|
||||
|
||||
func filterItems(inChannel <-chan *tapApi.OutputChannelItem, outChannel chan *tapApi.OutputChannelItem, filterOptions *shared.TrafficFilteringOptions) {
|
||||
func filterItems(inChannel <-chan *tapApi.OutputChannelItem, outChannel chan *tapApi.OutputChannelItem, filterOptions *tapApi.TrafficFilteringOptions) {
|
||||
for message := range inChannel {
|
||||
if message.ConnectionInfo.IsOutgoing && api.CheckIsServiceIP(message.ConnectionInfo.ServerIP) {
|
||||
continue
|
||||
@@ -251,10 +252,6 @@ func filterItems(inChannel <-chan *tapApi.OutputChannelItem, outChannel chan *ta
|
||||
continue
|
||||
}
|
||||
|
||||
// if !filterOptions.DisableRedaction {
|
||||
// sensitiveDataFiltering.FilterSensitiveInfoFromHarRequest(message, filterOptions)
|
||||
// }
|
||||
|
||||
outChannel <- message
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +116,15 @@ func startReadingChannel(outputItems <-chan *tapApi.OutputChannelItem, extension
|
||||
baseEntry := extension.Dissector.Summarize(mizuEntry)
|
||||
mizuEntry.EstimatedSizeBytes = getEstimatedEntrySizeBytes(mizuEntry)
|
||||
database.CreateEntry(mizuEntry)
|
||||
if extension.Protocol.Name == "http" {
|
||||
var pair tapApi.RequestResponsePair
|
||||
json.Unmarshal([]byte(mizuEntry.Entry), &pair)
|
||||
harEntry, _ := utils.NewEntry(&pair)
|
||||
rules, _ := models.RunValidationRulesState(*harEntry, mizuEntry.Service)
|
||||
baseEntry.Rules = rules
|
||||
baseEntry.Latency = mizuEntry.ElapsedTime
|
||||
}
|
||||
|
||||
baseEntryBytes, _ := models.CreateBaseEntryWebSocketMessage(baseEntry)
|
||||
BroadcastToBrowserClients(baseEntryBytes)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package controllers
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/google/martian/har"
|
||||
"mizuserver/pkg/database"
|
||||
"mizuserver/pkg/models"
|
||||
"mizuserver/pkg/providers"
|
||||
@@ -13,6 +12,8 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/google/martian/har"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/romana/rlog"
|
||||
|
||||
@@ -140,11 +141,23 @@ func GetEntry(c *gin.Context) {
|
||||
|
||||
extension := extensionsMap[entryData.ProtocolName]
|
||||
protocol, representation, bodySize, _ := extension.Dissector.Represent(&entryData)
|
||||
|
||||
var rules []map[string]interface{}
|
||||
if entryData.ProtocolName == "http" {
|
||||
var pair tapApi.RequestResponsePair
|
||||
json.Unmarshal([]byte(entryData.Entry), &pair)
|
||||
harEntry, _ := utils.NewEntry(&pair)
|
||||
_, rulesMatched := models.RunValidationRulesState(*harEntry, entryData.Service)
|
||||
inrec, _ := json.Marshal(rulesMatched)
|
||||
json.Unmarshal(inrec, &rules)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, tapApi.MizuEntryWrapper{
|
||||
Protocol: protocol,
|
||||
Representation: string(representation),
|
||||
BodySize: bodySize,
|
||||
Data: entryData,
|
||||
Rules: rules,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@ package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
tapApi "github.com/up9inc/mizu/tap/api"
|
||||
|
||||
"mizuserver/pkg/rules"
|
||||
"mizuserver/pkg/utils"
|
||||
|
||||
"github.com/google/martian/har"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
@@ -15,15 +16,6 @@ func GetEntry(r *tapApi.MizuEntry, v tapApi.DataUnmarshaler) error {
|
||||
return v.UnmarshalData(r)
|
||||
}
|
||||
|
||||
// TODO: until we fixed the Rules feature
|
||||
//func NewApplicableRules(status bool, latency int64, number int) tapApi.ApplicableRules {
|
||||
// ar := tapApi.ApplicableRules{}
|
||||
// ar.Status = status
|
||||
// ar.Latency = latency
|
||||
// ar.NumberOfRules = number
|
||||
// return ar
|
||||
//}
|
||||
|
||||
type EntriesFilter struct {
|
||||
Limit int `form:"limit" validate:"required,min=1,max=200"`
|
||||
Operator string `form:"operator" validate:"required,oneof='lt' 'gt'"`
|
||||
@@ -105,33 +97,8 @@ type ExtendedCreator struct {
|
||||
Source *string `json:"_source"`
|
||||
}
|
||||
|
||||
type FullEntryWithPolicy struct {
|
||||
RulesMatched []rules.RulesMatched `json:"rulesMatched,omitempty"`
|
||||
Entry har.Entry `json:"entry"`
|
||||
Service string `json:"service"`
|
||||
func RunValidationRulesState(harEntry har.Entry, service string) (tapApi.ApplicableRules, []rules.RulesMatched) {
|
||||
resultPolicyToSend := rules.MatchRequestPolicy(harEntry, service)
|
||||
statusPolicyToSend, latency, numberOfRules := rules.PassedValidationRules(resultPolicyToSend)
|
||||
return tapApi.ApplicableRules{Status: statusPolicyToSend, Latency: latency, NumberOfRules: numberOfRules}, resultPolicyToSend
|
||||
}
|
||||
|
||||
func (fewp *FullEntryWithPolicy) UnmarshalData(entry *tapApi.MizuEntry) error {
|
||||
var pair tapApi.RequestResponsePair
|
||||
if err := json.Unmarshal([]byte(entry.Entry), &pair); err != nil {
|
||||
return err
|
||||
}
|
||||
harEntry, err := utils.NewEntry(&pair)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fewp.Entry = *harEntry
|
||||
|
||||
_, resultPolicyToSend := rules.MatchRequestPolicy(fewp.Entry, entry.Service)
|
||||
fewp.RulesMatched = resultPolicyToSend
|
||||
fewp.Service = entry.Service
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: until we fixed the Rules feature
|
||||
//func RunValidationRulesState(harEntry har.Entry, service string) tapApi.ApplicableRules {
|
||||
// numberOfRules, resultPolicyToSend := rules.MatchRequestPolicy(harEntry, service)
|
||||
// statusPolicyToSend, latency, numberOfRules := rules.PassedValidationRules(resultPolicyToSend, numberOfRules)
|
||||
// ar := NewApplicableRules(statusPolicyToSend, latency, numberOfRules)
|
||||
// return ar
|
||||
//}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
@@ -41,7 +42,7 @@ func ValidateService(serviceFromRule string, service string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func MatchRequestPolicy(harEntry har.Entry, service string) (int, []RulesMatched) {
|
||||
func MatchRequestPolicy(harEntry har.Entry, service string) []RulesMatched {
|
||||
enforcePolicy, _ := shared.DecodeEnforcePolicy(fmt.Sprintf("%s/%s", shared.RulePolicyPath, shared.RulePolicyFileName))
|
||||
var resultPolicyToSend []RulesMatched
|
||||
for _, rule := range enforcePolicy.Rules {
|
||||
@@ -50,7 +51,8 @@ func MatchRequestPolicy(harEntry har.Entry, service string) (int, []RulesMatched
|
||||
}
|
||||
if rule.Type == "json" {
|
||||
var bodyJsonMap interface{}
|
||||
if err := json.Unmarshal(harEntry.Response.Content.Text, &bodyJsonMap); err != nil {
|
||||
contentTextDecoded, _ := base64.StdEncoding.DecodeString(string(harEntry.Response.Content.Text))
|
||||
if err := json.Unmarshal(contentTextDecoded, &bodyJsonMap); err != nil {
|
||||
continue
|
||||
}
|
||||
out, err := jsonpath.Read(bodyJsonMap, rule.Key)
|
||||
@@ -63,6 +65,7 @@ func MatchRequestPolicy(harEntry har.Entry, service string) (int, []RulesMatched
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
fmt.Println(matchValue, rule.Value)
|
||||
} else {
|
||||
val := fmt.Sprint(out)
|
||||
matchValue, err = regexp.MatchString(rule.Value, val)
|
||||
@@ -89,22 +92,28 @@ func MatchRequestPolicy(harEntry har.Entry, service string) (int, []RulesMatched
|
||||
resultPolicyToSend = appendRulesMatched(resultPolicyToSend, true, rule)
|
||||
}
|
||||
}
|
||||
return len(enforcePolicy.Rules), resultPolicyToSend
|
||||
return resultPolicyToSend
|
||||
}
|
||||
|
||||
func PassedValidationRules(rulesMatched []RulesMatched, numberOfRules int) (bool, int64, int) {
|
||||
if len(rulesMatched) == 0 {
|
||||
return false, 0, 0
|
||||
func PassedValidationRules(rulesMatched []RulesMatched) (bool, int64, int) {
|
||||
var numberOfRulesMatched = len(rulesMatched)
|
||||
var latency int64 = -1
|
||||
|
||||
if numberOfRulesMatched == 0 {
|
||||
return false, 0, numberOfRulesMatched
|
||||
}
|
||||
|
||||
for _, rule := range rulesMatched {
|
||||
if rule.Matched == false {
|
||||
return false, -1, len(rulesMatched)
|
||||
return false, latency, numberOfRulesMatched
|
||||
} else {
|
||||
if strings.ToLower(rule.Rule.Type) == "latency" {
|
||||
if rule.Rule.Latency < latency || latency == -1 {
|
||||
latency = rule.Rule.Latency
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, rule := range rulesMatched {
|
||||
if strings.ToLower(rule.Rule.Type) == "latency" {
|
||||
return true, rule.Rule.Latency, len(rulesMatched)
|
||||
}
|
||||
}
|
||||
return true, -1, len(rulesMatched)
|
||||
|
||||
return true, latency, numberOfRulesMatched
|
||||
}
|
||||
@@ -2,11 +2,12 @@ package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"github.com/up9inc/mizu/cli/config"
|
||||
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||
"github.com/up9inc/mizu/cli/logger"
|
||||
"github.com/up9inc/mizu/cli/telemetry"
|
||||
"os"
|
||||
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/up9inc/mizu/cli/uiUtils"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/shared/debounce"
|
||||
"github.com/up9inc/mizu/tap/api"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
core "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
@@ -108,7 +109,7 @@ func RunMizuTap() {
|
||||
}
|
||||
|
||||
go goUtils.HandleExcWrapper(watchApiServerPod, ctx, kubernetesProvider, cancel)
|
||||
go goUtils.HandleExcWrapper(watchPodsForTapping, ctx, kubernetesProvider, targetNamespaces, cancel)
|
||||
go goUtils.HandleExcWrapper(watchPodsForTapping, ctx, kubernetesProvider, targetNamespaces, cancel, mizuApiFilteringOptions)
|
||||
|
||||
//block until exit signal or error
|
||||
waitForFinish(ctx, cancel)
|
||||
@@ -123,7 +124,7 @@ func readValidationRules(file string) (string, error) {
|
||||
return string(newContent), nil
|
||||
}
|
||||
|
||||
func createMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string, mizuApiFilteringOptions *shared.TrafficFilteringOptions, mizuValidationRules string) error {
|
||||
func createMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string, mizuApiFilteringOptions *api.TrafficFilteringOptions, mizuValidationRules string) error {
|
||||
if !config.Config.IsNsRestrictedMode() {
|
||||
if err := createMizuNamespace(ctx, kubernetesProvider); err != nil {
|
||||
return err
|
||||
@@ -134,7 +135,7 @@ func createMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Pro
|
||||
return err
|
||||
}
|
||||
|
||||
if err := updateMizuTappers(ctx, kubernetesProvider, nodeToTappedPodIPMap); err != nil {
|
||||
if err := updateMizuTappers(ctx, kubernetesProvider, nodeToTappedPodIPMap, mizuApiFilteringOptions); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -158,7 +159,7 @@ func createMizuNamespace(ctx context.Context, kubernetesProvider *kubernetes.Pro
|
||||
return err
|
||||
}
|
||||
|
||||
func createMizuApiServer(ctx context.Context, kubernetesProvider *kubernetes.Provider, mizuApiFilteringOptions *shared.TrafficFilteringOptions) error {
|
||||
func createMizuApiServer(ctx context.Context, kubernetesProvider *kubernetes.Provider, mizuApiFilteringOptions *api.TrafficFilteringOptions) error {
|
||||
var err error
|
||||
|
||||
state.mizuServiceAccountExists, err = createRBACIfNecessary(ctx, kubernetesProvider)
|
||||
@@ -199,13 +200,13 @@ func createMizuApiServer(ctx context.Context, kubernetesProvider *kubernetes.Pro
|
||||
return nil
|
||||
}
|
||||
|
||||
func getMizuApiFilteringOptions() (*shared.TrafficFilteringOptions, error) {
|
||||
var compiledRegexSlice []*shared.SerializableRegexp
|
||||
func getMizuApiFilteringOptions() (*api.TrafficFilteringOptions, error) {
|
||||
var compiledRegexSlice []*api.SerializableRegexp
|
||||
|
||||
if config.Config.Tap.PlainTextFilterRegexes != nil && len(config.Config.Tap.PlainTextFilterRegexes) > 0 {
|
||||
compiledRegexSlice = make([]*shared.SerializableRegexp, 0)
|
||||
compiledRegexSlice = make([]*api.SerializableRegexp, 0)
|
||||
for _, regexStr := range config.Config.Tap.PlainTextFilterRegexes {
|
||||
compiledRegex, err := shared.CompileRegexToSerializableRegexp(regexStr)
|
||||
compiledRegex, err := api.CompileRegexToSerializableRegexp(regexStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -213,14 +214,14 @@ func getMizuApiFilteringOptions() (*shared.TrafficFilteringOptions, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return &shared.TrafficFilteringOptions{
|
||||
return &api.TrafficFilteringOptions{
|
||||
PlainTextMaskingRegexes: compiledRegexSlice,
|
||||
HealthChecksUserAgentHeaders: config.Config.Tap.HealthChecksUserAgentHeaders,
|
||||
DisableRedaction: config.Config.Tap.DisableRedaction,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func updateMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string) error {
|
||||
func updateMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string, mizuApiFilteringOptions *api.TrafficFilteringOptions) error {
|
||||
if len(nodeToTappedPodIPMap) > 0 {
|
||||
var serviceAccountName string
|
||||
if state.mizuServiceAccountExists {
|
||||
@@ -240,6 +241,7 @@ func updateMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provi
|
||||
serviceAccountName,
|
||||
config.Config.Tap.TapperResources,
|
||||
config.Config.ImagePullPolicy(),
|
||||
mizuApiFilteringOptions,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -346,7 +348,7 @@ func waitUntilNamespaceDeleted(ctx context.Context, cancel context.CancelFunc, k
|
||||
}
|
||||
}
|
||||
|
||||
func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Provider, targetNamespaces []string, cancel context.CancelFunc) {
|
||||
func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Provider, targetNamespaces []string, cancel context.CancelFunc, mizuApiFilteringOptions *api.TrafficFilteringOptions) {
|
||||
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider, targetNamespaces, config.Config.Tap.PodRegex())
|
||||
|
||||
restartTappers := func() {
|
||||
@@ -370,7 +372,7 @@ func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Pro
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error building node to ips map: %v", errormessage.FormatError(err)))
|
||||
cancel()
|
||||
}
|
||||
if err := updateMizuTappers(ctx, kubernetesProvider, nodeToTappedPodIPMap); err != nil {
|
||||
if err := updateMizuTappers(ctx, kubernetesProvider, nodeToTappedPodIPMap, mizuApiFilteringOptions); err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error updating daemonset: %v", errormessage.FormatError(err)))
|
||||
cancel()
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ require (
|
||||
github.com/spf13/cobra v1.1.3
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/up9inc/mizu/shared v0.0.0
|
||||
github.com/up9inc/mizu/tap/api v0.0.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
k8s.io/api v0.21.2
|
||||
k8s.io/apimachinery v0.21.2
|
||||
@@ -19,3 +20,5 @@ require (
|
||||
)
|
||||
|
||||
replace github.com/up9inc/mizu/shared v0.0.0 => ../shared
|
||||
|
||||
replace github.com/up9inc/mizu/tap/api v0.0.0 => ../tap/api
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/tap/api"
|
||||
core "k8s.io/api/core/v1"
|
||||
rbac "k8s.io/api/rbac/v1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -150,7 +151,7 @@ type ApiServerOptions struct {
|
||||
PodImage string
|
||||
ServiceAccountName string
|
||||
IsNamespaceRestricted bool
|
||||
MizuApiFilteringOptions *shared.TrafficFilteringOptions
|
||||
MizuApiFilteringOptions *api.TrafficFilteringOptions
|
||||
MaxEntriesDBSizeBytes int64
|
||||
Resources configStructs.Resources
|
||||
ImagePullPolicy core.PullPolicy
|
||||
@@ -575,7 +576,7 @@ func (provider *Provider) CreateConfigMap(ctx context.Context, namespace string,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespace string, daemonSetName string, podImage string, tapperPodName string, apiServerPodIp string, nodeToTappedPodIPMap map[string][]string, serviceAccountName string, resources configStructs.Resources, imagePullPolicy core.PullPolicy) error {
|
||||
func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespace string, daemonSetName string, podImage string, tapperPodName string, apiServerPodIp string, nodeToTappedPodIPMap map[string][]string, serviceAccountName string, resources configStructs.Resources, imagePullPolicy core.PullPolicy, mizuApiFilteringOptions *api.TrafficFilteringOptions) error {
|
||||
logger.Log.Debugf("Applying %d tapper daemon sets, ns: %s, daemonSetName: %s, podImage: %s, tapperPodName: %s", len(nodeToTappedPodIPMap), namespace, daemonSetName, podImage, tapperPodName)
|
||||
|
||||
if len(nodeToTappedPodIPMap) == 0 {
|
||||
@@ -587,6 +588,11 @@ func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespac
|
||||
return err
|
||||
}
|
||||
|
||||
marshaledFilteringOptions, err := json.Marshal(mizuApiFilteringOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mizuCmd := []string{
|
||||
"./mizuagent",
|
||||
"-i", "any",
|
||||
@@ -605,6 +611,7 @@ func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespac
|
||||
applyconfcore.EnvVar().WithName(shared.HostModeEnvVar).WithValue("1"),
|
||||
applyconfcore.EnvVar().WithName(shared.TappedAddressesPerNodeDictEnvVar).WithValue(string(nodeToTappedPodIPMapJsonStr)),
|
||||
applyconfcore.EnvVar().WithName(shared.GoGCEnvVar).WithValue("12800"),
|
||||
applyconfcore.EnvVar().WithName(shared.MizuFilteringOptionsEnvVar).WithValue(string(marshaledFilteringOptions)),
|
||||
)
|
||||
agentContainer.WithEnv(
|
||||
applyconfcore.EnvVar().WithName(shared.NodeNameEnvVar).WithValueFrom(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# Contributing to Mizu
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
# Kubernetes permissions for MIZU
|
||||
|
||||
This document describes in details all permissions required for full and correct operation of Mizu
|
||||
33
docs/TESTING.md
Normal file
33
docs/TESTING.md
Normal file
@@ -0,0 +1,33 @@
|
||||

|
||||
# Testing guidelines
|
||||
|
||||
## Generic guidelines
|
||||
* Use "[testing](https://pkg.go.dev/testing)" package
|
||||
* Write [Table-driven tests using subtests](https://go.dev/blog/subtests)
|
||||
* Use cleanup in test/subtest in order to clean up resources
|
||||
* Name the test func "Test<tested_func_name><tested_case>"
|
||||
|
||||
## Unit tests
|
||||
* Position the test file inside the folder of the tested package
|
||||
* In case of internal func testing
|
||||
* Name the test file "<tested_file_name>_internal_test.go"
|
||||
* Name the test package same as the package being tested
|
||||
* Example - [Config](../cli/config/config_internal_test.go)
|
||||
* In case of exported func testing
|
||||
* Name the test file "<tested_file_name>_test.go"
|
||||
* Name the test package "<tested_package>_test"
|
||||
* Example - [Slice Utils](../cli/mizu/sliceUtils_test.go)
|
||||
* Make sure to run test coverage to make sure you covered all the cases and lines in the func
|
||||
|
||||
## Acceptance tests
|
||||
* Position the test file inside the [acceptance tests folder](../acceptanceTests)
|
||||
* Name the file "<tested_command>_test.go"
|
||||
* Name the package "acceptanceTests"
|
||||
* Do not run as part of the short tests
|
||||
* Use/Create generic test utils func in acceptanceTests/testsUtils
|
||||
* Don't use sleep inside the tests - active check
|
||||
* Running acceptance tests locally
|
||||
* Switch to the branch that is being tested
|
||||
* Run acceptanceTests/setup.sh
|
||||
* Run tests (make acceptance-test)
|
||||
* Example - [Tap](../acceptanceTests/tap_test.go)
|
||||
@@ -74,12 +74,6 @@ func CreateWebSocketMessageTypeAnalyzeStatus(analyzeStatus AnalyzeStatus) WebSoc
|
||||
}
|
||||
}
|
||||
|
||||
type TrafficFilteringOptions struct {
|
||||
HealthChecksUserAgentHeaders []string
|
||||
PlainTextMaskingRegexes []*SerializableRegexp
|
||||
DisableRedaction bool
|
||||
}
|
||||
|
||||
type VersionResponse struct {
|
||||
SemVer string `json:"semver"`
|
||||
}
|
||||
@@ -99,6 +93,11 @@ type RulePolicy struct {
|
||||
Name string `yaml:"name"`
|
||||
}
|
||||
|
||||
type RulesMatched struct {
|
||||
Matched bool `json:"matched"`
|
||||
Rule RulePolicy `json:"rule"`
|
||||
}
|
||||
|
||||
func (r *RulePolicy) validateType() bool {
|
||||
permitedTypes := []string{"json", "header", "latency"}
|
||||
_, found := Find(permitedTypes, r.Type)
|
||||
|
||||
@@ -80,7 +80,7 @@ type SuperIdentifier struct {
|
||||
type Dissector interface {
|
||||
Register(*Extension)
|
||||
Ping()
|
||||
Dissect(b *bufio.Reader, isClient bool, tcpID *TcpID, counterPair *CounterPair, superTimer *SuperTimer, superIdentifier *SuperIdentifier, emitter Emitter) error
|
||||
Dissect(b *bufio.Reader, isClient bool, tcpID *TcpID, counterPair *CounterPair, superTimer *SuperTimer, superIdentifier *SuperIdentifier, emitter Emitter, options *TrafficFilteringOptions) error
|
||||
Analyze(item *OutputChannelItem, entryId string, resolvedSource string, resolvedDestination string) *MizuEntry
|
||||
Summarize(entry *MizuEntry) *BaseEntryDetails
|
||||
Represent(entry *MizuEntry) (protocol Protocol, object []byte, bodySize int64, err error)
|
||||
@@ -133,10 +133,11 @@ type MizuEntry struct {
|
||||
}
|
||||
|
||||
type MizuEntryWrapper struct {
|
||||
Protocol Protocol `json:"protocol"`
|
||||
Representation string `json:"representation"`
|
||||
BodySize int64 `json:"bodySize"`
|
||||
Data MizuEntry `json:"data"`
|
||||
Protocol Protocol `json:"protocol"`
|
||||
Representation string `json:"representation"`
|
||||
BodySize int64 `json:"bodySize"`
|
||||
Data MizuEntry `json:"data"`
|
||||
Rules []map[string]interface{} `json:"rulesMatched,omitempty"`
|
||||
}
|
||||
|
||||
type BaseEntryDetails struct {
|
||||
|
||||
7
tap/api/options.go
Normal file
7
tap/api/options.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package api
|
||||
|
||||
type TrafficFilteringOptions struct {
|
||||
HealthChecksUserAgentHeaders []string
|
||||
PlainTextMaskingRegexes []*SerializableRegexp
|
||||
DisableRedaction bool
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package shared
|
||||
package api
|
||||
|
||||
import "regexp"
|
||||
|
||||
@@ -41,7 +41,7 @@ func (d dissecting) Ping() {
|
||||
|
||||
const amqpRequest string = "amqp_request"
|
||||
|
||||
func (d dissecting) Dissect(b *bufio.Reader, isClient bool, tcpID *api.TcpID, counterPair *api.CounterPair, superTimer *api.SuperTimer, superIdentifier *api.SuperIdentifier, emitter api.Emitter) error {
|
||||
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 {
|
||||
r := AmqpReader{b}
|
||||
|
||||
var remaining int
|
||||
|
||||
@@ -3,6 +3,7 @@ module github.com/up9inc/mizu/tap/extensions/http
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/beevik/etree v1.1.0 // indirect
|
||||
github.com/google/martian v2.1.0+incompatible
|
||||
github.com/romana/rlog v0.0.0-20171115192701-f018bc92e7d7
|
||||
github.com/up9inc/mizu/tap/api v0.0.0
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
||||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/romana/rlog v0.0.0-20171115192701-f018bc92e7d7 h1:jkvpcEatpwuMF5O5LVxTnehj6YZ/aEZN4NWD/Xml4pI=
|
||||
|
||||
@@ -13,7 +13,14 @@ import (
|
||||
"github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
func handleHTTP2Stream(grpcAssembler *GrpcAssembler, tcpID *api.TcpID, superTimer *api.SuperTimer, emitter api.Emitter) error {
|
||||
func filterAndEmit(item *api.OutputChannelItem, emitter api.Emitter, options *api.TrafficFilteringOptions) {
|
||||
if !options.DisableRedaction {
|
||||
FilterSensitiveData(item, options)
|
||||
}
|
||||
emitter.Emit(item)
|
||||
}
|
||||
|
||||
func handleHTTP2Stream(grpcAssembler *GrpcAssembler, tcpID *api.TcpID, superTimer *api.SuperTimer, emitter api.Emitter, options *api.TrafficFilteringOptions) error {
|
||||
streamID, messageHTTP1, err := grpcAssembler.readMessage()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -64,13 +71,13 @@ func handleHTTP2Stream(grpcAssembler *GrpcAssembler, tcpID *api.TcpID, superTime
|
||||
|
||||
if item != nil {
|
||||
item.Protocol = http2Protocol
|
||||
emitter.Emit(item)
|
||||
filterAndEmit(item, emitter, options)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleHTTP1ClientStream(b *bufio.Reader, tcpID *api.TcpID, counterPair *api.CounterPair, superTimer *api.SuperTimer, emitter api.Emitter) error {
|
||||
func handleHTTP1ClientStream(b *bufio.Reader, tcpID *api.TcpID, counterPair *api.CounterPair, superTimer *api.SuperTimer, emitter api.Emitter, options *api.TrafficFilteringOptions) error {
|
||||
req, err := http.ReadRequest(b)
|
||||
if err != nil {
|
||||
// log.Println("Error reading stream:", err)
|
||||
@@ -107,12 +114,12 @@ func handleHTTP1ClientStream(b *bufio.Reader, tcpID *api.TcpID, counterPair *api
|
||||
ServerPort: tcpID.DstPort,
|
||||
IsOutgoing: true,
|
||||
}
|
||||
emitter.Emit(item)
|
||||
filterAndEmit(item, emitter, options)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleHTTP1ServerStream(b *bufio.Reader, tcpID *api.TcpID, counterPair *api.CounterPair, superTimer *api.SuperTimer, emitter api.Emitter) error {
|
||||
func handleHTTP1ServerStream(b *bufio.Reader, tcpID *api.TcpID, counterPair *api.CounterPair, superTimer *api.SuperTimer, emitter api.Emitter, options *api.TrafficFilteringOptions) error {
|
||||
res, err := http.ReadResponse(b, nil)
|
||||
if err != nil {
|
||||
// log.Println("Error reading stream:", err)
|
||||
@@ -157,7 +164,7 @@ func handleHTTP1ServerStream(b *bufio.Reader, tcpID *api.TcpID, counterPair *api
|
||||
ServerPort: tcpID.SrcPort,
|
||||
IsOutgoing: false,
|
||||
}
|
||||
emitter.Emit(item)
|
||||
filterAndEmit(item, emitter, options)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ 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) error {
|
||||
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 {
|
||||
ident := fmt.Sprintf("%s->%s:%s->%s", tcpID.SrcIP, tcpID.DstIP, tcpID.SrcPort, tcpID.DstPort)
|
||||
isHTTP2, err := checkIsHTTP2Connection(b, isClient)
|
||||
if err != nil {
|
||||
@@ -85,7 +85,7 @@ func (d dissecting) Dissect(b *bufio.Reader, isClient bool, tcpID *api.TcpID, co
|
||||
}
|
||||
|
||||
if isHTTP2 {
|
||||
err = handleHTTP2Stream(grpcAssembler, tcpID, superTimer, emitter)
|
||||
err = handleHTTP2Stream(grpcAssembler, tcpID, superTimer, emitter, options)
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
@@ -94,7 +94,7 @@ func (d dissecting) Dissect(b *bufio.Reader, isClient bool, tcpID *api.TcpID, co
|
||||
}
|
||||
dissected = true
|
||||
} else if isClient {
|
||||
err = handleHTTP1ClientStream(b, tcpID, counterPair, superTimer, emitter)
|
||||
err = handleHTTP1ClientStream(b, tcpID, counterPair, superTimer, emitter, options)
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
@@ -103,7 +103,7 @@ func (d dissecting) Dissect(b *bufio.Reader, isClient bool, tcpID *api.TcpID, co
|
||||
}
|
||||
dissected = true
|
||||
} else {
|
||||
err = handleHTTP1ServerStream(b, tcpID, counterPair, superTimer, emitter)
|
||||
err = handleHTTP1ServerStream(b, tcpID, counterPair, superTimer, emitter, options)
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
|
||||
209
tap/extensions/http/sensitive_data_cleaner.go
Normal file
209
tap/extensions/http/sensitive_data_cleaner.go
Normal file
@@ -0,0 +1,209 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
"github.com/romana/rlog"
|
||||
"github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
const maskedFieldPlaceholderValue = "[REDACTED]"
|
||||
|
||||
//these values MUST be all lower case and contain no `-` or `_` characters
|
||||
var personallyIdentifiableDataFields = []string{"token", "authorization", "authentication", "cookie", "userid", "password",
|
||||
"username", "user", "key", "passcode", "pass", "auth", "authtoken", "jwt",
|
||||
"bearer", "clientid", "clientsecret", "redirecturi", "phonenumber",
|
||||
"zip", "zipcode", "address", "country", "firstname", "lastname",
|
||||
"middlename", "fname", "lname", "birthdate"}
|
||||
|
||||
func FilterSensitiveData(item *api.OutputChannelItem, options *api.TrafficFilteringOptions) {
|
||||
request := item.Pair.Request.Payload.(HTTPPayload).Data.(*http.Request)
|
||||
response := item.Pair.Response.Payload.(HTTPPayload).Data.(*http.Response)
|
||||
|
||||
filterHeaders(&request.Header)
|
||||
filterHeaders(&response.Header)
|
||||
filterUrl(request.URL)
|
||||
filterRequestBody(request, options)
|
||||
filterResponseBody(response, options)
|
||||
}
|
||||
|
||||
func filterRequestBody(request *http.Request, options *api.TrafficFilteringOptions) {
|
||||
contenType := getContentTypeHeaderValue(request.Header)
|
||||
body, err := ioutil.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
rlog.Debugf("Filtering error reading body: %v", err)
|
||||
return
|
||||
}
|
||||
filteredBody, err := filterHttpBody([]byte(body), contenType, options)
|
||||
if err == nil {
|
||||
request.Body = ioutil.NopCloser(bytes.NewBuffer(filteredBody))
|
||||
} else {
|
||||
request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
||||
}
|
||||
}
|
||||
|
||||
func filterResponseBody(response *http.Response, options *api.TrafficFilteringOptions) {
|
||||
contentType := getContentTypeHeaderValue(response.Header)
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
rlog.Debugf("Filtering error reading body: %v", err)
|
||||
return
|
||||
}
|
||||
filteredBody, err := filterHttpBody([]byte(body), contentType, options)
|
||||
if err == nil {
|
||||
response.Body = ioutil.NopCloser(bytes.NewBuffer(filteredBody))
|
||||
} else {
|
||||
response.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
||||
}
|
||||
}
|
||||
|
||||
func filterHeaders(headers *http.Header) {
|
||||
for key, _ := range *headers {
|
||||
if strings.ToLower(key) == "cookie" {
|
||||
headers.Del(key)
|
||||
} else if isFieldNameSensitive(key) {
|
||||
headers.Set(key, maskedFieldPlaceholderValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getContentTypeHeaderValue(headers http.Header) string {
|
||||
for key, _ := range headers {
|
||||
if strings.ToLower(key) == "content-type" {
|
||||
return headers.Get(key)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func isFieldNameSensitive(fieldName string) bool {
|
||||
name := strings.ToLower(fieldName)
|
||||
name = strings.ReplaceAll(name, "_", "")
|
||||
name = strings.ReplaceAll(name, "-", "")
|
||||
name = strings.ReplaceAll(name, " ", "")
|
||||
|
||||
for _, sensitiveField := range personallyIdentifiableDataFields {
|
||||
if strings.Contains(name, sensitiveField) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func filterHttpBody(bytes []byte, contentType string, options *api.TrafficFilteringOptions) ([]byte, error) {
|
||||
mimeType := strings.Split(contentType, ";")[0]
|
||||
switch strings.ToLower(mimeType) {
|
||||
case "application/json":
|
||||
return filterJsonBody(bytes)
|
||||
case "text/html":
|
||||
fallthrough
|
||||
case "application/xhtml+xml":
|
||||
fallthrough
|
||||
case "text/xml":
|
||||
fallthrough
|
||||
case "application/xml":
|
||||
return filterXmlEtree(bytes)
|
||||
case "text/plain":
|
||||
if options != nil && options.PlainTextMaskingRegexes != nil {
|
||||
return filterPlainText(bytes, options), nil
|
||||
}
|
||||
}
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
func filterPlainText(bytes []byte, options *api.TrafficFilteringOptions) []byte {
|
||||
for _, regex := range options.PlainTextMaskingRegexes {
|
||||
bytes = regex.ReplaceAll(bytes, []byte(maskedFieldPlaceholderValue))
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
func filterXmlEtree(bytes []byte) ([]byte, error) {
|
||||
if !IsValidXML(bytes) {
|
||||
return nil, errors.New("Invalid XML")
|
||||
}
|
||||
xmlDoc := etree.NewDocument()
|
||||
err := xmlDoc.ReadFromBytes(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
filterXmlElement(xmlDoc.Root())
|
||||
}
|
||||
return xmlDoc.WriteToBytes()
|
||||
}
|
||||
|
||||
func IsValidXML(data []byte) bool {
|
||||
return xml.Unmarshal(data, new(interface{})) == nil
|
||||
}
|
||||
|
||||
func filterXmlElement(element *etree.Element) {
|
||||
for i, attribute := range element.Attr {
|
||||
if isFieldNameSensitive(attribute.Key) {
|
||||
element.Attr[i].Value = maskedFieldPlaceholderValue
|
||||
}
|
||||
}
|
||||
if element.ChildElements() == nil || len(element.ChildElements()) == 0 {
|
||||
if isFieldNameSensitive(element.Tag) {
|
||||
element.SetText(maskedFieldPlaceholderValue)
|
||||
}
|
||||
} else {
|
||||
for _, element := range element.ChildElements() {
|
||||
filterXmlElement(element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func filterJsonBody(bytes []byte) ([]byte, error) {
|
||||
var bodyJsonMap map[string]interface{}
|
||||
err := json.Unmarshal(bytes, &bodyJsonMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filterJsonMap(bodyJsonMap)
|
||||
return json.Marshal(bodyJsonMap)
|
||||
}
|
||||
|
||||
func filterJsonMap(jsonMap map[string]interface{}) {
|
||||
for key, value := range jsonMap {
|
||||
// Do not replace nil values with maskedFieldPlaceholderValue
|
||||
if value == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
nestedMap, isNested := value.(map[string]interface{})
|
||||
if isNested {
|
||||
filterJsonMap(nestedMap)
|
||||
} else {
|
||||
if isFieldNameSensitive(key) {
|
||||
jsonMap[key] = maskedFieldPlaceholderValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func filterUrl(url *url.URL) {
|
||||
if len(url.RawQuery) > 0 {
|
||||
newQueryArgs := make([]string, 0)
|
||||
for urlQueryParamName, urlQueryParamValues := range url.Query() {
|
||||
newValues := urlQueryParamValues
|
||||
if isFieldNameSensitive(urlQueryParamName) {
|
||||
newValues = []string{maskedFieldPlaceholderValue}
|
||||
}
|
||||
for _, paramValue := range newValues {
|
||||
newQueryArgs = append(newQueryArgs, fmt.Sprintf("%s=%s", urlQueryParamName, paramValue))
|
||||
}
|
||||
}
|
||||
|
||||
url.RawQuery = strings.Join(newQueryArgs, "&")
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ 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) error {
|
||||
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 {
|
||||
for {
|
||||
if superIdentifier.Protocol != nil && superIdentifier.Protocol != &_protocol {
|
||||
return errors.New("Identified by another protocol")
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
_debug "runtime/debug"
|
||||
"runtime/pprof"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -92,9 +93,10 @@ var outputLevel int
|
||||
var errorsMap map[string]uint
|
||||
var errorsMapMutex sync.Mutex
|
||||
var nErrors uint
|
||||
var ownIps []string // global
|
||||
var hostMode bool // global
|
||||
var extensions []*api.Extension // global
|
||||
var ownIps []string // global
|
||||
var hostMode bool // global
|
||||
var extensions []*api.Extension // global
|
||||
var filteringOptions *api.TrafficFilteringOptions // global
|
||||
|
||||
const baseStreamChannelTimeoutMs int = 5000 * 100
|
||||
|
||||
@@ -160,9 +162,10 @@ func (c *Context) GetCaptureInfo() gopacket.CaptureInfo {
|
||||
return c.CaptureInfo
|
||||
}
|
||||
|
||||
func StartPassiveTapper(opts *TapOpts, outputItems chan *api.OutputChannelItem, extensionsRef []*api.Extension) {
|
||||
func StartPassiveTapper(opts *TapOpts, outputItems chan *api.OutputChannelItem, extensionsRef []*api.Extension, options *api.TrafficFilteringOptions) {
|
||||
hostMode = opts.HostMode
|
||||
extensions = extensionsRef
|
||||
filteringOptions = options
|
||||
|
||||
if GetMemoryProfilingEnabled() {
|
||||
startMemoryProfiler()
|
||||
@@ -215,10 +218,10 @@ func startMemoryProfiler() {
|
||||
}
|
||||
|
||||
func closeTimedoutTcpStreamChannels() {
|
||||
maxNumberOfGoroutines = GetMaxNumberOfGoroutines()
|
||||
TcpStreamChannelTimeoutMs := GetTcpChannelTimeoutMs()
|
||||
for {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
_debug.FreeOSMemory()
|
||||
streams.Range(func(key interface{}, value interface{}) bool {
|
||||
streamWrapper := value.(*tcpStreamWrapper)
|
||||
stream := streamWrapper.stream
|
||||
|
||||
@@ -13,11 +13,9 @@ const (
|
||||
MaxBufferedPagesTotalEnvVarName = "MAX_BUFFERED_PAGES_TOTAL"
|
||||
MaxBufferedPagesPerConnectionEnvVarName = "MAX_BUFFERED_PAGES_PER_CONNECTION"
|
||||
TcpStreamChannelTimeoutMsEnvVarName = "TCP_STREAM_CHANNEL_TIMEOUT_MS"
|
||||
MaxNumberOfGoroutinesEnvVarName = "MAX_NUMBER_OF_GOROUTINES"
|
||||
MaxBufferedPagesTotalDefaultValue = 5000
|
||||
MaxBufferedPagesPerConnectionDefaultValue = 5000
|
||||
TcpStreamChannelTimeoutMsDefaultValue = 5000
|
||||
MaxNumberOfGoroutinesDefaultValue = 4000
|
||||
TcpStreamChannelTimeoutMsDefaultValue = 10000
|
||||
)
|
||||
|
||||
type globalSettings struct {
|
||||
@@ -62,14 +60,6 @@ func GetTcpChannelTimeoutMs() time.Duration {
|
||||
return time.Duration(valueFromEnv) * time.Millisecond
|
||||
}
|
||||
|
||||
func GetMaxNumberOfGoroutines() int {
|
||||
valueFromEnv, err := strconv.Atoi(os.Getenv(MaxNumberOfGoroutinesEnvVarName))
|
||||
if err != nil {
|
||||
return MaxNumberOfGoroutinesDefaultValue
|
||||
}
|
||||
return valueFromEnv
|
||||
}
|
||||
|
||||
func GetMemoryProfilingEnabled() bool {
|
||||
return os.Getenv(MemoryProfilingEnabledEnvVarName) == "1"
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ func (h *tcpReader) Close() {
|
||||
func (h *tcpReader) run(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
b := bufio.NewReader(h)
|
||||
err := h.extension.Dissector.Dissect(b, h.isClient, h.tcpID, h.counterPair, h.superTimer, h.parent.superIdentifier, h.emitter)
|
||||
err := h.extension.Dissector.Dissect(b, h.isClient, h.tcpID, h.counterPair, h.superTimer, h.parent.superIdentifier, h.emitter, filteringOptions)
|
||||
if err != nil {
|
||||
io.Copy(ioutil.Discard, b)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package tap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -33,8 +32,6 @@ type tcpStreamWrapper struct {
|
||||
var streams *sync.Map = &sync.Map{} // global
|
||||
var streamId int64 = 0
|
||||
|
||||
var maxNumberOfGoroutines int
|
||||
|
||||
func (factory *tcpStreamFactory) New(net, transport gopacket.Flow, tcp *layers.TCP, ac reassembly.AssemblerContext) reassembly.Stream {
|
||||
rlog.Debugf("* NEW: %s %s", net, transport)
|
||||
fsmOptions := reassembly.TCPSimpleFSMOptions{
|
||||
@@ -61,11 +58,6 @@ func (factory *tcpStreamFactory) New(net, transport gopacket.Flow, tcp *layers.T
|
||||
superIdentifier: &api.SuperIdentifier{},
|
||||
}
|
||||
if stream.isTapTarget {
|
||||
if runtime.NumGoroutine() > maxNumberOfGoroutines {
|
||||
appStats.IncDroppedTcpStreams()
|
||||
rlog.Debugf("Dropped a TCP stream because of load. Total dropped: %d Total Goroutines: %d\n", appStats.DroppedTcpStreams, runtime.NumGoroutine())
|
||||
return stream
|
||||
}
|
||||
streamId++
|
||||
stream.id = streamId
|
||||
for i, extension := range extensions {
|
||||
|
||||
@@ -42,7 +42,6 @@ const EntryTitle: React.FC<any> = ({protocol, data, bodySize, elapsedTime}) => {
|
||||
<div style={{right: "30px", position: "absolute", display: "flex"}}>
|
||||
{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>
|
||||
<div style={{opacity: 0.5}}>{'rulesMatched' in data ? data.rulesMatched?.length : '0'} Rules Applied</div>
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
@@ -72,7 +71,7 @@ export const EntryDetailed: React.FC<EntryDetailedProps> = ({entryData}) => {
|
||||
/>
|
||||
{entryData.data && <EntrySummary data={entryData.data}/>}
|
||||
<>
|
||||
{entryData.data && <EntryViewer representation={entryData.representation} color={entryData.protocol.backgroundColor}/>}
|
||||
{entryData.data && <EntryViewer representation={entryData.representation} rulesMatched={entryData.rulesMatched} elapsedTime={entryData.data.elapsedTime} color={entryData.protocol.backgroundColor}/>}
|
||||
</>
|
||||
</>
|
||||
};
|
||||
|
||||
@@ -215,10 +215,10 @@ export const EntryTablePolicySection: React.FC<EntryPolicySectionProps> = ({serv
|
||||
<>
|
||||
{
|
||||
rule.Key &&
|
||||
<tr className={styles.dataValue}><td><b>Key</b>:</td><td>{rule.Key}</td></tr>
|
||||
<tr className={styles.dataValue}><td><b>Key:</b></td> <td>{rule.Key}</td></tr>
|
||||
}
|
||||
{
|
||||
rule.Latency &&
|
||||
rule.Latency !== 0 &&
|
||||
<tr className={styles.dataValue}><td><b>Latency:</b></td> <td>{rule.Latency}</td></tr>
|
||||
}
|
||||
{
|
||||
@@ -231,7 +231,7 @@ export const EntryTablePolicySection: React.FC<EntryPolicySectionProps> = ({serv
|
||||
}
|
||||
{
|
||||
rule.Service &&
|
||||
<tr className={styles.dataValue}><td><b>Service:</b></td> <td>{service}</td></tr>
|
||||
<tr className={styles.dataValue}><td><b>Service:</b></td> <td>{rule.Service}</td></tr>
|
||||
}
|
||||
{
|
||||
rule.Type &&
|
||||
@@ -251,7 +251,7 @@ export const EntryTablePolicySection: React.FC<EntryPolicySectionProps> = ({serv
|
||||
</tbody>
|
||||
</table>
|
||||
</EntrySectionContainer>
|
||||
</> : <span/>
|
||||
</> : <span className={styles.noRules}>No rules could be applied to this request.</span>
|
||||
}
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
@@ -33,8 +33,7 @@ const SectionsRepresentation: React.FC<any> = ({data, color}) => {
|
||||
return <>{sections}</>;
|
||||
}
|
||||
|
||||
const AutoRepresentation: React.FC<any> = ({representation, color}) => {
|
||||
const rulesMatched = []
|
||||
const AutoRepresentation: React.FC<any> = ({representation, rulesMatched, elapsedTime, color}) => {
|
||||
const TABS = [
|
||||
{
|
||||
tab: 'request'
|
||||
@@ -68,8 +67,7 @@ const AutoRepresentation: React.FC<any> = ({representation, color}) => {
|
||||
<SectionsRepresentation data={response} color={color}/>
|
||||
</React.Fragment>}
|
||||
{currentTab === TABS[2].tab && <React.Fragment>
|
||||
{// FIXME: Fix here
|
||||
<EntryTablePolicySection service={representation.service} title={'Rule'} color={color} latency={0} response={response} arrayToIterate={rulesMatched ? rulesMatched : []}/>}
|
||||
<EntryTablePolicySection service={representation.service} title={'Rule'} color={color} latency={elapsedTime} response={response} arrayToIterate={rulesMatched ? rulesMatched : []}/>
|
||||
</React.Fragment>}
|
||||
</div>}
|
||||
</div>;
|
||||
@@ -77,11 +75,13 @@ const AutoRepresentation: React.FC<any> = ({representation, color}) => {
|
||||
|
||||
interface Props {
|
||||
representation: any;
|
||||
color: string,
|
||||
rulesMatched: any;
|
||||
color: string;
|
||||
elapsedTime: number;
|
||||
}
|
||||
|
||||
const EntryViewer: React.FC<Props> = ({representation, color}) => {
|
||||
return <AutoRepresentation representation={representation} color={color}/>
|
||||
const EntryViewer: React.FC<Props> = ({representation, rulesMatched, elapsedTime, color}) => {
|
||||
return <AutoRepresentation representation={representation} rulesMatched={rulesMatched} elapsedTime={elapsedTime} color={color}/>
|
||||
};
|
||||
|
||||
export default EntryViewer;
|
||||
|
||||
@@ -19,20 +19,31 @@
|
||||
|
||||
.rowSelected
|
||||
border: 1px $blue-color solid
|
||||
// border-left: 5px $blue-color solid
|
||||
margin-left: 10px
|
||||
margin-right: 3px
|
||||
|
||||
.ruleSuccessRow
|
||||
border: 1px $success-color solid
|
||||
// border-left: 5px $success-color solid
|
||||
background: #E8FFF1
|
||||
|
||||
.ruleSuccessRowSelected
|
||||
border: 1px #6FCF97 solid
|
||||
border-left: 5px #6FCF97 solid
|
||||
|
||||
.ruleFailureRow
|
||||
background: #FFE9EF
|
||||
|
||||
.ruleFailureRowSelected
|
||||
border: 1px $failure-color solid
|
||||
// border-left: 5px $failure-color solid
|
||||
border-left: 5px $failure-color solid
|
||||
|
||||
.ruleNumberText
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
|
||||
.ruleNumberTextFailure
|
||||
color: #DB2156
|
||||
|
||||
.ruleNumberTextSuccess
|
||||
color: #219653
|
||||
|
||||
.service
|
||||
text-overflow: ellipsis
|
||||
|
||||
@@ -43,6 +43,7 @@ interface EntryProps {
|
||||
|
||||
export const EntryItem: React.FC<EntryProps> = ({entry, setFocusedEntryId, isSelected, style}) => {
|
||||
const classification = getClassification(entry.statusCode)
|
||||
const numberOfRules = entry.rules.numberOfRules
|
||||
let ingoingIcon;
|
||||
let outgoingIcon;
|
||||
switch(classification) {
|
||||
@@ -62,47 +63,39 @@ export const EntryItem: React.FC<EntryProps> = ({entry, setFocusedEntryId, isSel
|
||||
break;
|
||||
}
|
||||
}
|
||||
// let additionalRulesProperties = "";
|
||||
// let ruleSuccess: boolean;
|
||||
let additionalRulesProperties = "";
|
||||
let ruleSuccess: boolean;
|
||||
let rule = 'latency' in entry.rules
|
||||
if (rule) {
|
||||
if (entry.rules.latency !== -1) {
|
||||
if (entry.rules.latency >= entry.latency) {
|
||||
// additionalRulesProperties = styles.ruleSuccessRow
|
||||
// ruleSuccess = true
|
||||
additionalRulesProperties = styles.ruleSuccessRow
|
||||
ruleSuccess = true
|
||||
} else {
|
||||
// additionalRulesProperties = styles.ruleFailureRow
|
||||
// ruleSuccess = false
|
||||
additionalRulesProperties = styles.ruleFailureRow
|
||||
ruleSuccess = false
|
||||
}
|
||||
if (isSelected) {
|
||||
// additionalRulesProperties += ` ${entry.rules.latency >= entry.latency ? styles.ruleSuccessRowSelected : styles.ruleFailureRowSelected}`
|
||||
additionalRulesProperties += ` ${entry.rules.latency >= entry.latency ? styles.ruleSuccessRowSelected : styles.ruleFailureRowSelected}`
|
||||
}
|
||||
} else {
|
||||
if (entry.rules.status) {
|
||||
// additionalRulesProperties = styles.ruleSuccessRow
|
||||
// ruleSuccess = true
|
||||
additionalRulesProperties = styles.ruleSuccessRow
|
||||
ruleSuccess = true
|
||||
} else {
|
||||
// additionalRulesProperties = styles.ruleFailureRow
|
||||
// ruleSuccess = false
|
||||
additionalRulesProperties = styles.ruleFailureRow
|
||||
ruleSuccess = false
|
||||
}
|
||||
if (isSelected) {
|
||||
// additionalRulesProperties += ` ${entry.rules.status ? styles.ruleSuccessRowSelected : styles.ruleFailureRowSelected}`
|
||||
additionalRulesProperties += ` ${entry.rules.status ? styles.ruleSuccessRowSelected : styles.ruleFailureRowSelected}`
|
||||
}
|
||||
}
|
||||
}
|
||||
let backgroundColor = "";
|
||||
if ('latency' in entry.rules) {
|
||||
if (entry.rules.latency !== -1) {
|
||||
backgroundColor = entry.rules.latency >= entry.latency ? styles.ruleSuccessRow : styles.ruleFailureRow
|
||||
} else {
|
||||
backgroundColor = entry.rules.status ? styles.ruleSuccessRow : styles.ruleFailureRow
|
||||
}
|
||||
}
|
||||
return <>
|
||||
<div
|
||||
id={entry.id}
|
||||
className={`${styles.row}
|
||||
${isSelected ? styles.rowSelected : backgroundColor}`}
|
||||
${isSelected && !rule ? styles.rowSelected : additionalRulesProperties}`}
|
||||
onClick={() => setFocusedEntryId(entry.id)}
|
||||
style={{
|
||||
border: isSelected ? `1px ${entry.protocol.backgroundColor} solid` : "1px transparent solid",
|
||||
@@ -122,6 +115,13 @@ export const EntryItem: React.FC<EntryProps> = ({entry, setFocusedEntryId, isSel
|
||||
<span title="Service Name">{entry.service}</span>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
rule ?
|
||||
<div className={`${styles.ruleNumberText} ${ruleSuccess ? styles.ruleNumberTextSuccess : styles.ruleNumberTextFailure}`}>
|
||||
{`Rules (${numberOfRules})`}
|
||||
</div>
|
||||
: ""
|
||||
}
|
||||
<div className={styles.directionContainer}>
|
||||
<span className={styles.port} title="Source Port">{entry.sourcePort}</span>
|
||||
{entry.isOutgoing ?
|
||||
@@ -138,4 +138,5 @@ export const EntryItem: React.FC<EntryProps> = ({entry, setFocusedEntryId, isSel
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user