mirror of
https://github.com/kubeshark/kubeshark.git
synced 2026-02-15 02:19:54 +00:00
Compare commits
16 Commits
36.0-dev10
...
36.0-dev26
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5525214d0a | ||
|
|
efd414a2ed | ||
|
|
b3e79ff244 | ||
|
|
d4b9fea5a7 | ||
|
|
d11770681b | ||
|
|
e9719cba3a | ||
|
|
15f7b889e2 | ||
|
|
d98ac0e8f7 | ||
|
|
a3c236ff0a | ||
|
|
4b280ecd6d | ||
|
|
de554f5fb6 | ||
|
|
7c159fffc0 | ||
|
|
1f2f63d11b | ||
|
|
e2544aea12 | ||
|
|
57e60073f5 | ||
|
|
f220ad2f1a |
4
.github/workflows/static_code_analysis.yml
vendored
4
.github/workflows/static_code_analysis.yml
vendored
@@ -32,6 +32,10 @@ jobs:
|
||||
id: agent_modified_files
|
||||
run: devops/check_modified_files.sh agent/
|
||||
|
||||
- name: Generate eBPF object files and go bindings
|
||||
id: generate_ebpf
|
||||
run: make bpf
|
||||
|
||||
- name: Go lint - agent
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
if: steps.agent_modified_files.outputs.matched == 'true'
|
||||
|
||||
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@@ -40,6 +40,10 @@ jobs:
|
||||
run: |
|
||||
./devops/install-capstone.sh
|
||||
|
||||
- name: Generate eBPF object files and go bindings
|
||||
id: generate_ebpf
|
||||
run: make bpf
|
||||
|
||||
- name: Check CLI modified files
|
||||
id: cli_modified_files
|
||||
run: devops/check_modified_files.sh cli/
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -56,3 +56,6 @@ tap/extensions/*/expect
|
||||
|
||||
# Ignore *.log files
|
||||
*.log
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
|
||||
20
Makefile
20
Makefile
@@ -8,7 +8,7 @@ SHELL=/bin/bash
|
||||
# HELP
|
||||
# This will output the help for each task
|
||||
# thanks to https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
|
||||
.PHONY: help ui agent agent-debug cli tap docker
|
||||
.PHONY: help ui agent agent-debug cli tap docker bpf clean-bpf
|
||||
|
||||
help: ## This help.
|
||||
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||
@@ -20,6 +20,13 @@ TS_SUFFIX="$(shell date '+%s')"
|
||||
GIT_BRANCH="$(shell git branch | grep \* | cut -d ' ' -f2 | tr '[:upper:]' '[:lower:]' | tr '/' '_')"
|
||||
BUCKET_PATH=static.up9.io/mizu/$(GIT_BRANCH)
|
||||
export VER?=0.0
|
||||
ARCH=$(shell uname -m)
|
||||
ifeq ($(ARCH),$(filter $(ARCH),aarch64 arm64))
|
||||
BPF_O_ARCH_LABEL=arm64
|
||||
else
|
||||
BPF_O_ARCH_LABEL=x86
|
||||
endif
|
||||
BPF_O_FILES = tap/tlstapper/tlstapper46_bpfel_$(BPF_O_ARCH_LABEL).o tap/tlstapper/tlstapper_bpfel_$(BPF_O_ARCH_LABEL).o
|
||||
|
||||
ui: ## Build UI.
|
||||
@(cd ui; npm i ; npm run build; )
|
||||
@@ -31,11 +38,17 @@ cli: ## Build CLI.
|
||||
cli-debug: ## Build CLI.
|
||||
@echo "building cli"; cd cli && $(MAKE) build-debug
|
||||
|
||||
agent: ## Build agent.
|
||||
agent: bpf ## Build agent.
|
||||
@(echo "building mizu agent .." )
|
||||
@(cd agent; go build -o build/mizuagent main.go)
|
||||
@ls -l agent/build
|
||||
|
||||
bpf: $(BPF_O_FILES)
|
||||
|
||||
$(BPF_O_FILES): $(wildcard tap/tlstapper/bpf/**/*.[ch])
|
||||
@(echo "building tlstapper bpf")
|
||||
@(./tap/tlstapper/bpf-builder/build.sh)
|
||||
|
||||
agent-debug: ## Build agent for debug.
|
||||
@(echo "building mizu agent for debug.." )
|
||||
@(cd agent; go build -gcflags="all=-N -l" -o build/mizuagent main.go)
|
||||
@@ -76,6 +89,9 @@ clean-cli: ## Clean CLI.
|
||||
clean-docker: ## Run clean docker
|
||||
@(echo "DOCKER cleanup - NOT IMPLEMENTED YET " )
|
||||
|
||||
clean-bpf:
|
||||
@(rm $(BPF_O_FILES) ; echo "bpf cleanup done" )
|
||||
|
||||
test-lint: ## Run lint on all modules
|
||||
cd agent && golangci-lint run
|
||||
cd shared && golangci-lint run
|
||||
|
||||
@@ -11,7 +11,6 @@ module.exports = defineConfig({
|
||||
testUrl: 'http://localhost:8899/',
|
||||
redactHeaderContent: 'User-Header[REDACTED]',
|
||||
redactBodyContent: '{ "User": "[REDACTED]" }',
|
||||
regexMaskingBodyContent: '[REDACTED]',
|
||||
greenFilterColor: 'rgb(210, 250, 210)',
|
||||
redFilterColor: 'rgb(250, 214, 220)',
|
||||
bodyJsonClass: '.hljs',
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import {isValueExistsInElement} from "../testHelpers/TrafficHelper";
|
||||
|
||||
it('Loading Mizu', function () {
|
||||
cy.visit(Cypress.env('testUrl'));
|
||||
});
|
||||
|
||||
isValueExistsInElement(true, Cypress.env('regexMaskingBodyContent'), Cypress.env('bodyJsonClass'));
|
||||
@@ -18,7 +18,6 @@ require (
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/go-logr/logr v1.2.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.7 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
@@ -29,7 +28,6 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/up9inc/mizu/logger v0.0.0 // indirect
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||
golang.org/x/sys v0.0.0-20220207234003-57398862261d // indirect
|
||||
|
||||
@@ -206,7 +206,6 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
|
||||
@@ -27,7 +27,7 @@ else
|
||||
fi
|
||||
|
||||
echo "Starting minikube..."
|
||||
minikube start --cpus 2 --memory 6946
|
||||
minikube start --cpus 2 --memory 6000
|
||||
|
||||
echo "Creating mizu tests namespaces"
|
||||
kubectl create namespace mizu-tests --dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
@@ -2,10 +2,8 @@ package acceptanceTests
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
@@ -343,7 +341,7 @@ func TestTapRedact(t *testing.T) {
|
||||
|
||||
tapNamespace := GetDefaultTapNamespace()
|
||||
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||
tapCmdArgs = append(tapCmdArgs, "--redact")
|
||||
tapCmdArgs = append(tapCmdArgs, "--redact", "--set", "tap.redact-patterns.request-headers=User-Header", "--set", "tap.redact-patterns.request-body=User")
|
||||
|
||||
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||
t.Logf("running command: %v", tapCmd.String())
|
||||
@@ -429,60 +427,6 @@ func TestTapNoRedact(t *testing.T) {
|
||||
RunCypressTests(t, "npx cypress run --spec \"cypress/e2e/tests/NoRedact.js\"")
|
||||
}
|
||||
|
||||
func TestTapRegexMasking(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...)
|
||||
|
||||
tapCmdArgs = append(tapCmdArgs, "--redact")
|
||||
|
||||
tapCmdArgs = append(tapCmdArgs, "-r", "Mizu")
|
||||
|
||||
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)
|
||||
for i := 0; i < DefaultEntriesCount; i++ {
|
||||
response, requestErr := http.Post(fmt.Sprintf("%v/post", proxyUrl), "text/plain", bytes.NewBufferString("Mizu"))
|
||||
if _, requestErr = ExecuteHttpRequest(response, requestErr); requestErr != nil {
|
||||
t.Errorf("failed to send proxy request, err: %v", requestErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
RunCypressTests(t, "npx cypress run --spec \"cypress/e2e/tests/RegexMasking.js\"")
|
||||
|
||||
}
|
||||
|
||||
func TestTapIgnoredUserAgents(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("ignored acceptance test")
|
||||
|
||||
@@ -215,12 +215,11 @@ func DeleteKubeFile(kubeContext string, namespace string, filename string) error
|
||||
func getDefaultCommandArgs() []string {
|
||||
agentImageValue := os.Getenv("MIZU_CI_IMAGE")
|
||||
setFlag := "--set"
|
||||
telemetry := "telemetry=false"
|
||||
agentImage := fmt.Sprintf("agent-image=%s", agentImageValue)
|
||||
imagePullPolicy := "image-pull-policy=IfNotPresent"
|
||||
headless := "headless=true"
|
||||
|
||||
return []string{setFlag, telemetry, setFlag, agentImage, setFlag, imagePullPolicy, setFlag, headless}
|
||||
return []string{setFlag, agentImage, setFlag, imagePullPolicy, setFlag, headless}
|
||||
}
|
||||
|
||||
func GetDefaultTapCommandArgs() []string {
|
||||
|
||||
@@ -48,7 +48,6 @@ require (
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/beevik/etree v1.1.0 // indirect
|
||||
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect
|
||||
github.com/chanced/dynamic v0.0.0-20211210164248-f8fadb1d735b // indirect
|
||||
github.com/cilium/ebpf v0.9.0 // indirect
|
||||
|
||||
@@ -101,7 +101,6 @@ github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
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/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
core "k8s.io/api/core/v1"
|
||||
|
||||
@@ -80,7 +83,24 @@ func GetGeneralStats(c *gin.Context) {
|
||||
}
|
||||
|
||||
func GetTrafficStats(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, providers.GetTrafficStats())
|
||||
startTime, endTime, err := getStartEndTime(c)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, providers.GetTrafficStats(startTime, endTime))
|
||||
}
|
||||
|
||||
func getStartEndTime(c *gin.Context) (time.Time, time.Time, error) {
|
||||
startTimeValue, err := strconv.Atoi(c.Query("startTimeMs"))
|
||||
if err != nil {
|
||||
return time.UnixMilli(0), time.UnixMilli(0), fmt.Errorf("invalid start time: %v", err)
|
||||
}
|
||||
endTimeValue, err := strconv.Atoi(c.Query("endTimeMs"))
|
||||
if err != nil {
|
||||
return time.UnixMilli(0), time.UnixMilli(0), fmt.Errorf("invalid end time: %v", err)
|
||||
}
|
||||
return time.UnixMilli(int64(startTimeValue)), time.UnixMilli(int64(endTimeValue)), nil
|
||||
}
|
||||
|
||||
func GetCurrentResolvingInformation(c *gin.Context) {
|
||||
|
||||
@@ -11,75 +11,30 @@ import (
|
||||
"github.com/up9inc/mizu/logger"
|
||||
)
|
||||
|
||||
// Keep it because we might want cookies in the future
|
||||
//func BuildCookies(rawCookies []interface{}) []har.Cookie {
|
||||
// cookies := make([]har.Cookie, 0, len(rawCookies))
|
||||
//
|
||||
// for _, cookie := range rawCookies {
|
||||
// c := cookie.(map[string]interface{})
|
||||
// expiresStr := ""
|
||||
// if c["expires"] != nil {
|
||||
// expiresStr = c["expires"].(string)
|
||||
// }
|
||||
// expires, _ := time.Parse(time.RFC3339, expiresStr)
|
||||
// httpOnly := false
|
||||
// if c["httponly"] != nil {
|
||||
// httpOnly, _ = strconv.ParseBool(c["httponly"].(string))
|
||||
// }
|
||||
// secure := false
|
||||
// if c["secure"] != nil {
|
||||
// secure, _ = strconv.ParseBool(c["secure"].(string))
|
||||
// }
|
||||
// path := ""
|
||||
// if c["path"] != nil {
|
||||
// path = c["path"].(string)
|
||||
// }
|
||||
// domain := ""
|
||||
// if c["domain"] != nil {
|
||||
// domain = c["domain"].(string)
|
||||
// }
|
||||
//
|
||||
// cookies = append(cookies, har.Cookie{
|
||||
// Name: c["name"].(string),
|
||||
// Value: c["value"].(string),
|
||||
// Path: path,
|
||||
// Domain: domain,
|
||||
// HTTPOnly: httpOnly,
|
||||
// Secure: secure,
|
||||
// Expires: expires,
|
||||
// Expires8601: expiresStr,
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// return cookies
|
||||
//}
|
||||
|
||||
func BuildHeaders(rawHeaders []interface{}) ([]Header, string, string, string, string, string) {
|
||||
func BuildHeaders(rawHeaders map[string]interface{}) ([]Header, string, string, string, string, string) {
|
||||
var host, scheme, authority, path, status string
|
||||
headers := make([]Header, 0, len(rawHeaders))
|
||||
|
||||
for _, header := range rawHeaders {
|
||||
h := header.(map[string]interface{})
|
||||
|
||||
for key, value := range rawHeaders {
|
||||
headers = append(headers, Header{
|
||||
Name: h["name"].(string),
|
||||
Value: h["value"].(string),
|
||||
Name: key,
|
||||
Value: value.(string),
|
||||
})
|
||||
|
||||
if h["name"] == "Host" {
|
||||
host = h["value"].(string)
|
||||
if key == "Host" {
|
||||
host = value.(string)
|
||||
}
|
||||
if h["name"] == ":authority" {
|
||||
authority = h["value"].(string)
|
||||
if key == ":authority" {
|
||||
authority = value.(string)
|
||||
}
|
||||
if h["name"] == ":scheme" {
|
||||
scheme = h["value"].(string)
|
||||
if key == ":scheme" {
|
||||
scheme = value.(string)
|
||||
}
|
||||
if h["name"] == ":path" {
|
||||
path = h["value"].(string)
|
||||
if key == ":path" {
|
||||
path = value.(string)
|
||||
}
|
||||
if h["name"] == ":status" {
|
||||
status = h["value"].(string)
|
||||
if key == ":status" {
|
||||
status = value.(string)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,8 +74,8 @@ func BuildPostParams(rawParams []interface{}) []Param {
|
||||
}
|
||||
|
||||
func NewRequest(request map[string]interface{}) (harRequest *Request, err error) {
|
||||
headers, host, scheme, authority, path, _ := BuildHeaders(request["_headers"].([]interface{}))
|
||||
cookies := make([]Cookie, 0) // BuildCookies(request["_cookies"].([]interface{}))
|
||||
headers, host, scheme, authority, path, _ := BuildHeaders(request["headers"].(map[string]interface{}))
|
||||
cookies := make([]Cookie, 0)
|
||||
|
||||
postData, _ := request["postData"].(map[string]interface{})
|
||||
mimeType := postData["mimeType"]
|
||||
@@ -134,12 +89,20 @@ func NewRequest(request map[string]interface{}) (harRequest *Request, err error)
|
||||
}
|
||||
|
||||
queryString := make([]QueryString, 0)
|
||||
for _, _qs := range request["_queryString"].([]interface{}) {
|
||||
qs := _qs.(map[string]interface{})
|
||||
queryString = append(queryString, QueryString{
|
||||
Name: qs["name"].(string),
|
||||
Value: qs["value"].(string),
|
||||
})
|
||||
for key, value := range request["queryString"].(map[string]interface{}) {
|
||||
if valuesInterface, ok := value.([]interface{}); ok {
|
||||
for _, valueInterface := range valuesInterface {
|
||||
queryString = append(queryString, QueryString{
|
||||
Name: key,
|
||||
Value: valueInterface.(string),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
queryString = append(queryString, QueryString{
|
||||
Name: key,
|
||||
Value: value.(string),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("http://%s%s", host, request["url"].(string))
|
||||
@@ -172,8 +135,8 @@ func NewRequest(request map[string]interface{}) (harRequest *Request, err error)
|
||||
}
|
||||
|
||||
func NewResponse(response map[string]interface{}) (harResponse *Response, err error) {
|
||||
headers, _, _, _, _, _status := BuildHeaders(response["_headers"].([]interface{}))
|
||||
cookies := make([]Cookie, 0) // BuildCookies(response["_cookies"].([]interface{}))
|
||||
headers, _, _, _, _, _status := BuildHeaders(response["headers"].(map[string]interface{}))
|
||||
cookies := make([]Cookie, 0)
|
||||
|
||||
content, _ := response["content"].(map[string]interface{})
|
||||
mimeType := content["mimeType"]
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -82,13 +81,12 @@ func GetGeneralStats() *GeneralStats {
|
||||
|
||||
func InitProtocolToColor(protocolMap map[string]*api.Protocol) {
|
||||
for item, value := range protocolMap {
|
||||
splitted := strings.SplitN(item, "/", 3)
|
||||
protocolToColor[splitted[len(splitted)-1]] = value.BackgroundColor
|
||||
protocolToColor[api.GetProtocolSummary(item).Abbreviation] = value.BackgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
func GetTrafficStats() *TrafficStatsResponse {
|
||||
bucketsStatsCopy := getBucketStatsCopy()
|
||||
func GetTrafficStats(startTime time.Time, endTime time.Time) *TrafficStatsResponse {
|
||||
bucketsStatsCopy := getFilteredBucketStatsCopy(startTime, endTime)
|
||||
|
||||
return &TrafficStatsResponse{
|
||||
Protocols: getAvailableProtocols(bucketsStatsCopy),
|
||||
@@ -264,7 +262,7 @@ func convertAccumulativeStatsDictToArray(methodsPerProtocolAggregated map[string
|
||||
return protocolsData
|
||||
}
|
||||
|
||||
func getBucketStatsCopy() BucketStats {
|
||||
func getFilteredBucketStatsCopy(startTime time.Time, endTime time.Time) BucketStats {
|
||||
bucketStatsCopy := BucketStats{}
|
||||
bucketStatsLocker.Lock()
|
||||
if err := copier.Copy(&bucketStatsCopy, bucketsStats); err != nil {
|
||||
@@ -272,7 +270,18 @@ func getBucketStatsCopy() BucketStats {
|
||||
return nil
|
||||
}
|
||||
bucketStatsLocker.Unlock()
|
||||
return bucketStatsCopy
|
||||
|
||||
filteredBucketStatsCopy := BucketStats{}
|
||||
interval := InternalBucketThreshold
|
||||
|
||||
for _, bucket := range bucketStatsCopy {
|
||||
if (bucket.BucketTime.After(startTime.Add(-1*interval/2).Round(interval)) && bucket.BucketTime.Before(endTime.Add(-1*interval/2).Round(interval))) ||
|
||||
bucket.BucketTime.Equal(startTime.Add(-1*interval/2).Round(interval)) ||
|
||||
bucket.BucketTime.Equal(endTime.Add(-1*interval/2).Round(interval)) {
|
||||
filteredBucketStatsCopy = append(filteredBucketStatsCopy, bucket)
|
||||
}
|
||||
}
|
||||
return filteredBucketStatsCopy
|
||||
}
|
||||
|
||||
func getAggregatedResultTiming(stats BucketStats, interval time.Duration) map[time.Time]map[string]map[string]*AccumulativeStatsCounter {
|
||||
|
||||
@@ -4,9 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/up9inc/mizu/cli/utils"
|
||||
@@ -93,45 +91,3 @@ func (provider *Provider) ReportTappedPods(pods []core.Pod) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *Provider) GetGeneralStats() (map[string]interface{}, error) {
|
||||
generalStatsUrl := fmt.Sprintf("%s/status/general", provider.url)
|
||||
|
||||
response, requestErr := utils.Get(generalStatsUrl, provider.client)
|
||||
if requestErr != nil {
|
||||
return nil, fmt.Errorf("failed to get general stats for telemetry, err: %w", requestErr)
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
|
||||
data, readErr := ioutil.ReadAll(response.Body)
|
||||
if readErr != nil {
|
||||
return nil, fmt.Errorf("failed to read general stats for telemetry, err: %v", readErr)
|
||||
}
|
||||
|
||||
var generalStats map[string]interface{}
|
||||
if parseErr := json.Unmarshal(data, &generalStats); parseErr != nil {
|
||||
return nil, fmt.Errorf("failed to parse general stats for telemetry, err: %v", parseErr)
|
||||
}
|
||||
return generalStats, nil
|
||||
}
|
||||
|
||||
func (provider *Provider) GetVersion() (string, error) {
|
||||
versionUrl, _ := url.Parse(fmt.Sprintf("%s/metadata/version", provider.url))
|
||||
req := &http.Request{
|
||||
Method: http.MethodGet,
|
||||
URL: versionUrl,
|
||||
}
|
||||
statusResp, err := utils.Do(req, provider.client)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer statusResp.Body.Close()
|
||||
|
||||
versionResponse := &shared.VersionResponse{}
|
||||
if err := json.NewDecoder(statusResp.Body).Decode(&versionResponse); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return versionResponse.Ver, nil
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||
"github.com/up9inc/mizu/cli/telemetry"
|
||||
"github.com/up9inc/mizu/logger"
|
||||
)
|
||||
|
||||
@@ -12,7 +11,6 @@ var checkCmd = &cobra.Command{
|
||||
Use: "check",
|
||||
Short: "Check the Mizu installation for potential problems",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
go telemetry.ReportRun("check", nil)
|
||||
runMizuCheck()
|
||||
return nil
|
||||
},
|
||||
|
||||
@@ -2,14 +2,12 @@ package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/up9inc/mizu/cli/telemetry"
|
||||
)
|
||||
|
||||
var cleanCmd = &cobra.Command{
|
||||
Use: "clean",
|
||||
Short: "Removes all mizu resources",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
go telemetry.ReportRun("clean", nil)
|
||||
performCleanCommand()
|
||||
return nil
|
||||
},
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/up9inc/mizu/cli/config"
|
||||
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||
"github.com/up9inc/mizu/cli/telemetry"
|
||||
"github.com/up9inc/mizu/cli/uiUtils"
|
||||
"github.com/up9inc/mizu/logger"
|
||||
)
|
||||
@@ -16,8 +15,6 @@ var configCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Generate config with default values",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
go telemetry.ReportRun("config", config.Config.Config)
|
||||
|
||||
configWithDefaults, err := config.GetConfigWithDefaults()
|
||||
if err != nil {
|
||||
logger.Log.Errorf("Failed generating config with defaults, err: %v", err)
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||
"github.com/up9inc/mizu/cli/telemetry"
|
||||
"github.com/up9inc/mizu/logger"
|
||||
)
|
||||
|
||||
@@ -12,7 +11,6 @@ var installCmd = &cobra.Command{
|
||||
Use: "install",
|
||||
Short: "Installs mizu components",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
go telemetry.ReportRun("install", nil)
|
||||
runMizuInstall()
|
||||
return nil
|
||||
},
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||
"github.com/up9inc/mizu/cli/errormessage"
|
||||
"github.com/up9inc/mizu/cli/mizu/fsUtils"
|
||||
"github.com/up9inc/mizu/cli/telemetry"
|
||||
"github.com/up9inc/mizu/logger"
|
||||
)
|
||||
|
||||
@@ -17,8 +16,6 @@ var logsCmd = &cobra.Command{
|
||||
Use: "logs",
|
||||
Short: "Create a zip file with logs for Github issue or troubleshoot",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
go telemetry.ReportRun("logs", config.Config.Logs)
|
||||
|
||||
kubernetesProvider, err := getKubernetesProviderForCli()
|
||||
if err != nil {
|
||||
return nil
|
||||
|
||||
@@ -48,7 +48,6 @@ func init() {
|
||||
tapCmd.Flags().Uint16P(configStructs.GuiPortTapName, "p", defaultTapConfig.GuiPort, "Provide a custom port for the web interface webserver")
|
||||
tapCmd.Flags().StringSliceP(configStructs.NamespacesTapName, "n", defaultTapConfig.Namespaces, "Namespaces selector")
|
||||
tapCmd.Flags().BoolP(configStructs.AllNamespacesTapName, "A", defaultTapConfig.AllNamespaces, "Tap all namespaces")
|
||||
tapCmd.Flags().StringSliceP(configStructs.PlainTextFilterRegexesTapName, "r", defaultTapConfig.PlainTextFilterRegexes, "List of regex expressions that are used to filter matching values from text/plain http bodies")
|
||||
tapCmd.Flags().Bool(configStructs.EnableRedactionTapName, defaultTapConfig.EnableRedaction, "Enables redaction of potentially sensitive request/response headers and body values")
|
||||
tapCmd.Flags().String(configStructs.HumanMaxEntriesDBSizeTapName, defaultTapConfig.HumanMaxEntriesDBSize, "Override the default max entries db size")
|
||||
tapCmd.Flags().String(configStructs.InsertionFilterName, defaultTapConfig.InsertionFilter, "Set the insertion filter. Accepts string or a file path.")
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/up9inc/mizu/cli/resources"
|
||||
"github.com/up9inc/mizu/cli/telemetry"
|
||||
"github.com/up9inc/mizu/cli/utils"
|
||||
|
||||
core "k8s.io/api/core/v1"
|
||||
@@ -109,8 +108,6 @@ func RunMizuTap() {
|
||||
}
|
||||
|
||||
func finishTapExecution(kubernetesProvider *kubernetes.Provider) {
|
||||
telemetry.ReportTapTelemetry(apiProvider, config.Config.Tap, state.startTime)
|
||||
|
||||
finishMizuExecution(kubernetesProvider, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace)
|
||||
}
|
||||
|
||||
@@ -126,7 +123,6 @@ func getTapMizuAgentConfig() *shared.MizuAgentConfig {
|
||||
AgentDatabasePath: shared.DataDirPath,
|
||||
ServiceMap: config.Config.ServiceMap,
|
||||
OAS: config.Config.OAS,
|
||||
Telemetry: config.Config.Telemetry,
|
||||
}
|
||||
|
||||
return &mizuAgentConfig
|
||||
@@ -151,17 +147,18 @@ func printTappedPodsPreview(ctx context.Context, kubernetesProvider *kubernetes.
|
||||
}
|
||||
}
|
||||
|
||||
func startTapperSyncer(ctx context.Context, cancel context.CancelFunc, provider *kubernetes.Provider, targetNamespaces []string, mizuApiFilteringOptions api.TrafficFilteringOptions, startTime time.Time) error {
|
||||
func startTapperSyncer(ctx context.Context, cancel context.CancelFunc, provider *kubernetes.Provider, targetNamespaces []string, startTime time.Time) error {
|
||||
tapperSyncer, err := kubernetes.CreateAndStartMizuTapperSyncer(ctx, provider, kubernetes.TapperSyncerConfig{
|
||||
TargetNamespaces: targetNamespaces,
|
||||
PodFilterRegex: *config.Config.Tap.PodRegex(),
|
||||
MizuResourcesNamespace: config.Config.MizuResourcesNamespace,
|
||||
AgentImage: config.Config.AgentImage,
|
||||
TapperResources: config.Config.Tap.TapperResources,
|
||||
ImagePullPolicy: config.Config.ImagePullPolicy(),
|
||||
LogLevel: config.Config.LogLevel(),
|
||||
IgnoredUserAgents: config.Config.Tap.IgnoredUserAgents,
|
||||
MizuApiFilteringOptions: mizuApiFilteringOptions,
|
||||
TargetNamespaces: targetNamespaces,
|
||||
PodFilterRegex: *config.Config.Tap.PodRegex(),
|
||||
MizuResourcesNamespace: config.Config.MizuResourcesNamespace,
|
||||
AgentImage: config.Config.AgentImage,
|
||||
TapperResources: config.Config.Tap.TapperResources,
|
||||
ImagePullPolicy: config.Config.ImagePullPolicy(),
|
||||
LogLevel: config.Config.LogLevel(),
|
||||
MizuApiFilteringOptions: api.TrafficFilteringOptions{
|
||||
IgnoredUserAgents: config.Config.Tap.IgnoredUserAgents,
|
||||
},
|
||||
MizuServiceAccountExists: state.mizuServiceAccountExists,
|
||||
ServiceMesh: config.Config.Tap.ServiceMesh,
|
||||
Tls: config.Config.Tap.Tls,
|
||||
@@ -229,27 +226,6 @@ func getErrorDisplayTextForK8sTapManagerError(err kubernetes.K8sTapManagerError)
|
||||
}
|
||||
}
|
||||
|
||||
func getMizuApiFilteringOptions() (*api.TrafficFilteringOptions, error) {
|
||||
var compiledRegexSlice []*api.SerializableRegexp
|
||||
|
||||
if config.Config.Tap.PlainTextFilterRegexes != nil && len(config.Config.Tap.PlainTextFilterRegexes) > 0 {
|
||||
compiledRegexSlice = make([]*api.SerializableRegexp, 0)
|
||||
for _, regexStr := range config.Config.Tap.PlainTextFilterRegexes {
|
||||
compiledRegex, err := api.CompileRegexToSerializableRegexp(regexStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
compiledRegexSlice = append(compiledRegexSlice, compiledRegex)
|
||||
}
|
||||
}
|
||||
|
||||
return &api.TrafficFilteringOptions{
|
||||
PlainTextMaskingRegexes: compiledRegexSlice,
|
||||
IgnoredUserAgents: config.Config.Tap.IgnoredUserAgents,
|
||||
EnableRedaction: config.Config.Tap.EnableRedaction,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func watchApiServerPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
|
||||
podExactRegex := regexp.MustCompile(fmt.Sprintf("^%s$", kubernetes.ApiServerPodName))
|
||||
podWatchHelper := kubernetes.NewPodWatchHelper(kubernetesProvider, podExactRegex)
|
||||
@@ -367,8 +343,7 @@ func watchApiServerEvents(ctx context.Context, kubernetesProvider *kubernetes.Pr
|
||||
func postApiServerStarted(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
|
||||
startProxyReportErrorIfAny(kubernetesProvider, ctx, cancel, config.Config.Tap.GuiPort)
|
||||
|
||||
options, _ := getMizuApiFilteringOptions()
|
||||
if err := startTapperSyncer(ctx, cancel, kubernetesProvider, state.targetNamespaces, *options, state.startTime); err != nil {
|
||||
if err := startTapperSyncer(ctx, cancel, kubernetesProvider, state.targetNamespaces, state.startTime); err != nil {
|
||||
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error starting mizu tapper syncer: %v", errormessage.FormatError(err)))
|
||||
cancel()
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/up9inc/mizu/cli/config"
|
||||
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||
"github.com/up9inc/mizu/cli/telemetry"
|
||||
"github.com/up9inc/mizu/logger"
|
||||
|
||||
"github.com/creasty/defaults"
|
||||
@@ -18,8 +17,6 @@ var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print version info",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
go telemetry.ReportRun("version", config.Config.Version)
|
||||
|
||||
if config.Config.Version.DebugInfo {
|
||||
timeStampInt, _ := strconv.ParseInt(mizu.BuildTimestamp, 10, 0)
|
||||
logger.Log.Infof("Version: %s \nBranch: %s (%s)", mizu.Ver, mizu.Branch, mizu.GitCommitHash)
|
||||
|
||||
@@ -3,9 +3,7 @@ package cmd
|
||||
import (
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/up9inc/mizu/cli/config"
|
||||
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||
"github.com/up9inc/mizu/cli/telemetry"
|
||||
"github.com/up9inc/mizu/logger"
|
||||
)
|
||||
|
||||
@@ -13,7 +11,6 @@ var viewCmd = &cobra.Command{
|
||||
Use: "view",
|
||||
Short: "Open GUI in browser",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
go telemetry.ReportRun("view", config.Config.View)
|
||||
runMizuView()
|
||||
return nil
|
||||
},
|
||||
|
||||
@@ -31,7 +31,6 @@ type ConfigStruct struct {
|
||||
AgentImage string `yaml:"agent-image,omitempty" readonly:""`
|
||||
ImagePullPolicyStr string `yaml:"image-pull-policy" default:"Always"`
|
||||
MizuResourcesNamespace string `yaml:"mizu-resources-namespace" default:"mizu"`
|
||||
Telemetry bool `yaml:"telemetry" default:"true"`
|
||||
DumpLogs bool `yaml:"dump-logs" default:"false"`
|
||||
KubeConfigPathStr string `yaml:"kube-config-path"`
|
||||
KubeContext string `yaml:"kube-context"`
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/up9inc/mizu/cli/uiUtils"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
@@ -15,38 +16,43 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
GuiPortTapName = "gui-port"
|
||||
NamespacesTapName = "namespaces"
|
||||
AllNamespacesTapName = "all-namespaces"
|
||||
PlainTextFilterRegexesTapName = "regex-masking"
|
||||
EnableRedactionTapName = "redact"
|
||||
HumanMaxEntriesDBSizeTapName = "max-entries-db-size"
|
||||
InsertionFilterName = "insertion-filter"
|
||||
DryRunTapName = "dry-run"
|
||||
ServiceMeshName = "service-mesh"
|
||||
TlsName = "tls"
|
||||
ProfilerName = "profiler"
|
||||
MaxLiveStreamsName = "max-live-streams"
|
||||
GuiPortTapName = "gui-port"
|
||||
NamespacesTapName = "namespaces"
|
||||
AllNamespacesTapName = "all-namespaces"
|
||||
EnableRedactionTapName = "redact"
|
||||
HumanMaxEntriesDBSizeTapName = "max-entries-db-size"
|
||||
InsertionFilterName = "insertion-filter"
|
||||
DryRunTapName = "dry-run"
|
||||
ServiceMeshName = "service-mesh"
|
||||
TlsName = "tls"
|
||||
ProfilerName = "profiler"
|
||||
MaxLiveStreamsName = "max-live-streams"
|
||||
)
|
||||
|
||||
type TapConfig struct {
|
||||
PodRegexStr string `yaml:"regex" default:".*"`
|
||||
GuiPort uint16 `yaml:"gui-port" default:"8899"`
|
||||
ProxyHost string `yaml:"proxy-host" default:"127.0.0.1"`
|
||||
Namespaces []string `yaml:"namespaces"`
|
||||
AllNamespaces bool `yaml:"all-namespaces" default:"false"`
|
||||
PlainTextFilterRegexes []string `yaml:"regex-masking"`
|
||||
IgnoredUserAgents []string `yaml:"ignored-user-agents"`
|
||||
EnableRedaction bool `yaml:"redact" default:"false"`
|
||||
HumanMaxEntriesDBSize string `yaml:"max-entries-db-size" default:"200MB"`
|
||||
InsertionFilter string `yaml:"insertion-filter" default:""`
|
||||
DryRun bool `yaml:"dry-run" default:"false"`
|
||||
ApiServerResources shared.Resources `yaml:"api-server-resources"`
|
||||
TapperResources shared.Resources `yaml:"tapper-resources"`
|
||||
ServiceMesh bool `yaml:"service-mesh" default:"false"`
|
||||
Tls bool `yaml:"tls" default:"false"`
|
||||
Profiler bool `yaml:"profiler" default:"false"`
|
||||
MaxLiveStreams int `yaml:"max-live-streams" default:"500"`
|
||||
PodRegexStr string `yaml:"regex" default:".*"`
|
||||
GuiPort uint16 `yaml:"gui-port" default:"8899"`
|
||||
ProxyHost string `yaml:"proxy-host" default:"127.0.0.1"`
|
||||
Namespaces []string `yaml:"namespaces"`
|
||||
AllNamespaces bool `yaml:"all-namespaces" default:"false"`
|
||||
IgnoredUserAgents []string `yaml:"ignored-user-agents"`
|
||||
EnableRedaction bool `yaml:"redact" default:"false"`
|
||||
RedactPatterns struct {
|
||||
RequestHeaders []string `yaml:"request-headers"`
|
||||
ResponseHeaders []string `yaml:"response-headers"`
|
||||
RequestBody []string `yaml:"request-body"`
|
||||
ResponseBody []string `yaml:"response-body"`
|
||||
RequestQueryParams []string `yaml:"request-query-params"`
|
||||
} `yaml:"redact-patterns"`
|
||||
HumanMaxEntriesDBSize string `yaml:"max-entries-db-size" default:"200MB"`
|
||||
InsertionFilter string `yaml:"insertion-filter" default:""`
|
||||
DryRun bool `yaml:"dry-run" default:"false"`
|
||||
ApiServerResources shared.Resources `yaml:"api-server-resources"`
|
||||
TapperResources shared.Resources `yaml:"tapper-resources"`
|
||||
ServiceMesh bool `yaml:"service-mesh" default:"false"`
|
||||
Tls bool `yaml:"tls" default:"false"`
|
||||
Profiler bool `yaml:"profiler" default:"false"`
|
||||
MaxLiveStreams int `yaml:"max-live-streams" default:"500"`
|
||||
}
|
||||
|
||||
func (config *TapConfig) PodRegex() *regexp.Regexp {
|
||||
@@ -71,9 +77,48 @@ func (config *TapConfig) GetInsertionFilter() string {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
redactFilter := getRedactFilter(config)
|
||||
if insertionFilter != "" && redactFilter != "" {
|
||||
return fmt.Sprintf("(%s) and (%s)", insertionFilter, redactFilter)
|
||||
} else if insertionFilter == "" && redactFilter != "" {
|
||||
return redactFilter
|
||||
}
|
||||
|
||||
return insertionFilter
|
||||
}
|
||||
|
||||
func getRedactFilter(config *TapConfig) string {
|
||||
if !config.EnableRedaction {
|
||||
return ""
|
||||
}
|
||||
|
||||
var redactValues []string
|
||||
for _, requestHeader := range config.RedactPatterns.RequestHeaders {
|
||||
redactValues = append(redactValues, fmt.Sprintf("request.headers['%s']", requestHeader))
|
||||
}
|
||||
for _, responseHeader := range config.RedactPatterns.ResponseHeaders {
|
||||
redactValues = append(redactValues, fmt.Sprintf("response.headers['%s']", responseHeader))
|
||||
}
|
||||
|
||||
for _, requestBody := range config.RedactPatterns.RequestBody {
|
||||
redactValues = append(redactValues, fmt.Sprintf("request.postData.text.json()...%s", requestBody))
|
||||
}
|
||||
for _, responseBody := range config.RedactPatterns.ResponseBody {
|
||||
redactValues = append(redactValues, fmt.Sprintf("response.content.text.json()...%s", responseBody))
|
||||
}
|
||||
|
||||
for _, requestQueryParams := range config.RedactPatterns.RequestQueryParams {
|
||||
redactValues = append(redactValues, fmt.Sprintf("request.queryString['%s']", requestQueryParams))
|
||||
}
|
||||
|
||||
if len(redactValues) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf("redact(\"%s\")", strings.Join(redactValues, "\",\""))
|
||||
}
|
||||
|
||||
func (config *TapConfig) Validate() error {
|
||||
_, compileErr := regexp.Compile(config.PodRegexStr)
|
||||
if compileErr != nil {
|
||||
|
||||
@@ -4,7 +4,6 @@ go 1.17
|
||||
|
||||
require (
|
||||
github.com/creasty/defaults v1.5.2
|
||||
github.com/denisbrodbeck/machineid v1.0.1
|
||||
github.com/google/go-github/v37 v37.0.0
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/spf13/cobra v1.3.0
|
||||
|
||||
@@ -145,8 +145,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE=
|
||||
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
|
||||
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
|
||||
@@ -14,8 +14,6 @@ var (
|
||||
Platform = ""
|
||||
)
|
||||
|
||||
const DEVENVVAR = "MIZU_DISABLE_TELEMTRY"
|
||||
|
||||
func GetMizuFolderPath() string {
|
||||
home, homeDirErr := os.UserHomeDir()
|
||||
if homeDirErr != nil {
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -18,10 +17,6 @@ import (
|
||||
)
|
||||
|
||||
func CheckNewerVersion(versionChan chan string) {
|
||||
if _, present := os.LookupEnv(mizu.DEVENVVAR); present {
|
||||
versionChan <- ""
|
||||
return
|
||||
}
|
||||
logger.Log.Debugf("Checking for newer version...")
|
||||
start := time.Now()
|
||||
client := github.NewClient(nil)
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
package telemetry
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/denisbrodbeck/machineid"
|
||||
"github.com/up9inc/mizu/cli/apiserver"
|
||||
"github.com/up9inc/mizu/cli/config"
|
||||
"github.com/up9inc/mizu/cli/mizu"
|
||||
"github.com/up9inc/mizu/logger"
|
||||
)
|
||||
|
||||
const telemetryUrl = "https://us-east4-up9-prod.cloudfunctions.net/mizu-telemetry"
|
||||
|
||||
func ReportRun(cmd string, args interface{}) {
|
||||
if !shouldRunTelemetry() {
|
||||
logger.Log.Debug("not reporting telemetry")
|
||||
return
|
||||
}
|
||||
|
||||
argsBytes, _ := json.Marshal(args)
|
||||
argsMap := map[string]interface{}{
|
||||
"cmd": cmd,
|
||||
"args": string(argsBytes),
|
||||
}
|
||||
|
||||
if err := sendTelemetry(argsMap); err != nil {
|
||||
logger.Log.Debug(err)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Log.Debugf("successfully reported telemetry for cmd %v", cmd)
|
||||
}
|
||||
|
||||
func ReportTapTelemetry(apiProvider *apiserver.Provider, args interface{}, startTime time.Time) {
|
||||
if !shouldRunTelemetry() {
|
||||
logger.Log.Debug("not reporting telemetry")
|
||||
return
|
||||
}
|
||||
|
||||
generalStats, err := apiProvider.GetGeneralStats()
|
||||
if err != nil {
|
||||
logger.Log.Debugf("[ERROR] failed to get general stats from api server %v", err)
|
||||
return
|
||||
}
|
||||
argsBytes, _ := json.Marshal(args)
|
||||
argsMap := map[string]interface{}{
|
||||
"cmd": "tap",
|
||||
"args": string(argsBytes),
|
||||
"executionTimeInSeconds": int(time.Since(startTime).Seconds()),
|
||||
"apiCallsCount": generalStats["EntriesCount"],
|
||||
"trafficVolumeInGB": generalStats["EntriesVolumeInGB"],
|
||||
}
|
||||
|
||||
if err := sendTelemetry(argsMap); err != nil {
|
||||
logger.Log.Debug(err)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Log.Debug("successfully reported telemetry of tap command")
|
||||
}
|
||||
|
||||
func shouldRunTelemetry() bool {
|
||||
if _, present := os.LookupEnv(mizu.DEVENVVAR); present {
|
||||
return false
|
||||
}
|
||||
if !config.Config.Telemetry {
|
||||
return false
|
||||
}
|
||||
|
||||
return mizu.Branch == "main" || mizu.Branch == "develop"
|
||||
}
|
||||
|
||||
func sendTelemetry(argsMap map[string]interface{}) error {
|
||||
argsMap["component"] = "mizu_cli"
|
||||
argsMap["buildTimestamp"] = mizu.BuildTimestamp
|
||||
argsMap["branch"] = mizu.Branch
|
||||
argsMap["version"] = mizu.Ver
|
||||
argsMap["platform"] = mizu.Platform
|
||||
|
||||
if machineId, err := machineid.ProtectedID("mizu"); err == nil {
|
||||
argsMap["machineId"] = machineId
|
||||
}
|
||||
|
||||
jsonValue, _ := json.Marshal(argsMap)
|
||||
|
||||
if resp, err := http.Post(telemetryUrl, "application/json", bytes.NewBuffer(jsonValue)); err != nil {
|
||||
return fmt.Errorf("ERROR: failed sending telemetry, err: %v, response %v", err, resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -57,7 +57,7 @@ log "Writing output to $MIZU_BENCHMARK_OUTPUT_DIR"
|
||||
cd $MIZU_HOME || exit 1
|
||||
|
||||
export HOST_MODE=0
|
||||
export SENSITIVE_DATA_FILTERING_OPTIONS='{"EnableRedaction": false}'
|
||||
export SENSITIVE_DATA_FILTERING_OPTIONS='{}'
|
||||
export MIZU_DEBUG_DISABLE_PCAP=false
|
||||
export MIZU_DEBUG_DISABLE_TCP_REASSEMBLY=false
|
||||
export MIZU_DEBUG_DISABLE_TCP_STREAM=false
|
||||
|
||||
@@ -43,7 +43,6 @@ type TapperSyncerConfig struct {
|
||||
TapperResources shared.Resources
|
||||
ImagePullPolicy core.PullPolicy
|
||||
LogLevel logging.Level
|
||||
IgnoredUserAgents []string
|
||||
MizuApiFilteringOptions api.TrafficFilteringOptions
|
||||
MizuServiceAccountExists bool
|
||||
ServiceMesh bool
|
||||
|
||||
@@ -43,7 +43,6 @@ type MizuAgentConfig struct {
|
||||
AgentDatabasePath string `json:"agentDatabasePath"`
|
||||
ServiceMap bool `json:"serviceMap"`
|
||||
OAS OASConfig `json:"oas"`
|
||||
Telemetry bool `json:"telemetry"`
|
||||
}
|
||||
|
||||
type WebSocketMessageMetadata struct {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -25,6 +26,15 @@ func (protocol *ProtocolSummary) ToString() string {
|
||||
return fmt.Sprintf("%s?%s?%s", protocol.Name, protocol.Version, protocol.Abbreviation)
|
||||
}
|
||||
|
||||
func GetProtocolSummary(inputString string) *ProtocolSummary {
|
||||
splitted := strings.SplitN(inputString, "?", 3)
|
||||
return &ProtocolSummary{
|
||||
Name: splitted[0],
|
||||
Version: splitted[1],
|
||||
Abbreviation: splitted[2],
|
||||
}
|
||||
}
|
||||
|
||||
type Protocol struct {
|
||||
ProtocolSummary
|
||||
LongName string `json:"longName"`
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package api
|
||||
|
||||
type TrafficFilteringOptions struct {
|
||||
IgnoredUserAgents []string
|
||||
PlainTextMaskingRegexes []*SerializableRegexp
|
||||
EnableRedaction bool
|
||||
IgnoredUserAgents []string
|
||||
}
|
||||
|
||||
@@ -13,4 +13,4 @@ test-pull-bin:
|
||||
|
||||
test-pull-expect:
|
||||
@mkdir -p expect
|
||||
@[ "${skipexpect}" ] && echo "Skipping downloading expected JSONs" || gsutil -o 'GSUtil:parallel_process_count=5' -o 'GSUtil:parallel_thread_count=5' -m cp -r gs://static.up9.io/mizu/test-pcap/expect15/amqp/\* expect
|
||||
@[ "${skipexpect}" ] && echo "Skipping downloading expected JSONs" || gsutil -o 'GSUtil:parallel_process_count=5' -o 'GSUtil:parallel_thread_count=5' -m cp -r gs://static.up9.io/mizu/test-pcap/expect16/amqp/\* expect
|
||||
|
||||
@@ -4,16 +4,20 @@ go 1.17
|
||||
|
||||
require (
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/up9inc/mizu/logger v0.0.0
|
||||
github.com/up9inc/mizu/tap/api v0.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.0 // indirect
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/up9inc/mizu/tap/dbgctl v0.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
)
|
||||
|
||||
replace github.com/up9inc/mizu/logger v0.0.0 => ../../../logger
|
||||
|
||||
replace github.com/up9inc/mizu/tap/api v0.0.0 => ../../api
|
||||
|
||||
replace github.com/up9inc/mizu/tap/dbgctl v0.0.0 => ../../dbgctl
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/up9inc/mizu/logger"
|
||||
"github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
@@ -25,14 +25,14 @@ var connectionMethodMap = map[int]string{
|
||||
61: "connection unblocked",
|
||||
}
|
||||
|
||||
// var channelMethodMap = map[int]string{
|
||||
// 10: "channel open",
|
||||
// 11: "channel open-ok",
|
||||
// 20: "channel flow",
|
||||
// 21: "channel flow-ok",
|
||||
// 40: "channel close",
|
||||
// 41: "channel close-ok",
|
||||
// }
|
||||
var channelMethodMap = map[int]string{
|
||||
10: "channel open",
|
||||
11: "channel open-ok",
|
||||
20: "channel flow",
|
||||
21: "channel flow-ok",
|
||||
40: "channel close",
|
||||
41: "channel close-ok",
|
||||
}
|
||||
|
||||
var exchangeMethodMap = map[int]string{
|
||||
10: "exchange declare",
|
||||
@@ -94,29 +94,41 @@ type AMQPWrapper struct {
|
||||
Details interface{} `json:"details"`
|
||||
}
|
||||
|
||||
func emitAMQP(event interface{}, _type string, method string, connectionInfo *api.ConnectionInfo, captureTime time.Time, captureSize int, emitter api.Emitter, capture api.Capture) {
|
||||
request := &api.GenericMessage{
|
||||
IsRequest: true,
|
||||
CaptureTime: captureTime,
|
||||
Payload: AMQPPayload{
|
||||
Data: &AMQPWrapper{
|
||||
Method: method,
|
||||
Url: "",
|
||||
Details: event,
|
||||
},
|
||||
},
|
||||
type emptyResponse struct {
|
||||
}
|
||||
|
||||
const emptyMethod = "empty"
|
||||
|
||||
func getIdent(reader api.TcpReader, methodFrame *MethodFrame) (ident string) {
|
||||
tcpID := reader.GetTcpID()
|
||||
// To match methods to their Ok(s)
|
||||
methodId := methodFrame.MethodId - methodFrame.MethodId%10
|
||||
|
||||
if reader.GetIsClient() {
|
||||
ident = fmt.Sprintf(
|
||||
"%s_%s_%s_%s_%d_%d_%d",
|
||||
tcpID.SrcIP,
|
||||
tcpID.DstIP,
|
||||
tcpID.SrcPort,
|
||||
tcpID.DstPort,
|
||||
methodFrame.ChannelId,
|
||||
methodFrame.ClassId,
|
||||
methodId,
|
||||
)
|
||||
} else {
|
||||
ident = fmt.Sprintf(
|
||||
"%s_%s_%s_%s_%d_%d_%d",
|
||||
tcpID.DstIP,
|
||||
tcpID.SrcIP,
|
||||
tcpID.DstPort,
|
||||
tcpID.SrcPort,
|
||||
methodFrame.ChannelId,
|
||||
methodFrame.ClassId,
|
||||
methodId,
|
||||
)
|
||||
}
|
||||
item := &api.OutputChannelItem{
|
||||
Protocol: protocol,
|
||||
Capture: capture,
|
||||
Timestamp: captureTime.UnixNano() / int64(time.Millisecond),
|
||||
ConnectionInfo: connectionInfo,
|
||||
Pair: &api.RequestResponsePair{
|
||||
Request: *request,
|
||||
Response: api.GenericMessage{},
|
||||
},
|
||||
}
|
||||
emitter.Emit(item)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func representProperties(properties map[string]interface{}, rep []interface{}) ([]interface{}, string, string) {
|
||||
@@ -460,6 +472,36 @@ func representQueueDeclare(event map[string]interface{}) []interface{} {
|
||||
return rep
|
||||
}
|
||||
|
||||
func representQueueDeclareOk(event map[string]interface{}) []interface{} {
|
||||
rep := make([]interface{}, 0)
|
||||
|
||||
details, _ := json.Marshal([]api.TableData{
|
||||
{
|
||||
Name: "Queue",
|
||||
Value: event["queue"].(string),
|
||||
Selector: `response.queue`,
|
||||
},
|
||||
{
|
||||
Name: "Message Count",
|
||||
Value: fmt.Sprintf("%g", event["messageCount"].(float64)),
|
||||
Selector: `response.messageCount`,
|
||||
},
|
||||
{
|
||||
Name: "Consumer Count",
|
||||
Value: fmt.Sprintf("%g", event["consumerCount"].(float64)),
|
||||
Selector: `response.consumerCount`,
|
||||
},
|
||||
})
|
||||
|
||||
rep = append(rep, api.SectionData{
|
||||
Type: api.TABLE,
|
||||
Title: "Details",
|
||||
Data: string(details),
|
||||
})
|
||||
|
||||
return rep
|
||||
}
|
||||
|
||||
func representExchangeDeclare(event map[string]interface{}) []interface{} {
|
||||
rep := make([]interface{}, 0)
|
||||
|
||||
@@ -571,7 +613,7 @@ func representConnectionStart(event map[string]interface{}) []interface{} {
|
||||
x, _ := json.Marshal(value)
|
||||
outcome = string(x)
|
||||
default:
|
||||
panic("Unknown data type for the server property!")
|
||||
logger.Log.Info("Unknown data type for the server property!")
|
||||
}
|
||||
headers = append(headers, api.TableData{
|
||||
Name: name,
|
||||
@@ -593,6 +635,65 @@ func representConnectionStart(event map[string]interface{}) []interface{} {
|
||||
return rep
|
||||
}
|
||||
|
||||
func representConnectionStartOk(event map[string]interface{}) []interface{} {
|
||||
rep := make([]interface{}, 0)
|
||||
|
||||
details, _ := json.Marshal([]api.TableData{
|
||||
{
|
||||
Name: "Mechanism",
|
||||
Value: event["mechanism"].(string),
|
||||
Selector: `response.mechanism`,
|
||||
},
|
||||
{
|
||||
Name: "Mechanism",
|
||||
Value: event["mechanism"].(string),
|
||||
Selector: `response.response`,
|
||||
},
|
||||
{
|
||||
Name: "Locale",
|
||||
Value: event["locale"].(string),
|
||||
Selector: `response.locale`,
|
||||
},
|
||||
})
|
||||
rep = append(rep, api.SectionData{
|
||||
Type: api.TABLE,
|
||||
Title: "Details",
|
||||
Data: string(details),
|
||||
})
|
||||
|
||||
if event["clientProperties"] != nil {
|
||||
headers := make([]api.TableData, 0)
|
||||
for name, value := range event["clientProperties"].(map[string]interface{}) {
|
||||
var outcome string
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
outcome = v
|
||||
case map[string]interface{}:
|
||||
x, _ := json.Marshal(value)
|
||||
outcome = string(x)
|
||||
default:
|
||||
logger.Log.Info("Unknown data type for the client property!")
|
||||
}
|
||||
headers = append(headers, api.TableData{
|
||||
Name: name,
|
||||
Value: outcome,
|
||||
Selector: fmt.Sprintf(`response.clientProperties["%s"]`, name),
|
||||
})
|
||||
}
|
||||
sort.Slice(headers, func(i, j int) bool {
|
||||
return headers[i].Name < headers[j].Name
|
||||
})
|
||||
headersMarshaled, _ := json.Marshal(headers)
|
||||
rep = append(rep, api.SectionData{
|
||||
Type: api.TABLE,
|
||||
Title: "Client Properties",
|
||||
Data: string(headersMarshaled),
|
||||
})
|
||||
}
|
||||
|
||||
return rep
|
||||
}
|
||||
|
||||
func representConnectionClose(event map[string]interface{}) []interface{} {
|
||||
replyCode := ""
|
||||
|
||||
@@ -750,3 +851,122 @@ func representBasicConsume(event map[string]interface{}) []interface{} {
|
||||
|
||||
return rep
|
||||
}
|
||||
|
||||
func representBasicConsumeOk(event map[string]interface{}) []interface{} {
|
||||
rep := make([]interface{}, 0)
|
||||
|
||||
details, _ := json.Marshal([]api.TableData{
|
||||
{
|
||||
Name: "Consumer Tag",
|
||||
Value: event["consumerTag"].(string),
|
||||
Selector: `response.consumerTag`,
|
||||
},
|
||||
})
|
||||
|
||||
rep = append(rep, api.SectionData{
|
||||
Type: api.TABLE,
|
||||
Title: "Details",
|
||||
Data: string(details),
|
||||
})
|
||||
|
||||
return rep
|
||||
}
|
||||
|
||||
func representConnectionOpen(event map[string]interface{}) []interface{} {
|
||||
rep := make([]interface{}, 0)
|
||||
|
||||
details, _ := json.Marshal([]api.TableData{
|
||||
{
|
||||
Name: "Virtual Host",
|
||||
Value: event["virtualHost"].(string),
|
||||
Selector: `request.virtualHost`,
|
||||
},
|
||||
})
|
||||
rep = append(rep, api.SectionData{
|
||||
Type: api.TABLE,
|
||||
Title: "Details",
|
||||
Data: string(details),
|
||||
})
|
||||
|
||||
return rep
|
||||
}
|
||||
|
||||
func representConnectionTune(event map[string]interface{}) []interface{} {
|
||||
rep := make([]interface{}, 0)
|
||||
|
||||
details, _ := json.Marshal([]api.TableData{
|
||||
{
|
||||
Name: "Channel Max",
|
||||
Value: fmt.Sprintf("%g", event["channelMax"].(float64)),
|
||||
Selector: `request.channelMax`,
|
||||
},
|
||||
{
|
||||
Name: "Frame Max",
|
||||
Value: fmt.Sprintf("%g", event["frameMax"].(float64)),
|
||||
Selector: `request.frameMax`,
|
||||
},
|
||||
{
|
||||
Name: "Heartbeat",
|
||||
Value: fmt.Sprintf("%g", event["heartbeat"].(float64)),
|
||||
Selector: `request.heartbeat`,
|
||||
},
|
||||
})
|
||||
rep = append(rep, api.SectionData{
|
||||
Type: api.TABLE,
|
||||
Title: "Details",
|
||||
Data: string(details),
|
||||
})
|
||||
|
||||
return rep
|
||||
}
|
||||
|
||||
func representBasicCancel(event map[string]interface{}) []interface{} {
|
||||
rep := make([]interface{}, 0)
|
||||
|
||||
details, _ := json.Marshal([]api.TableData{
|
||||
{
|
||||
Name: "Consumer Tag",
|
||||
Value: event["consumerTag"].(string),
|
||||
Selector: `response.consumerTag`,
|
||||
},
|
||||
{
|
||||
Name: "NoWait",
|
||||
Value: strconv.FormatBool(event["noWait"].(bool)),
|
||||
Selector: `request.noWait`,
|
||||
},
|
||||
})
|
||||
|
||||
rep = append(rep, api.SectionData{
|
||||
Type: api.TABLE,
|
||||
Title: "Details",
|
||||
Data: string(details),
|
||||
})
|
||||
|
||||
return rep
|
||||
}
|
||||
|
||||
func representBasicCancelOk(event map[string]interface{}) []interface{} {
|
||||
rep := make([]interface{}, 0)
|
||||
|
||||
details, _ := json.Marshal([]api.TableData{
|
||||
{
|
||||
Name: "Consumer Tag",
|
||||
Value: event["consumerTag"].(string),
|
||||
Selector: `response.consumerTag`,
|
||||
},
|
||||
})
|
||||
|
||||
rep = append(rep, api.SectionData{
|
||||
Type: api.TABLE,
|
||||
Title: "Details",
|
||||
Data: string(details),
|
||||
})
|
||||
|
||||
return rep
|
||||
}
|
||||
|
||||
func representEmpty(event map[string]interface{}) []interface{} {
|
||||
rep := make([]interface{}, 0)
|
||||
|
||||
return rep
|
||||
}
|
||||
|
||||
@@ -46,22 +46,12 @@ func (d dissecting) Ping() {
|
||||
log.Printf("pong %s", protocol.Name)
|
||||
}
|
||||
|
||||
const amqpRequest string = "amqp_request"
|
||||
|
||||
func (d dissecting) Dissect(b *bufio.Reader, reader api.TcpReader, options *api.TrafficFilteringOptions) error {
|
||||
r := AmqpReader{b}
|
||||
|
||||
var remaining int
|
||||
var header *HeaderFrame
|
||||
|
||||
connectionInfo := &api.ConnectionInfo{
|
||||
ClientIP: reader.GetTcpID().SrcIP,
|
||||
ClientPort: reader.GetTcpID().SrcPort,
|
||||
ServerIP: reader.GetTcpID().DstIP,
|
||||
ServerPort: reader.GetTcpID().DstPort,
|
||||
IsOutgoing: true,
|
||||
}
|
||||
|
||||
eventBasicPublish := &BasicPublish{
|
||||
Exchange: "",
|
||||
RoutingKey: "",
|
||||
@@ -83,6 +73,10 @@ func (d dissecting) Dissect(b *bufio.Reader, reader api.TcpReader, options *api.
|
||||
|
||||
var lastMethodFrameMessage Message
|
||||
|
||||
var ident string
|
||||
isClient := reader.GetIsClient()
|
||||
reqResMatcher := reader.GetReqResMatcher().(*requestResponseMatcher)
|
||||
|
||||
for {
|
||||
frameVal, err := r.readFrame()
|
||||
if err == io.EOF {
|
||||
@@ -121,16 +115,22 @@ func (d dissecting) Dissect(b *bufio.Reader, reader api.TcpReader, options *api.
|
||||
switch lastMethodFrameMessage.(type) {
|
||||
case *BasicPublish:
|
||||
eventBasicPublish.Body = f.Body
|
||||
emitAMQP(*eventBasicPublish, amqpRequest, basicMethodMap[40], connectionInfo, reader.GetCaptureTime(), reader.GetReadProgress().Current(), reader.GetEmitter(), reader.GetParent().GetOrigin())
|
||||
reqResMatcher.emitEvent(isClient, ident, basicMethodMap[40], *eventBasicPublish, reader)
|
||||
reqResMatcher.emitEvent(!isClient, ident, emptyMethod, &emptyResponse{}, reader)
|
||||
|
||||
case *BasicDeliver:
|
||||
eventBasicDeliver.Body = f.Body
|
||||
emitAMQP(*eventBasicDeliver, amqpRequest, basicMethodMap[60], connectionInfo, reader.GetCaptureTime(), reader.GetReadProgress().Current(), reader.GetEmitter(), reader.GetParent().GetOrigin())
|
||||
reqResMatcher.emitEvent(!isClient, ident, basicMethodMap[60], *eventBasicDeliver, reader)
|
||||
reqResMatcher.emitEvent(isClient, ident, emptyMethod, &emptyResponse{}, reader)
|
||||
}
|
||||
|
||||
case *MethodFrame:
|
||||
reader.GetParent().SetProtocol(&protocol)
|
||||
|
||||
lastMethodFrameMessage = f.Method
|
||||
|
||||
ident = getIdent(reader, f)
|
||||
|
||||
switch m := f.Method.(type) {
|
||||
case *BasicPublish:
|
||||
eventBasicPublish.Exchange = m.Exchange
|
||||
@@ -146,7 +146,10 @@ func (d dissecting) Dissect(b *bufio.Reader, reader api.TcpReader, options *api.
|
||||
NoWait: m.NoWait,
|
||||
Arguments: m.Arguments,
|
||||
}
|
||||
emitAMQP(*eventQueueBind, amqpRequest, queueMethodMap[20], connectionInfo, reader.GetCaptureTime(), reader.GetReadProgress().Current(), reader.GetEmitter(), reader.GetParent().GetOrigin())
|
||||
reqResMatcher.emitEvent(isClient, ident, queueMethodMap[20], *eventQueueBind, reader)
|
||||
|
||||
case *QueueBindOk:
|
||||
reqResMatcher.emitEvent(isClient, ident, queueMethodMap[21], m, reader)
|
||||
|
||||
case *BasicConsume:
|
||||
eventBasicConsume := &BasicConsume{
|
||||
@@ -158,7 +161,10 @@ func (d dissecting) Dissect(b *bufio.Reader, reader api.TcpReader, options *api.
|
||||
NoWait: m.NoWait,
|
||||
Arguments: m.Arguments,
|
||||
}
|
||||
emitAMQP(*eventBasicConsume, amqpRequest, basicMethodMap[20], connectionInfo, reader.GetCaptureTime(), reader.GetReadProgress().Current(), reader.GetEmitter(), reader.GetParent().GetOrigin())
|
||||
reqResMatcher.emitEvent(isClient, ident, basicMethodMap[20], *eventBasicConsume, reader)
|
||||
|
||||
case *BasicConsumeOk:
|
||||
reqResMatcher.emitEvent(isClient, ident, basicMethodMap[21], m, reader)
|
||||
|
||||
case *BasicDeliver:
|
||||
eventBasicDeliver.ConsumerTag = m.ConsumerTag
|
||||
@@ -177,7 +183,10 @@ func (d dissecting) Dissect(b *bufio.Reader, reader api.TcpReader, options *api.
|
||||
NoWait: m.NoWait,
|
||||
Arguments: m.Arguments,
|
||||
}
|
||||
emitAMQP(*eventQueueDeclare, amqpRequest, queueMethodMap[10], connectionInfo, reader.GetCaptureTime(), reader.GetReadProgress().Current(), reader.GetEmitter(), reader.GetParent().GetOrigin())
|
||||
reqResMatcher.emitEvent(isClient, ident, queueMethodMap[10], *eventQueueDeclare, reader)
|
||||
|
||||
case *QueueDeclareOk:
|
||||
reqResMatcher.emitEvent(isClient, ident, queueMethodMap[11], m, reader)
|
||||
|
||||
case *ExchangeDeclare:
|
||||
eventExchangeDeclare := &ExchangeDeclare{
|
||||
@@ -190,17 +199,19 @@ func (d dissecting) Dissect(b *bufio.Reader, reader api.TcpReader, options *api.
|
||||
NoWait: m.NoWait,
|
||||
Arguments: m.Arguments,
|
||||
}
|
||||
emitAMQP(*eventExchangeDeclare, amqpRequest, exchangeMethodMap[10], connectionInfo, reader.GetCaptureTime(), reader.GetReadProgress().Current(), reader.GetEmitter(), reader.GetParent().GetOrigin())
|
||||
reqResMatcher.emitEvent(isClient, ident, exchangeMethodMap[10], *eventExchangeDeclare, reader)
|
||||
|
||||
case *ExchangeDeclareOk:
|
||||
reqResMatcher.emitEvent(isClient, ident, exchangeMethodMap[11], m, reader)
|
||||
|
||||
case *ConnectionStart:
|
||||
eventConnectionStart := &ConnectionStart{
|
||||
VersionMajor: m.VersionMajor,
|
||||
VersionMinor: m.VersionMinor,
|
||||
ServerProperties: m.ServerProperties,
|
||||
Mechanisms: m.Mechanisms,
|
||||
Locales: m.Locales,
|
||||
}
|
||||
emitAMQP(*eventConnectionStart, amqpRequest, connectionMethodMap[10], connectionInfo, reader.GetCaptureTime(), reader.GetReadProgress().Current(), reader.GetEmitter(), reader.GetParent().GetOrigin())
|
||||
// In our tests, *ConnectionStart does not result in *ConnectionStartOk
|
||||
reqResMatcher.emitEvent(!isClient, ident, connectionMethodMap[10], m, reader)
|
||||
reqResMatcher.emitEvent(isClient, ident, emptyMethod, &emptyResponse{}, reader)
|
||||
|
||||
case *ConnectionStartOk:
|
||||
// In our tests, *ConnectionStart does not result in *ConnectionStartOk
|
||||
reqResMatcher.emitEvent(isClient, ident, connectionMethodMap[11], m, reader)
|
||||
|
||||
case *ConnectionClose:
|
||||
eventConnectionClose := &ConnectionClose{
|
||||
@@ -209,7 +220,40 @@ func (d dissecting) Dissect(b *bufio.Reader, reader api.TcpReader, options *api.
|
||||
ClassId: m.ClassId,
|
||||
MethodId: m.MethodId,
|
||||
}
|
||||
emitAMQP(*eventConnectionClose, amqpRequest, connectionMethodMap[50], connectionInfo, reader.GetCaptureTime(), reader.GetReadProgress().Current(), reader.GetEmitter(), reader.GetParent().GetOrigin())
|
||||
reqResMatcher.emitEvent(isClient, ident, connectionMethodMap[50], *eventConnectionClose, reader)
|
||||
|
||||
case *ConnectionCloseOk:
|
||||
reqResMatcher.emitEvent(isClient, ident, connectionMethodMap[51], m, reader)
|
||||
|
||||
case *connectionOpen:
|
||||
eventConnectionOpen := &connectionOpen{
|
||||
VirtualHost: m.VirtualHost,
|
||||
}
|
||||
reqResMatcher.emitEvent(isClient, ident, connectionMethodMap[40], *eventConnectionOpen, reader)
|
||||
|
||||
case *connectionOpenOk:
|
||||
reqResMatcher.emitEvent(isClient, ident, connectionMethodMap[41], m, reader)
|
||||
|
||||
case *channelOpen:
|
||||
reqResMatcher.emitEvent(isClient, ident, channelMethodMap[10], m, reader)
|
||||
|
||||
case *channelOpenOk:
|
||||
reqResMatcher.emitEvent(isClient, ident, channelMethodMap[11], m, reader)
|
||||
|
||||
case *connectionTune:
|
||||
// In our tests, *connectionTune does not result in *connectionTuneOk
|
||||
reqResMatcher.emitEvent(!isClient, ident, connectionMethodMap[30], m, reader)
|
||||
reqResMatcher.emitEvent(isClient, ident, emptyMethod, &emptyResponse{}, reader)
|
||||
|
||||
case *connectionTuneOk:
|
||||
// In our tests, *connectionTune does not result in *connectionTuneOk
|
||||
reqResMatcher.emitEvent(isClient, ident, connectionMethodMap[31], m, reader)
|
||||
|
||||
case *basicCancel:
|
||||
reqResMatcher.emitEvent(isClient, ident, basicMethodMap[30], m, reader)
|
||||
|
||||
case *basicCancelOk:
|
||||
reqResMatcher.emitEvent(isClient, ident, basicMethodMap[31], m, reader)
|
||||
}
|
||||
|
||||
default:
|
||||
@@ -220,9 +264,17 @@ func (d dissecting) Dissect(b *bufio.Reader, reader api.TcpReader, options *api.
|
||||
|
||||
func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string, resolvedDestination string, namespace string) *api.Entry {
|
||||
request := item.Pair.Request.Payload.(map[string]interface{})
|
||||
response := item.Pair.Response.Payload.(map[string]interface{})
|
||||
reqDetails := request["details"].(map[string]interface{})
|
||||
resDetails := response["details"].(map[string]interface{})
|
||||
|
||||
elapsedTime := item.Pair.Response.CaptureTime.Sub(item.Pair.Request.CaptureTime).Round(time.Millisecond).Milliseconds()
|
||||
if elapsedTime < 0 {
|
||||
elapsedTime = 0
|
||||
}
|
||||
|
||||
reqDetails["method"] = request["method"]
|
||||
resDetails["method"] = response["method"]
|
||||
return &api.Entry{
|
||||
Protocol: protocol.ProtocolSummary,
|
||||
Capture: item.Capture,
|
||||
@@ -236,13 +288,15 @@ func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string,
|
||||
IP: item.ConnectionInfo.ServerIP,
|
||||
Port: item.ConnectionInfo.ServerPort,
|
||||
},
|
||||
Namespace: namespace,
|
||||
Outgoing: item.ConnectionInfo.IsOutgoing,
|
||||
Request: reqDetails,
|
||||
RequestSize: item.Pair.Request.CaptureSize,
|
||||
Timestamp: item.Timestamp,
|
||||
StartTime: item.Pair.Request.CaptureTime,
|
||||
ElapsedTime: 0,
|
||||
Namespace: namespace,
|
||||
Outgoing: item.ConnectionInfo.IsOutgoing,
|
||||
Request: reqDetails,
|
||||
Response: resDetails,
|
||||
RequestSize: item.Pair.Request.CaptureSize,
|
||||
ResponseSize: item.Pair.Response.CaptureSize,
|
||||
Timestamp: item.Timestamp,
|
||||
StartTime: item.Pair.Request.CaptureTime,
|
||||
ElapsedTime: elapsedTime,
|
||||
}
|
||||
|
||||
}
|
||||
@@ -283,6 +337,21 @@ func (d dissecting) Summarize(entry *api.Entry) *api.BaseEntry {
|
||||
case basicMethodMap[20]:
|
||||
summary = entry.Request["queue"].(string)
|
||||
summaryQuery = fmt.Sprintf(`request.queue == "%s"`, summary)
|
||||
case connectionMethodMap[40]:
|
||||
summary = entry.Request["virtualHost"].(string)
|
||||
summaryQuery = fmt.Sprintf(`request.virtualHost == "%s"`, summary)
|
||||
case connectionMethodMap[30]:
|
||||
summary = fmt.Sprintf("%g", entry.Request["channelMax"].(float64))
|
||||
summaryQuery = fmt.Sprintf(`request.channelMax == "%s"`, summary)
|
||||
case connectionMethodMap[31]:
|
||||
summary = fmt.Sprintf("%g", entry.Request["channelMax"].(float64))
|
||||
summaryQuery = fmt.Sprintf(`request.channelMax == "%s"`, summary)
|
||||
case basicMethodMap[30]:
|
||||
summary = entry.Request["consumerTag"].(string)
|
||||
summaryQuery = fmt.Sprintf(`request.consumerTag == "%s"`, summary)
|
||||
case basicMethodMap[31]:
|
||||
summary = entry.Request["consumerTag"].(string)
|
||||
summaryQuery = fmt.Sprintf(`request.consumerTag == "%s"`, summary)
|
||||
}
|
||||
|
||||
return &api.BaseEntry{
|
||||
@@ -306,6 +375,8 @@ func (d dissecting) Summarize(entry *api.Entry) *api.BaseEntry {
|
||||
func (d dissecting) Represent(request map[string]interface{}, response map[string]interface{}) (object []byte, err error) {
|
||||
representation := make(map[string]interface{})
|
||||
var repRequest []interface{}
|
||||
var repResponse []interface{}
|
||||
|
||||
switch request["method"].(string) {
|
||||
case basicMethodMap[40]:
|
||||
repRequest = representBasicPublish(request)
|
||||
@@ -323,9 +394,45 @@ func (d dissecting) Represent(request map[string]interface{}, response map[strin
|
||||
repRequest = representQueueBind(request)
|
||||
case basicMethodMap[20]:
|
||||
repRequest = representBasicConsume(request)
|
||||
case connectionMethodMap[40]:
|
||||
repRequest = representConnectionOpen(request)
|
||||
case channelMethodMap[10]:
|
||||
repRequest = representEmpty(request)
|
||||
case connectionMethodMap[30]:
|
||||
repRequest = representConnectionTune(request)
|
||||
case basicMethodMap[30]:
|
||||
repRequest = representBasicCancel(request)
|
||||
}
|
||||
|
||||
switch response["method"].(string) {
|
||||
case queueMethodMap[11]:
|
||||
repResponse = representQueueDeclareOk(response)
|
||||
case exchangeMethodMap[11]:
|
||||
repResponse = representEmpty(response)
|
||||
case connectionMethodMap[11]:
|
||||
repResponse = representConnectionStartOk(response)
|
||||
case connectionMethodMap[51]:
|
||||
repResponse = representEmpty(response)
|
||||
case basicMethodMap[21]:
|
||||
repResponse = representBasicConsumeOk(response)
|
||||
case queueMethodMap[21]:
|
||||
repResponse = representEmpty(response)
|
||||
case connectionMethodMap[41]:
|
||||
repResponse = representEmpty(response)
|
||||
case channelMethodMap[11]:
|
||||
repResponse = representEmpty(request)
|
||||
case connectionMethodMap[31]:
|
||||
repResponse = representConnectionTune(request)
|
||||
case basicMethodMap[31]:
|
||||
repResponse = representBasicCancelOk(request)
|
||||
case emptyMethod:
|
||||
repResponse = representEmpty(response)
|
||||
}
|
||||
|
||||
representation["request"] = repRequest
|
||||
representation["response"] = repResponse
|
||||
object, err = json.Marshal(representation)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -336,7 +443,7 @@ func (d dissecting) Macros() map[string]string {
|
||||
}
|
||||
|
||||
func (d dissecting) NewResponseRequestMatcher() api.RequestResponseMatcher {
|
||||
return nil
|
||||
return createResponseRequestMatcher()
|
||||
}
|
||||
|
||||
var Dissector dissecting
|
||||
|
||||
113
tap/extensions/amqp/matcher.go
Normal file
113
tap/extensions/amqp/matcher.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package amqp
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
// Key is {client_addr}_{client_port}_{dest_addr}_{dest_port}_{channel_id}_{class_id}_{method_id}
|
||||
type requestResponseMatcher struct {
|
||||
openMessagesMap *sync.Map
|
||||
}
|
||||
|
||||
func createResponseRequestMatcher() api.RequestResponseMatcher {
|
||||
return &requestResponseMatcher{openMessagesMap: &sync.Map{}}
|
||||
}
|
||||
|
||||
func (matcher *requestResponseMatcher) GetMap() *sync.Map {
|
||||
return matcher.openMessagesMap
|
||||
}
|
||||
|
||||
func (matcher *requestResponseMatcher) SetMaxTry(value int) {
|
||||
}
|
||||
|
||||
func (matcher *requestResponseMatcher) emitEvent(isRequest bool, ident string, method string, event interface{}, reader api.TcpReader) {
|
||||
reader.GetParent().SetProtocol(&protocol)
|
||||
|
||||
var item *api.OutputChannelItem
|
||||
if isRequest {
|
||||
item = matcher.registerRequest(ident, method, event, reader.GetCaptureTime(), reader.GetReadProgress().Current())
|
||||
} else {
|
||||
item = matcher.registerResponse(ident, method, event, reader.GetCaptureTime(), reader.GetReadProgress().Current())
|
||||
}
|
||||
|
||||
if item != nil {
|
||||
item.ConnectionInfo = &api.ConnectionInfo{
|
||||
ClientIP: reader.GetTcpID().SrcIP,
|
||||
ClientPort: reader.GetTcpID().SrcPort,
|
||||
ServerIP: reader.GetTcpID().DstIP,
|
||||
ServerPort: reader.GetTcpID().DstPort,
|
||||
IsOutgoing: true,
|
||||
}
|
||||
item.Capture = reader.GetParent().GetOrigin()
|
||||
reader.GetEmitter().Emit(item)
|
||||
}
|
||||
}
|
||||
|
||||
func (matcher *requestResponseMatcher) registerRequest(ident string, method string, request interface{}, captureTime time.Time, captureSize int) *api.OutputChannelItem {
|
||||
requestAMQPMessage := api.GenericMessage{
|
||||
IsRequest: true,
|
||||
CaptureTime: captureTime,
|
||||
CaptureSize: captureSize,
|
||||
Payload: AMQPPayload{
|
||||
Data: &AMQPWrapper{
|
||||
Method: method,
|
||||
Url: "",
|
||||
Details: request,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if response, found := matcher.openMessagesMap.LoadAndDelete(ident); found {
|
||||
// Type assertion always succeeds because all of the map's values are of api.GenericMessage type
|
||||
responseAMQPMessage := response.(*api.GenericMessage)
|
||||
if responseAMQPMessage.IsRequest {
|
||||
return nil
|
||||
}
|
||||
return matcher.preparePair(&requestAMQPMessage, responseAMQPMessage)
|
||||
}
|
||||
|
||||
matcher.openMessagesMap.Store(ident, &requestAMQPMessage)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (matcher *requestResponseMatcher) registerResponse(ident string, method string, response interface{}, captureTime time.Time, captureSize int) *api.OutputChannelItem {
|
||||
responseAMQPMessage := api.GenericMessage{
|
||||
IsRequest: false,
|
||||
CaptureTime: captureTime,
|
||||
CaptureSize: captureSize,
|
||||
Payload: AMQPPayload{
|
||||
Data: &AMQPWrapper{
|
||||
Method: method,
|
||||
Url: "",
|
||||
Details: response,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if request, found := matcher.openMessagesMap.LoadAndDelete(ident); found {
|
||||
// Type assertion always succeeds because all of the map's values are of api.GenericMessage type
|
||||
requestAMQPMessage := request.(*api.GenericMessage)
|
||||
if !requestAMQPMessage.IsRequest {
|
||||
return nil
|
||||
}
|
||||
return matcher.preparePair(requestAMQPMessage, &responseAMQPMessage)
|
||||
}
|
||||
|
||||
matcher.openMessagesMap.Store(ident, &responseAMQPMessage)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (matcher *requestResponseMatcher) preparePair(requestAMQPMessage *api.GenericMessage, responseAMQPMessage *api.GenericMessage) *api.OutputChannelItem {
|
||||
return &api.OutputChannelItem{
|
||||
Protocol: protocol,
|
||||
Timestamp: requestAMQPMessage.CaptureTime.UnixNano() / int64(time.Millisecond),
|
||||
ConnectionInfo: nil,
|
||||
Pair: &api.RequestResponsePair{
|
||||
Request: *requestAMQPMessage,
|
||||
Response: *responseAMQPMessage,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -81,10 +81,10 @@ func (msg *ConnectionStart) read(r io.Reader) (err error) {
|
||||
}
|
||||
|
||||
type ConnectionStartOk struct {
|
||||
ClientProperties Table
|
||||
Mechanism string
|
||||
Response string
|
||||
Locale string
|
||||
ClientProperties Table `json:"clientProperties"`
|
||||
Mechanism string `json:"mechanism"`
|
||||
Response string `json:"response"`
|
||||
Locale string `json:"locale"`
|
||||
}
|
||||
|
||||
func (msg *ConnectionStartOk) read(r io.Reader) (err error) {
|
||||
@@ -135,9 +135,9 @@ func (msg *connectionSecureOk) read(r io.Reader) (err error) {
|
||||
}
|
||||
|
||||
type connectionTune struct {
|
||||
ChannelMax uint16
|
||||
FrameMax uint32
|
||||
Heartbeat uint16
|
||||
ChannelMax uint16 `json:"channelMax"`
|
||||
FrameMax uint32 `json:"frameMax"`
|
||||
Heartbeat uint16 `json:"heartbeat"`
|
||||
}
|
||||
|
||||
func (msg *connectionTune) read(r io.Reader) (err error) {
|
||||
@@ -181,7 +181,7 @@ func (msg *connectionTuneOk) read(r io.Reader) (err error) {
|
||||
}
|
||||
|
||||
type connectionOpen struct {
|
||||
VirtualHost string
|
||||
VirtualHost string `json:"virtualHost"`
|
||||
reserved1 string
|
||||
reserved2 bool
|
||||
}
|
||||
@@ -580,9 +580,9 @@ func (msg *QueueDeclare) read(r io.Reader) (err error) {
|
||||
}
|
||||
|
||||
type QueueDeclareOk struct {
|
||||
Queue string
|
||||
MessageCount uint32
|
||||
ConsumerCount uint32
|
||||
Queue string `json:"queue"`
|
||||
MessageCount uint32 `json:"messageCount"`
|
||||
ConsumerCount uint32 `json:"consumerCount"`
|
||||
}
|
||||
|
||||
func (msg *QueueDeclareOk) read(r io.Reader) (err error) {
|
||||
@@ -840,7 +840,7 @@ func (msg *BasicConsume) read(r io.Reader) (err error) {
|
||||
}
|
||||
|
||||
type BasicConsumeOk struct {
|
||||
ConsumerTag string
|
||||
ConsumerTag string `json:"consumerTag"`
|
||||
}
|
||||
|
||||
func (msg *BasicConsumeOk) read(r io.Reader) (err error) {
|
||||
@@ -853,8 +853,8 @@ func (msg *BasicConsumeOk) read(r io.Reader) (err error) {
|
||||
}
|
||||
|
||||
type basicCancel struct {
|
||||
ConsumerTag string
|
||||
NoWait bool
|
||||
ConsumerTag string `json:"consumerTag"`
|
||||
NoWait bool `json:"noWait"`
|
||||
}
|
||||
|
||||
func (msg *basicCancel) read(r io.Reader) (err error) {
|
||||
@@ -873,7 +873,7 @@ func (msg *basicCancel) read(r io.Reader) (err error) {
|
||||
}
|
||||
|
||||
type basicCancelOk struct {
|
||||
ConsumerTag string
|
||||
ConsumerTag string `json:"consumerTag"`
|
||||
}
|
||||
|
||||
func (msg *basicCancelOk) read(r io.Reader) (err error) {
|
||||
|
||||
@@ -13,4 +13,4 @@ test-pull-bin:
|
||||
|
||||
test-pull-expect:
|
||||
@mkdir -p expect
|
||||
@[ "${skipexpect}" ] && echo "Skipping downloading expected JSONs" || gsutil -o 'GSUtil:parallel_process_count=5' -o 'GSUtil:parallel_thread_count=5' -m cp -r gs://static.up9.io/mizu/test-pcap/expect15/http/\* expect
|
||||
@[ "${skipexpect}" ] && echo "Skipping downloading expected JSONs" || gsutil -o 'GSUtil:parallel_process_count=5' -o 'GSUtil:parallel_thread_count=5' -m cp -r gs://static.up9.io/mizu/test-pcap/expect16/http/\* expect
|
||||
|
||||
@@ -18,10 +18,6 @@ func filterAndEmit(item *api.OutputChannelItem, emitter api.Emitter, options *ap
|
||||
return
|
||||
}
|
||||
|
||||
if options.EnableRedaction {
|
||||
FilterSensitiveData(item, options)
|
||||
}
|
||||
|
||||
replaceForwardedFor(item)
|
||||
|
||||
emitter.Emit(item)
|
||||
|
||||
@@ -6,13 +6,16 @@ import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
func mapSliceRebuildAsMap(mapSlice []interface{}) (newMap map[string]interface{}) {
|
||||
newMap = make(map[string]interface{})
|
||||
for _, item := range mapSlice {
|
||||
|
||||
mergedMapSlice := mapSliceMergeRepeatedKeys(mapSlice)
|
||||
for _, item := range mergedMapSlice {
|
||||
h := item.(map[string]interface{})
|
||||
newMap[h["name"].(string)] = h["value"]
|
||||
}
|
||||
@@ -20,6 +23,28 @@ func mapSliceRebuildAsMap(mapSlice []interface{}) (newMap map[string]interface{}
|
||||
return
|
||||
}
|
||||
|
||||
func mapSliceRebuildAsMergedMap(mapSlice []interface{}) (newMap map[string]interface{}) {
|
||||
newMap = make(map[string]interface{})
|
||||
|
||||
mergedMapSlice := mapSliceMergeRepeatedKeys(mapSlice)
|
||||
for _, item := range mergedMapSlice {
|
||||
h := item.(map[string]interface{})
|
||||
|
||||
if valuesInterface, ok := h["value"].([]interface{}); ok {
|
||||
var values []string
|
||||
for _, valueInterface := range valuesInterface {
|
||||
values = append(values, valueInterface.(string))
|
||||
}
|
||||
|
||||
newMap[h["name"].(string)] = strings.Join(values, ",")
|
||||
} else {
|
||||
newMap[h["name"].(string)] = h["value"]
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func mapSliceMergeRepeatedKeys(mapSlice []interface{}) (newMapSlice []interface{}) {
|
||||
newMapSlice = make([]interface{}, 0)
|
||||
valuesMap := make(map[string][]interface{})
|
||||
@@ -47,6 +72,24 @@ func mapSliceMergeRepeatedKeys(mapSlice []interface{}) (newMapSlice []interface{
|
||||
return
|
||||
}
|
||||
|
||||
func representMapAsTable(mapToTable map[string]interface{}, selectorPrefix string) (representation string) {
|
||||
var table []api.TableData
|
||||
|
||||
keys := make([]string, 0, len(mapToTable))
|
||||
for k := range mapToTable {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, key := range keys {
|
||||
table = append(table, createTableForKey(key, mapToTable[key], selectorPrefix)...)
|
||||
}
|
||||
|
||||
obj, _ := json.Marshal(table)
|
||||
representation = string(obj)
|
||||
return
|
||||
}
|
||||
|
||||
func representMapSliceAsTable(mapSlice []interface{}, selectorPrefix string) (representation string) {
|
||||
var table []api.TableData
|
||||
for _, item := range mapSlice {
|
||||
@@ -54,34 +97,7 @@ func representMapSliceAsTable(mapSlice []interface{}, selectorPrefix string) (re
|
||||
key := h["name"].(string)
|
||||
value := h["value"]
|
||||
|
||||
var reflectKind reflect.Kind
|
||||
reflectType := reflect.TypeOf(value)
|
||||
if reflectType == nil {
|
||||
reflectKind = reflect.Interface
|
||||
} else {
|
||||
reflectKind = reflect.TypeOf(value).Kind()
|
||||
}
|
||||
|
||||
switch reflectKind {
|
||||
case reflect.Slice:
|
||||
fallthrough
|
||||
case reflect.Array:
|
||||
for i, el := range value.([]interface{}) {
|
||||
selector := fmt.Sprintf("%s.%s[%d]", selectorPrefix, key, i)
|
||||
table = append(table, api.TableData{
|
||||
Name: fmt.Sprintf("%s [%d]", key, i),
|
||||
Value: el,
|
||||
Selector: selector,
|
||||
})
|
||||
}
|
||||
default:
|
||||
selector := fmt.Sprintf("%s[\"%s\"]", selectorPrefix, key)
|
||||
table = append(table, api.TableData{
|
||||
Name: key,
|
||||
Value: value,
|
||||
Selector: selector,
|
||||
})
|
||||
}
|
||||
table = append(table, createTableForKey(key, value, selectorPrefix)...)
|
||||
}
|
||||
|
||||
obj, _ := json.Marshal(table)
|
||||
@@ -89,6 +105,41 @@ func representMapSliceAsTable(mapSlice []interface{}, selectorPrefix string) (re
|
||||
return
|
||||
}
|
||||
|
||||
func createTableForKey(key string, value interface{}, selectorPrefix string) []api.TableData {
|
||||
var table []api.TableData
|
||||
|
||||
var reflectKind reflect.Kind
|
||||
reflectType := reflect.TypeOf(value)
|
||||
if reflectType == nil {
|
||||
reflectKind = reflect.Interface
|
||||
} else {
|
||||
reflectKind = reflect.TypeOf(value).Kind()
|
||||
}
|
||||
|
||||
switch reflectKind {
|
||||
case reflect.Slice:
|
||||
fallthrough
|
||||
case reflect.Array:
|
||||
for i, el := range value.([]interface{}) {
|
||||
selector := fmt.Sprintf("%s.%s[%d]", selectorPrefix, key, i)
|
||||
table = append(table, api.TableData{
|
||||
Name: fmt.Sprintf("%s [%d]", key, i),
|
||||
Value: el,
|
||||
Selector: selector,
|
||||
})
|
||||
}
|
||||
default:
|
||||
selector := fmt.Sprintf("%s[\"%s\"]", selectorPrefix, key)
|
||||
table = append(table, api.TableData{
|
||||
Name: key,
|
||||
Value: value,
|
||||
Selector: selector,
|
||||
})
|
||||
}
|
||||
|
||||
return table
|
||||
}
|
||||
|
||||
func representSliceAsTable(slice []interface{}, selectorPrefix string) (representation string) {
|
||||
var table []api.TableData
|
||||
for i, item := range slice {
|
||||
|
||||
@@ -286,19 +286,13 @@ func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string,
|
||||
reqDetails["pathSegments"] = strings.Split(path, "/")[1:]
|
||||
|
||||
// Rearrange the maps for the querying
|
||||
reqDetails["_headers"] = reqDetails["headers"]
|
||||
reqDetails["headers"] = mapSliceRebuildAsMap(reqDetails["_headers"].([]interface{}))
|
||||
resDetails["_headers"] = resDetails["headers"]
|
||||
resDetails["headers"] = mapSliceRebuildAsMap(resDetails["_headers"].([]interface{}))
|
||||
reqDetails["headers"] = mapSliceRebuildAsMergedMap(reqDetails["headers"].([]interface{}))
|
||||
resDetails["headers"] = mapSliceRebuildAsMergedMap(resDetails["headers"].([]interface{}))
|
||||
|
||||
reqDetails["_cookies"] = reqDetails["cookies"]
|
||||
reqDetails["cookies"] = mapSliceRebuildAsMap(reqDetails["_cookies"].([]interface{}))
|
||||
resDetails["_cookies"] = resDetails["cookies"]
|
||||
resDetails["cookies"] = mapSliceRebuildAsMap(resDetails["_cookies"].([]interface{}))
|
||||
reqDetails["cookies"] = mapSliceRebuildAsMergedMap(reqDetails["cookies"].([]interface{}))
|
||||
resDetails["cookies"] = mapSliceRebuildAsMergedMap(resDetails["cookies"].([]interface{}))
|
||||
|
||||
reqDetails["_queryString"] = reqDetails["queryString"]
|
||||
reqDetails["_queryStringMerged"] = mapSliceMergeRepeatedKeys(reqDetails["_queryString"].([]interface{}))
|
||||
reqDetails["queryString"] = mapSliceRebuildAsMap(reqDetails["_queryStringMerged"].([]interface{}))
|
||||
reqDetails["queryString"] = mapSliceRebuildAsMap(reqDetails["queryString"].([]interface{}))
|
||||
|
||||
elapsedTime := item.Pair.Response.CaptureTime.Sub(item.Pair.Request.CaptureTime).Round(time.Millisecond).Milliseconds()
|
||||
if elapsedTime < 0 {
|
||||
@@ -397,19 +391,19 @@ func representRequest(request map[string]interface{}) (repRequest []interface{})
|
||||
repRequest = append(repRequest, api.SectionData{
|
||||
Type: api.TABLE,
|
||||
Title: "Headers",
|
||||
Data: representMapSliceAsTable(request["_headers"].([]interface{}), `request.headers`),
|
||||
Data: representMapAsTable(request["headers"].(map[string]interface{}), `request.headers`),
|
||||
})
|
||||
|
||||
repRequest = append(repRequest, api.SectionData{
|
||||
Type: api.TABLE,
|
||||
Title: "Cookies",
|
||||
Data: representMapSliceAsTable(request["_cookies"].([]interface{}), `request.cookies`),
|
||||
Data: representMapAsTable(request["cookies"].(map[string]interface{}), `request.cookies`),
|
||||
})
|
||||
|
||||
repRequest = append(repRequest, api.SectionData{
|
||||
Type: api.TABLE,
|
||||
Title: "Query String",
|
||||
Data: representMapSliceAsTable(request["_queryStringMerged"].([]interface{}), `request.queryString`),
|
||||
Data: representMapAsTable(request["queryString"].(map[string]interface{}), `request.queryString`),
|
||||
})
|
||||
|
||||
postData, _ := request["postData"].(map[string]interface{})
|
||||
@@ -485,13 +479,13 @@ func representResponse(response map[string]interface{}) (repResponse []interface
|
||||
repResponse = append(repResponse, api.SectionData{
|
||||
Type: api.TABLE,
|
||||
Title: "Headers",
|
||||
Data: representMapSliceAsTable(response["_headers"].([]interface{}), `response.headers`),
|
||||
Data: representMapAsTable(response["headers"].(map[string]interface{}), `response.headers`),
|
||||
})
|
||||
|
||||
repResponse = append(repResponse, api.SectionData{
|
||||
Type: api.TABLE,
|
||||
Title: "Cookies",
|
||||
Data: representMapSliceAsTable(response["_cookies"].([]interface{}), `response.cookies`),
|
||||
Data: representMapAsTable(response["cookies"].(map[string]interface{}), `response.cookies`),
|
||||
})
|
||||
|
||||
content, _ := response["content"].(map[string]interface{})
|
||||
|
||||
@@ -1,30 +1,14 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
"github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
const maskedFieldPlaceholderValue = "[REDACTED]"
|
||||
const userAgent = "user-agent"
|
||||
|
||||
//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 IsIgnoredUserAgent(item *api.OutputChannelItem, options *api.TrafficFilteringOptions) bool {
|
||||
if item.Protocol.Name != "http" {
|
||||
return false
|
||||
@@ -48,192 +32,3 @@ func IsIgnoredUserAgent(item *api.OutputChannelItem, options *api.TrafficFilteri
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
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 {
|
||||
return
|
||||
}
|
||||
filteredBody, err := filterHttpBody(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 {
|
||||
return
|
||||
}
|
||||
filteredBody, err := filterHttpBody(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) == userAgent {
|
||||
continue
|
||||
}
|
||||
|
||||
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 {
|
||||
if fieldName == ":authority" {
|
||||
return false
|
||||
}
|
||||
|
||||
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, "&")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ docker build -t mizu-ebpf-builder . || exit 1
|
||||
BPF_TARGET=amd64
|
||||
BPF_CFLAGS="-O2 -g -D__TARGET_ARCH_x86"
|
||||
ARCH=$(uname -m)
|
||||
if [[ $ARCH == "aarch64" ]]; then
|
||||
if [[ $ARCH == "aarch64" || $ARCH == "arm64" ]]; then
|
||||
BPF_TARGET=arm64
|
||||
BPF_CFLAGS="-O2 -g -D__TARGET_ARCH_arm64"
|
||||
fi
|
||||
@@ -18,7 +18,7 @@ docker run --rm \
|
||||
--name mizu-ebpf-builder \
|
||||
-v $MIZU_HOME:/mizu \
|
||||
-v $(go env GOPATH):/root/go \
|
||||
-it mizu-ebpf-builder \
|
||||
mizu-ebpf-builder \
|
||||
sh -c "
|
||||
BPF_TARGET=\"$BPF_TARGET\" BPF_CFLAGS=\"$BPF_CFLAGS\" go generate tap/tlstapper/tls_tapper.go
|
||||
chown $(id -u):$(id -g) tap/tlstapper/tlstapper*_bpf*
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2840
ui-common/package-lock.json
generated
2840
ui-common/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -26,14 +26,15 @@
|
||||
"@craco/craco": "^6.4.3",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/node": "^12.20.54",
|
||||
"sass": "^1.52.3",
|
||||
"react": "^17.0.2",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-dom": "^17.0.2",
|
||||
"recoil": "^0.7.2"
|
||||
"recoil": "^0.7.2",
|
||||
"sass": "^1.52.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@craco/craco": "^6.4.3",
|
||||
"@elastic/eui": "^60.2.0",
|
||||
"@emotion/react": "^11.9.0",
|
||||
"@emotion/styled": "^11.8.1",
|
||||
"@mui/icons-material": "^5.8.2",
|
||||
@@ -72,6 +73,7 @@
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||
"@svgr/rollup": "^6.2.1",
|
||||
"@types/ace": "^0.0.48",
|
||||
"cross-env": "^7.0.3",
|
||||
"env-cmd": "^10.1.0",
|
||||
"gh-pages": "^4.0.0",
|
||||
@@ -84,7 +86,7 @@
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"rollup-plugin-sass": "^1.2.12",
|
||||
"rollup-plugin-scss": "^3.0.0",
|
||||
"typescript": "^4.7.2"
|
||||
"typescript": "^4.5.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
@@ -93,6 +95,7 @@
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"src/*.scss",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ export const AutoRepresentation: React.FC<any> = ({ representation, color, opene
|
||||
badge: null
|
||||
}]
|
||||
|
||||
if (response) {
|
||||
if (response && response.length > 0) {
|
||||
arr.push({
|
||||
tab: 'Response',
|
||||
badge: null
|
||||
@@ -71,7 +71,7 @@ export const AutoRepresentation: React.FC<any> = ({ representation, color, opene
|
||||
{getOpenedTabIndex() === TabsEnum.Request && <React.Fragment>
|
||||
<SectionsRepresentation data={request} color={color} requestRepresentation={request} />
|
||||
</React.Fragment>}
|
||||
{response && getOpenedTabIndex() === TabsEnum.Response && <React.Fragment>
|
||||
{response && response.length > 0 && getOpenedTabIndex() === TabsEnum.Response && <React.Fragment>
|
||||
<SectionsRepresentation data={response} color={color} />
|
||||
</React.Fragment>}
|
||||
</div>}
|
||||
|
||||
@@ -97,4 +97,7 @@ $modalMargin-from-edge : 35px
|
||||
overflow: hidden
|
||||
|
||||
.servicesFilterList
|
||||
height: calc(100% - 30px - 52px)
|
||||
height: calc(100% - 30px)
|
||||
|
||||
.protocolsFilterList
|
||||
height: 100%
|
||||
|
||||
@@ -228,7 +228,7 @@ export const ServiceMapModal: React.FC<ServiceMapModalProps> = ({ isOpen, onClos
|
||||
<div className={styles.filterWrapper}>
|
||||
<div className={styles.card}>
|
||||
<SelectList items={getProtocolsForFilter} checkBoxWidth="5%" tableName={"PROTOCOLS"} multiSelect={true}
|
||||
checkedValues={checkedProtocols} setCheckedValues={onProtocolsChange} tableClassName={styles.filters}
|
||||
checkedValues={checkedProtocols} setCheckedValues={onProtocolsChange} tableClassName={styles.filters + ` ${styles.protocolsFilterList}`}
|
||||
inputSearchClass={styles.servicesFilterSearch} isFilterable={false} />
|
||||
</div>
|
||||
<div className={styles.servicesFilterWrapper + ` ${styles.card}`}>
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import React, { useState, Fragment } from 'react';
|
||||
import { EuiProvider } from '@elastic/eui';
|
||||
|
||||
import {
|
||||
EuiSuperDatePicker,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import dateMath from '@elastic/datemath';
|
||||
import '@elastic/eui/dist/eui_theme_light.css';
|
||||
|
||||
interface TimeRangePickerProps {
|
||||
refreshStats: (startTime, endTime) => void;
|
||||
}
|
||||
|
||||
export const TimeRangePicker: React.FC<TimeRangePickerProps> = ({ refreshStats }) => {
|
||||
const [recentlyUsedRanges, setRecentlyUsedRanges] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [start, setStart] = useState('now-30m');
|
||||
const [end, setEnd] = useState('now');
|
||||
const [isPaused, setIsPaused] = useState(true);
|
||||
const [refreshInterval, setRefreshInterval] = useState();
|
||||
|
||||
const dateConvertor = (inputStart, inputEnd) => {
|
||||
const startMoment = dateMath.parse(inputStart);
|
||||
if (!startMoment || !startMoment.isValid()) {
|
||||
console.error("Unable to parse start string");
|
||||
}
|
||||
const endMoment = dateMath.parse(inputEnd, { roundUp: true });
|
||||
if (!endMoment || !endMoment.isValid()) {
|
||||
console.error("Unable to parse end string");
|
||||
}
|
||||
return { startMoment: startMoment.format("x"), endMoment: endMoment.format("x") }
|
||||
}
|
||||
|
||||
const onTimeChange = ({ start, end }) => {
|
||||
const recentlyUsedRange = recentlyUsedRanges.filter(recentlyUsedRange => {
|
||||
const isDuplicate =
|
||||
recentlyUsedRange.start === start && recentlyUsedRange.end === end;
|
||||
return !isDuplicate;
|
||||
});
|
||||
recentlyUsedRange.unshift({ start, end });
|
||||
setStart(start);
|
||||
setEnd(end);
|
||||
setRecentlyUsedRanges(
|
||||
recentlyUsedRange.length > 10
|
||||
? recentlyUsedRange.slice(0, 9)
|
||||
: recentlyUsedRange
|
||||
);
|
||||
const { startMoment, endMoment } = dateConvertor(start, end)
|
||||
refreshStats(startMoment, endMoment)
|
||||
setIsLoading(true);
|
||||
startLoading();
|
||||
};
|
||||
|
||||
const onRefresh = ({ start, end, refreshInterval }) => {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(resolve, 100);
|
||||
}).then(() => {
|
||||
const { startMoment, endMoment } = dateConvertor(start, end)
|
||||
refreshStats(startMoment, endMoment)
|
||||
});
|
||||
};
|
||||
|
||||
const startLoading = () => {
|
||||
setTimeout(stopLoading, 1000);
|
||||
};
|
||||
const stopLoading = () => {
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const onRefreshChange = ({ isPaused, refreshInterval }) => {
|
||||
setIsPaused(isPaused);
|
||||
setRefreshInterval(refreshInterval);
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiProvider>
|
||||
<Fragment>
|
||||
<EuiSpacer />
|
||||
<EuiSuperDatePicker
|
||||
width='auto'
|
||||
isLoading={isLoading}
|
||||
start={start}
|
||||
end={end}
|
||||
onTimeChange={onTimeChange}
|
||||
onRefresh={onRefresh}
|
||||
isPaused={isPaused}
|
||||
refreshInterval={refreshInterval}
|
||||
onRefreshChange={onRefreshChange}
|
||||
recentlyUsedRanges={recentlyUsedRanges}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
</Fragment>
|
||||
</EuiProvider>
|
||||
);
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
@import "../../../../variables.module"
|
||||
|
||||
.barChartContainer
|
||||
width: 100%
|
||||
display: flex
|
||||
@@ -6,3 +8,4 @@
|
||||
.axisText
|
||||
font-size: 12px
|
||||
opacity: 0.9
|
||||
font-family: $text-font-family
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
@import "../../../../variables.module"
|
||||
|
||||
.entryName
|
||||
margin-left: 5px
|
||||
font-family: $text-font-family
|
||||
@@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState } from "react";
|
||||
import { Cell, Legend, Pie, PieChart, Tooltip } from "recharts";
|
||||
import { Utils } from "../../../../helpers/Utils";
|
||||
import { ALL_PROTOCOLS ,StatsMode as PieChartMode } from "../consts"
|
||||
import styles from "./TrafficPieChart.module.sass";
|
||||
|
||||
const RADIAN = Math.PI / 180;
|
||||
const renderCustomizedLabel = ({
|
||||
@@ -76,7 +77,7 @@ export const TrafficPieChart: React.FC<TrafficPieChartProps> = ({ pieChartMode,
|
||||
if (selectedProtocol === ALL_PROTOCOLS) {
|
||||
legend = data.map(protocol => <div style={{ marginBottom: 5, display: "flex" }}>
|
||||
<div style={{ height: 15, width: 30, background: protocol?.color }} />
|
||||
<span style={{ marginLeft: 5 }}>
|
||||
<span className={styles.entryName}>
|
||||
{protocol.name}
|
||||
</span>
|
||||
</div>)
|
||||
@@ -84,7 +85,7 @@ export const TrafficPieChart: React.FC<TrafficPieChartProps> = ({ pieChartMode,
|
||||
legend = data.find(protocol => protocol.name === selectedProtocol)?.methods.map((method) => <div
|
||||
style={{ marginBottom: 5, display: "flex" }}>
|
||||
<div style={{ height: 15, width: 30, background: method.color}} />
|
||||
<span style={{ marginLeft: 5 }}>
|
||||
<span className={styles.entryName}>
|
||||
{method.name}
|
||||
</span>
|
||||
</div>)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
@import "../../../variables.module"
|
||||
|
||||
.headlineContainer
|
||||
display: flex
|
||||
|
||||
.title
|
||||
color: #494677
|
||||
font-family: Source Sans Pro,Lucida Grande,Tahoma,sans-serif
|
||||
font-family: $text-font-family
|
||||
font-size: 28px
|
||||
font-weight: 600
|
||||
|
||||
@@ -13,16 +15,21 @@
|
||||
top: 20px
|
||||
|
||||
.mainContainer
|
||||
padding: 30px
|
||||
text-align: center
|
||||
|
||||
.selectContainer
|
||||
display: flex
|
||||
justify-content: space-evenly
|
||||
align-items: center
|
||||
margin-bottom: 4%
|
||||
|
||||
.selectTitle
|
||||
font-family: $text-font-family
|
||||
margin-right: 15px
|
||||
|
||||
.select
|
||||
border: none
|
||||
border-bottom: 1px black solid
|
||||
outline: none
|
||||
width: 100px
|
||||
font-family: $text-font-family
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Backdrop, Box, Button, debounce, Fade, Modal } from "@mui/material";
|
||||
import { Backdrop, Box, debounce, Fade, Modal } from "@mui/material";
|
||||
import styles from "./TrafficStatsModal.module.sass";
|
||||
import closeIcon from "assets/close.svg";
|
||||
import { TrafficPieChart } from "./TrafficPieChart/TrafficPieChart";
|
||||
import { TimelineBarChart } from "./TimelineBarChart/TimelineBarChart";
|
||||
import refreshIcon from "assets/refresh.svg";
|
||||
import { useCommonStyles } from "../../../helpers/commonStyle";
|
||||
import { LoadingWrapper } from "../../UI/withLoading/withLoading";
|
||||
import { ALL_PROTOCOLS, StatsMode } from "./consts";
|
||||
import { TimeRangePicker } from "./TimelineBarChart/TimeRangePicker/TimeTangePicker";
|
||||
|
||||
const modalStyle = {
|
||||
position: 'absolute',
|
||||
@@ -15,7 +14,7 @@ const modalStyle = {
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, 0%)',
|
||||
width: '60vw',
|
||||
height: '82vh',
|
||||
height: '90vh',
|
||||
bgcolor: 'background.paper',
|
||||
borderRadius: '5px',
|
||||
boxShadow: 24,
|
||||
@@ -26,11 +25,10 @@ const modalStyle = {
|
||||
interface TrafficStatsModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
getTrafficStatsDataApi: () => Promise<any>
|
||||
getTrafficStatsDataApi: (start?, end?) => Promise<any>
|
||||
}
|
||||
|
||||
export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, onClose, getTrafficStatsDataApi }) => {
|
||||
|
||||
const modes = Object.keys(StatsMode).filter(x => !(parseInt(x) >= 0));
|
||||
const [statsMode, setStatsMode] = useState(modes[0]);
|
||||
const [selectedProtocol, setSelectedProtocol] = useState(ALL_PROTOCOLS);
|
||||
@@ -38,14 +36,13 @@ export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, on
|
||||
const [timelineStatsData, setTimelineStatsData] = useState(null);
|
||||
const [protocols, setProtocols] = useState([])
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const commonClasses = useCommonStyles();
|
||||
|
||||
const getTrafficStats = useCallback(async () => {
|
||||
const getTrafficStats = useCallback(async (startTime, endTime) => {
|
||||
if (isOpen && getTrafficStatsDataApi) {
|
||||
(async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const statsData = await getTrafficStatsDataApi();
|
||||
const statsData = await getTrafficStatsDataApi(startTime, endTime);
|
||||
setPieStatsData(statsData.pie);
|
||||
setTimelineStatsData(statsData.timeline);
|
||||
setProtocols(statsData.protocols)
|
||||
@@ -59,11 +56,13 @@ export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, on
|
||||
}, [isOpen, getTrafficStatsDataApi, setPieStatsData, setTimelineStatsData])
|
||||
|
||||
useEffect(() => {
|
||||
getTrafficStats();
|
||||
const now = new Date().getTime();
|
||||
const halfAnHourAgo = now - (30 * 60 * 1000);
|
||||
getTrafficStats(halfAnHourAgo, now);
|
||||
}, [getTrafficStats])
|
||||
|
||||
const refreshStats = debounce(() => {
|
||||
getTrafficStats();
|
||||
const refreshStats = debounce((newStartTime, newEndTime) => {
|
||||
getTrafficStats(newStartTime, newEndTime);
|
||||
}, 500);
|
||||
|
||||
return (
|
||||
@@ -82,30 +81,24 @@ export const TrafficStatsModal: React.FC<TrafficStatsModalProps> = ({ isOpen, on
|
||||
</div>
|
||||
<div className={styles.headlineContainer}>
|
||||
<div className={styles.title}>Traffic Statistics</div>
|
||||
<Button style={{ marginLeft: "2%", textTransform: 'unset' }}
|
||||
startIcon={<img src={refreshIcon} className="custom" alt="refresh"></img>}
|
||||
size="medium"
|
||||
variant="contained"
|
||||
className={commonClasses.outlinedButton + " " + commonClasses.imagedButton}
|
||||
onClick={refreshStats}
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.mainContainer}>
|
||||
<div className={styles.selectContainer}>
|
||||
<div>
|
||||
<span style={{ marginRight: 15 }}>Breakdown By</span>
|
||||
<span className={styles.selectTitle}>Breakdown By</span>
|
||||
<select className={styles.select} value={statsMode} onChange={(e) => setStatsMode(e.target.value)}>
|
||||
{modes.map(mode => <option key={mode} value={mode}>{mode}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<span style={{ marginRight: 15 }}>Protocol</span>
|
||||
<span className={styles.selectTitle}>Protocol</span>
|
||||
<select className={styles.select} value={selectedProtocol} onChange={(e) => setSelectedProtocol(e.target.value)}>
|
||||
{protocols.map(protocol => <option key={protocol} value={protocol}>{protocol}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<TimeRangePicker refreshStats={refreshStats} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<LoadingWrapper isLoading={isLoading} loaderMargin={20} loaderHeight={50}>
|
||||
|
||||
@@ -14,6 +14,8 @@ $content-section-color: #f8f9fc;
|
||||
$blue-gray: #494677;
|
||||
$light-gray: #8F9BB2;
|
||||
|
||||
$text-font-family: Source Sans Pro, Lucida Grande, Tahoma, sans-serif;
|
||||
|
||||
:export {
|
||||
mainBackgroundColor: $main-background-color;
|
||||
headerBackgroundColor: $header-background-color;
|
||||
@@ -25,4 +27,6 @@ $light-gray: #8F9BB2;
|
||||
failureColor: $failure-color;
|
||||
blueGray: $blue-gray;
|
||||
lightGray: $light-gray;
|
||||
contentSectionColor: $content-section-color;
|
||||
textFontFamily: $text-font-family
|
||||
}
|
||||
|
||||
67736
ui/package-lock.json
generated
67736
ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,8 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@craco/craco": "^6.4.3",
|
||||
"@elastic/datemath": "^5.0.3",
|
||||
"@elastic/eui": "^60.2.0",
|
||||
"@emotion/react": "^11.9.0",
|
||||
"@emotion/styled": "^11.8.1",
|
||||
"@mui/material": "^5.8.2",
|
||||
@@ -23,7 +25,6 @@
|
||||
"mobx": "^6.6.0",
|
||||
"moment": "^2.29.3",
|
||||
"node-fetch": "^3.2.4",
|
||||
"sass": "^1.52.3",
|
||||
"numeral": "^2.0.6",
|
||||
"react": "^17.0.2",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
@@ -35,8 +36,9 @@
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"react-toastify": "^8.2.0",
|
||||
"redoc": "^2.0.0-rc.71",
|
||||
"sass": "^1.52.3",
|
||||
"styled-components": "^5.3.5",
|
||||
"typescript": "^4.7.2",
|
||||
"typescript": "^4.5.3",
|
||||
"web-vitals": "^2.1.4",
|
||||
"xml-formatter": "^2.6.1"
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@ import oasModalOpenAtom from './recoil/oasModalOpen/atom';
|
||||
import trafficStatsModalOpenAtom from "./recoil/trafficStatsModalOpen";
|
||||
import { OasModal } from '@up9/mizu-common';
|
||||
import Api from './helpers/api';
|
||||
import {ThemeProvider, StyledEngineProvider, createTheme} from '@mui/material';
|
||||
import { ThemeProvider, StyledEngineProvider, createTheme } from '@mui/material';
|
||||
import { TrafficStatsModal } from '@up9/mizu-common';
|
||||
|
||||
const api = Api.getInstance()
|
||||
@@ -36,7 +36,7 @@ const App = () => {
|
||||
openModal={oasModalOpen}
|
||||
handleCloseModal={() => setOasModalOpen(false)}
|
||||
/>}
|
||||
<TrafficStatsModal isOpen={trafficStatsModalOpen} onClose={() => setTrafficStatsModalOpen(false)} getTrafficStatsDataApi={api.getTrafficStats}/>
|
||||
<TrafficStatsModal isOpen={trafficStatsModalOpen} onClose={() => setTrafficStatsModalOpen(false)} getTrafficStatsDataApi={api.getTrafficStats} />
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</StyledEngineProvider>
|
||||
|
||||
@@ -116,8 +116,8 @@ export default class Api {
|
||||
});
|
||||
}
|
||||
|
||||
getTrafficStats = async () => {
|
||||
const response = await client.get("/status/trafficStats");
|
||||
getTrafficStats = async (startTimeMs, endTimeMs) => {
|
||||
const response = await client.get("/status/trafficStats", {params: {startTimeMs, endTimeMs}});
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user