mirror of
https://github.com/kubeshark/kubeshark.git
synced 2026-05-04 08:17:56 +00:00
Compare commits
17 Commits
30.0-dev29
...
31.0-dev7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8eeb0e54c9 | ||
|
|
97db24aeba | ||
|
|
63cf7ac34e | ||
|
|
e867b7d0f1 | ||
|
|
dcd8a64f43 | ||
|
|
bf8d5ed069 | ||
|
|
1f6e539590 | ||
|
|
590fa08c81 | ||
|
|
0a9be1884a | ||
|
|
40c745068e | ||
|
|
10dffd9331 | ||
|
|
0a800e8d8a | ||
|
|
068a4ff86e | ||
|
|
c45a869b75 | ||
|
|
0a793cd9e0 | ||
|
|
8d19080c11 | ||
|
|
319c3c7a8d |
2
.github/workflows/acceptance_tests.yml
vendored
2
.github/workflows/acceptance_tests.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
status: ${{ job.status }}
|
status: ${{ job.status }}
|
||||||
notification_title: 'Mizu {workflow} has {status_message}'
|
notification_title: 'Mizu {workflow} has {status_message}'
|
||||||
message_format: '{emoji} *{workflow}* {status_message} during <{run_url}|run>, after commit: <{commit_url}|{commit_sha}>'
|
message_format: '{emoji} *{workflow}* {status_message} during <{run_url}|run>, after commit <{commit_url}|{commit_sha} ${{ github.event.head_commit.message }}> ${{ github.event.head_commit.committer.name }} <${{ github.event.head_commit.committer.email }}>'
|
||||||
footer: 'Linked Repo <{repo_url}|{repo}>'
|
footer: 'Linked Repo <{repo_url}|{repo}>'
|
||||||
notify_when: 'failure'
|
notify_when: 'failure'
|
||||||
env:
|
env:
|
||||||
|
|||||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Check modified files
|
- name: Check modified files
|
||||||
id: modified_files
|
id: modified_files
|
||||||
run: devops/check_modified_files.sh agent/ shared/ tap/ ui/ Dockerfile
|
run: devops/check_modified_files.sh agent/ shared/ tap/ ui/ ui-common/ Dockerfile
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
if: steps.modified_files.outputs.matched == 'true'
|
if: steps.modified_files.outputs.matched == 'true'
|
||||||
|
|||||||
1
.github/workflows/test.yml
vendored
1
.github/workflows/test.yml
vendored
@@ -18,6 +18,7 @@ jobs:
|
|||||||
run-unit-tests:
|
run-unit-tests:
|
||||||
name: Unit Tests
|
name: Unit Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 20
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|||||||
23
Dockerfile
23
Dockerfile
@@ -1,13 +1,23 @@
|
|||||||
ARG BUILDARCH=amd64
|
ARG BUILDARCH=amd64
|
||||||
ARG TARGETARCH=amd64
|
ARG TARGETARCH=amd64
|
||||||
|
|
||||||
|
### Front-end common
|
||||||
|
FROM node:16 AS front-end-common
|
||||||
|
|
||||||
|
WORKDIR /app/ui-build
|
||||||
|
COPY ui-common/package.json .
|
||||||
|
COPY ui-common/package-lock.json .
|
||||||
|
RUN npm i
|
||||||
|
COPY ui-common .
|
||||||
|
RUN npm pack
|
||||||
|
|
||||||
### Front-end
|
### Front-end
|
||||||
FROM node:16 AS front-end
|
FROM node:16 AS front-end
|
||||||
|
|
||||||
WORKDIR /app/ui-build
|
WORKDIR /app/ui-build
|
||||||
|
|
||||||
COPY ui/package.json .
|
COPY ui/package.json ui/package-lock.json ./
|
||||||
COPY ui/package-lock.json .
|
COPY --from=front-end-common ["/app/ui-build/up9-mizu-common-0.0.0.tgz", "."]
|
||||||
RUN npm i
|
RUN npm i
|
||||||
COPY ui .
|
COPY ui .
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
@@ -15,7 +25,7 @@ RUN npm run build
|
|||||||
### Base builder image for native builds architecture
|
### Base builder image for native builds architecture
|
||||||
FROM golang:1.17-alpine AS builder-native-base
|
FROM golang:1.17-alpine AS builder-native-base
|
||||||
ENV CGO_ENABLED=1 GOOS=linux
|
ENV CGO_ENABLED=1 GOOS=linux
|
||||||
RUN apk add libpcap-dev g++ perl-utils
|
RUN apk add --no-cache libpcap-dev g++ perl-utils
|
||||||
|
|
||||||
|
|
||||||
### Intermediate builder image for x86-64 to x86-64 native builds
|
### Intermediate builder image for x86-64 to x86-64 native builds
|
||||||
@@ -79,15 +89,14 @@ RUN go build -ldflags="-extldflags=-static -s -w \
|
|||||||
# Download Basenine executable, verify the sha1sum
|
# Download Basenine executable, verify the sha1sum
|
||||||
ADD https://github.com/up9inc/basenine/releases/download/v0.6.6/basenine_linux_${GOARCH} ./basenine_linux_${GOARCH}
|
ADD https://github.com/up9inc/basenine/releases/download/v0.6.6/basenine_linux_${GOARCH} ./basenine_linux_${GOARCH}
|
||||||
ADD https://github.com/up9inc/basenine/releases/download/v0.6.6/basenine_linux_${GOARCH}.sha256 ./basenine_linux_${GOARCH}.sha256
|
ADD https://github.com/up9inc/basenine/releases/download/v0.6.6/basenine_linux_${GOARCH}.sha256 ./basenine_linux_${GOARCH}.sha256
|
||||||
RUN shasum -a 256 -c basenine_linux_${GOARCH}.sha256
|
|
||||||
RUN chmod +x ./basenine_linux_${GOARCH}
|
|
||||||
RUN mv ./basenine_linux_${GOARCH} ./basenine
|
|
||||||
|
|
||||||
|
RUN shasum -a 256 -c basenine_linux_"${GOARCH}".sha256 && \
|
||||||
|
chmod +x ./basenine_linux_"${GOARCH}" && \
|
||||||
|
mv ./basenine_linux_"${GOARCH}" ./basenine
|
||||||
|
|
||||||
### The shipped image
|
### The shipped image
|
||||||
ARG TARGETARCH=amd64
|
ARG TARGETARCH=amd64
|
||||||
FROM ${TARGETARCH}/busybox:latest
|
FROM ${TARGETARCH}/busybox:latest
|
||||||
|
|
||||||
# gin-gonic runs in debug mode without this
|
# gin-gonic runs in debug mode without this
|
||||||
ENV GIN_MODE=release
|
ENV GIN_MODE=release
|
||||||
|
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -73,7 +73,7 @@ clean-agent: ## Clean agent.
|
|||||||
clean-cli: ## Clean CLI.
|
clean-cli: ## Clean CLI.
|
||||||
@(cd cli; make clean ; echo "CLI cleanup done" )
|
@(cd cli; make clean ; echo "CLI cleanup done" )
|
||||||
|
|
||||||
clean-docker: ## Run clen docker
|
clean-docker: ## Run clean docker
|
||||||
@(echo "DOCKER cleanup - NOT IMPLEMENTED YET " )
|
@(echo "DOCKER cleanup - NOT IMPLEMENTED YET " )
|
||||||
|
|
||||||
test-lint: ## Run lint on all modules
|
test-lint: ## Run lint on all modules
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/up9inc/mizu/agent/pkg/dependency"
|
"github.com/up9inc/mizu/agent/pkg/dependency"
|
||||||
"github.com/up9inc/mizu/agent/pkg/elastic"
|
"github.com/up9inc/mizu/agent/pkg/elastic"
|
||||||
|
"github.com/up9inc/mizu/agent/pkg/entries"
|
||||||
"github.com/up9inc/mizu/agent/pkg/middlewares"
|
"github.com/up9inc/mizu/agent/pkg/middlewares"
|
||||||
"github.com/up9inc/mizu/agent/pkg/models"
|
"github.com/up9inc/mizu/agent/pkg/models"
|
||||||
"github.com/up9inc/mizu/agent/pkg/oas"
|
"github.com/up9inc/mizu/agent/pkg/oas"
|
||||||
@@ -370,5 +371,6 @@ func handleIncomingMessageAsTapper(socketConnection *websocket.Conn) {
|
|||||||
|
|
||||||
func initializeDependencies() {
|
func initializeDependencies() {
|
||||||
dependency.RegisterGenerator(dependency.ServiceMapGeneratorDependency, func() interface{} { return servicemap.GetDefaultServiceMapInstance() })
|
dependency.RegisterGenerator(dependency.ServiceMapGeneratorDependency, func() interface{} { return servicemap.GetDefaultServiceMapInstance() })
|
||||||
dependency.RegisterGenerator(dependency.OasGeneratorDependency, func() interface{} { return oas.GetDefaultOasGeneratorInstance() })
|
dependency.RegisterGenerator(dependency.OasGeneratorDependency, func() interface{} { return oas.GetDefaultOasGeneratorInstance(nil) })
|
||||||
|
dependency.RegisterGenerator(dependency.EntriesProvider, func() interface{} { return &entries.BasenineEntriesProvider{} })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/up9inc/mizu/agent/pkg/models"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -19,8 +20,6 @@ import (
|
|||||||
|
|
||||||
"github.com/up9inc/mizu/agent/pkg/servicemap"
|
"github.com/up9inc/mizu/agent/pkg/servicemap"
|
||||||
|
|
||||||
"github.com/up9inc/mizu/agent/pkg/models"
|
|
||||||
"github.com/up9inc/mizu/agent/pkg/oas"
|
|
||||||
"github.com/up9inc/mizu/agent/pkg/resolver"
|
"github.com/up9inc/mizu/agent/pkg/resolver"
|
||||||
"github.com/up9inc/mizu/agent/pkg/utils"
|
"github.com/up9inc/mizu/agent/pkg/utils"
|
||||||
|
|
||||||
@@ -140,20 +139,6 @@ func startReadingChannel(outputItems <-chan *tapApi.OutputChannelItem, extension
|
|||||||
rules, _, _ := models.RunValidationRulesState(*harEntry, mizuEntry.Destination.Name)
|
rules, _, _ := models.RunValidationRulesState(*harEntry, mizuEntry.Destination.Name)
|
||||||
mizuEntry.Rules = rules
|
mizuEntry.Rules = rules
|
||||||
}
|
}
|
||||||
|
|
||||||
entryWSource := oas.EntryWithSource{
|
|
||||||
Entry: *harEntry,
|
|
||||||
Source: mizuEntry.Source.Name,
|
|
||||||
Destination: mizuEntry.Destination.Name,
|
|
||||||
Id: mizuEntry.Id,
|
|
||||||
}
|
|
||||||
|
|
||||||
if entryWSource.Destination == "" {
|
|
||||||
entryWSource.Destination = mizuEntry.Destination.IP + ":" + mizuEntry.Destination.Port
|
|
||||||
}
|
|
||||||
|
|
||||||
oasGenerator := dependency.GetInstance(dependency.OasGeneratorDependency).(oas.OasGeneratorSink)
|
|
||||||
oasGenerator.PushEntry(&entryWSource)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(mizuEntry)
|
data, err := json.Marshal(mizuEntry)
|
||||||
|
|||||||
@@ -1,25 +1,20 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/up9inc/mizu/agent/pkg/app"
|
"github.com/up9inc/mizu/agent/pkg/dependency"
|
||||||
"github.com/up9inc/mizu/agent/pkg/har"
|
"github.com/up9inc/mizu/agent/pkg/entries"
|
||||||
"github.com/up9inc/mizu/agent/pkg/models"
|
"github.com/up9inc/mizu/agent/pkg/models"
|
||||||
"github.com/up9inc/mizu/agent/pkg/validation"
|
"github.com/up9inc/mizu/agent/pkg/validation"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
basenine "github.com/up9inc/basenine/client/go"
|
|
||||||
"github.com/up9inc/mizu/shared"
|
|
||||||
"github.com/up9inc/mizu/shared/logger"
|
"github.com/up9inc/mizu/shared/logger"
|
||||||
tapApi "github.com/up9inc/mizu/tap/api"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Error(c *gin.Context, err error) bool {
|
func HandleEntriesError(c *gin.Context, err error) bool {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log.Errorf("Error getting entry: %v", err)
|
logger.Log.Errorf("Error getting entry: %v", err)
|
||||||
_ = c.Error(err)
|
_ = c.Error(err)
|
||||||
@@ -49,45 +44,18 @@ func GetEntries(c *gin.Context) {
|
|||||||
entriesRequest.TimeoutMs = 3000
|
entriesRequest.TimeoutMs = 3000
|
||||||
}
|
}
|
||||||
|
|
||||||
data, meta, err := basenine.Fetch(shared.BasenineHost, shared.BaseninePort,
|
entriesProvider := dependency.GetInstance(dependency.EntriesProvider).(entries.EntriesProvider)
|
||||||
entriesRequest.LeftOff, entriesRequest.Direction, entriesRequest.Query,
|
entries, metadata, err := entriesProvider.GetEntries(entriesRequest)
|
||||||
entriesRequest.Limit, time.Duration(entriesRequest.TimeoutMs)*time.Millisecond)
|
if !HandleEntriesError(c, err) {
|
||||||
if err != nil {
|
baseEntries := make([]interface{}, 0)
|
||||||
c.JSON(http.StatusInternalServerError, validationError)
|
for _, entry := range entries {
|
||||||
}
|
baseEntries = append(baseEntries, entry.Base)
|
||||||
|
|
||||||
response := &models.EntriesResponse{}
|
|
||||||
var dataSlice []interface{}
|
|
||||||
|
|
||||||
for _, row := range data {
|
|
||||||
var entry *tapApi.Entry
|
|
||||||
err = json.Unmarshal(row, &entry)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
|
||||||
"error": true,
|
|
||||||
"type": "error",
|
|
||||||
"autoClose": "5000",
|
|
||||||
"msg": string(row),
|
|
||||||
})
|
|
||||||
return // exit
|
|
||||||
}
|
}
|
||||||
|
c.JSON(http.StatusOK, models.EntriesResponse{
|
||||||
extension := app.ExtensionsMap[entry.Protocol.Name]
|
Data: baseEntries,
|
||||||
base := extension.Dissector.Summarize(entry)
|
Meta: metadata,
|
||||||
|
})
|
||||||
dataSlice = append(dataSlice, base)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var metadata *basenine.Metadata
|
|
||||||
err = json.Unmarshal(meta, &metadata)
|
|
||||||
if err != nil {
|
|
||||||
logger.Log.Debugf("Error recieving metadata: %v", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
response.Data = dataSlice
|
|
||||||
response.Meta = metadata
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, response)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetEntry(c *gin.Context) {
|
func GetEntry(c *gin.Context) {
|
||||||
@@ -102,54 +70,11 @@ func GetEntry(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, _ := strconv.Atoi(c.Param("id"))
|
id, _ := strconv.Atoi(c.Param("id"))
|
||||||
var entry *tapApi.Entry
|
|
||||||
bytes, err := basenine.Single(shared.BasenineHost, shared.BaseninePort, id, singleEntryRequest.Query)
|
|
||||||
if Error(c, err) {
|
|
||||||
return // exit
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(bytes, &entry)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusNotFound, gin.H{
|
|
||||||
"error": true,
|
|
||||||
"type": "error",
|
|
||||||
"autoClose": "5000",
|
|
||||||
"msg": string(bytes),
|
|
||||||
})
|
|
||||||
return // exit
|
|
||||||
}
|
|
||||||
|
|
||||||
extension := app.ExtensionsMap[entry.Protocol.Name]
|
entriesProvider := dependency.GetInstance(dependency.EntriesProvider).(entries.EntriesProvider)
|
||||||
base := extension.Dissector.Summarize(entry)
|
entry, err := entriesProvider.GetEntry(singleEntryRequest, id)
|
||||||
var representation []byte
|
|
||||||
representation, err = extension.Dissector.Represent(entry.Request, entry.Response)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusNotFound, gin.H{
|
|
||||||
"error": true,
|
|
||||||
"type": "error",
|
|
||||||
"autoClose": "5000",
|
|
||||||
"msg": err.Error(),
|
|
||||||
})
|
|
||||||
return // exit
|
|
||||||
}
|
|
||||||
|
|
||||||
var rules []map[string]interface{}
|
if !HandleEntriesError(c, err) {
|
||||||
var isRulesEnabled bool
|
c.JSON(http.StatusOK, entry)
|
||||||
if entry.Protocol.Name == "http" {
|
|
||||||
harEntry, _ := har.NewEntry(entry.Request, entry.Response, entry.StartTime, entry.ElapsedTime)
|
|
||||||
_, rulesMatched, _isRulesEnabled := models.RunValidationRulesState(*harEntry, entry.Destination.Name)
|
|
||||||
isRulesEnabled = _isRulesEnabled
|
|
||||||
inrec, _ := json.Marshal(rulesMatched)
|
|
||||||
if err := json.Unmarshal(inrec, &rules); err != nil {
|
|
||||||
logger.Log.Error(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, tapApi.EntryWrapper{
|
|
||||||
Protocol: entry.Protocol,
|
|
||||||
Representation: string(representation),
|
|
||||||
Data: entry,
|
|
||||||
Base: base,
|
|
||||||
Rules: rules,
|
|
||||||
IsRulesEnabled: isRulesEnabled,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
basenine "github.com/up9inc/basenine/client/go"
|
||||||
|
"net"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/up9inc/mizu/agent/pkg/dependency"
|
"github.com/up9inc/mizu/agent/pkg/dependency"
|
||||||
"github.com/up9inc/mizu/agent/pkg/oas"
|
"github.com/up9inc/mizu/agent/pkg/oas"
|
||||||
@@ -11,39 +15,55 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestGetOASServers(t *testing.T) {
|
func TestGetOASServers(t *testing.T) {
|
||||||
dependency.RegisterGenerator(dependency.OasGeneratorDependency, func() interface{} { return oas.GetDefaultOasGeneratorInstance() })
|
recorder, c := getRecorderAndContext()
|
||||||
|
|
||||||
recorder := httptest.NewRecorder()
|
|
||||||
c, _ := gin.CreateTestContext(recorder)
|
|
||||||
oas.GetDefaultOasGeneratorInstance().Start()
|
|
||||||
oas.GetDefaultOasGeneratorInstance().GetServiceSpecs().Store("some", oas.NewGen("some"))
|
|
||||||
|
|
||||||
GetOASServers(c)
|
GetOASServers(c)
|
||||||
t.Logf("Written body: %s", recorder.Body.String())
|
t.Logf("Written body: %s", recorder.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetOASAllSpecs(t *testing.T) {
|
func TestGetOASAllSpecs(t *testing.T) {
|
||||||
dependency.RegisterGenerator(dependency.OasGeneratorDependency, func() interface{} { return oas.GetDefaultOasGeneratorInstance() })
|
recorder, c := getRecorderAndContext()
|
||||||
|
|
||||||
recorder := httptest.NewRecorder()
|
|
||||||
c, _ := gin.CreateTestContext(recorder)
|
|
||||||
oas.GetDefaultOasGeneratorInstance().Start()
|
|
||||||
oas.GetDefaultOasGeneratorInstance().GetServiceSpecs().Store("some", oas.NewGen("some"))
|
|
||||||
|
|
||||||
GetOASAllSpecs(c)
|
GetOASAllSpecs(c)
|
||||||
t.Logf("Written body: %s", recorder.Body.String())
|
t.Logf("Written body: %s", recorder.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetOASSpec(t *testing.T) {
|
func TestGetOASSpec(t *testing.T) {
|
||||||
dependency.RegisterGenerator(dependency.OasGeneratorDependency, func() interface{} { return oas.GetDefaultOasGeneratorInstance() })
|
recorder, c := getRecorderAndContext()
|
||||||
|
|
||||||
recorder := httptest.NewRecorder()
|
|
||||||
c, _ := gin.CreateTestContext(recorder)
|
|
||||||
oas.GetDefaultOasGeneratorInstance().Start()
|
|
||||||
oas.GetDefaultOasGeneratorInstance().GetServiceSpecs().Store("some", oas.NewGen("some"))
|
|
||||||
|
|
||||||
c.Params = []gin.Param{{Key: "id", Value: "some"}}
|
c.Params = []gin.Param{{Key: "id", Value: "some"}}
|
||||||
|
|
||||||
GetOASSpec(c)
|
GetOASSpec(c)
|
||||||
t.Logf("Written body: %s", recorder.Body.String())
|
t.Logf("Written body: %s", recorder.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fakeConn struct {
|
||||||
|
sendBuffer *bytes.Buffer
|
||||||
|
receiveBuffer *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f fakeConn) Read(p []byte) (int, error) { return f.sendBuffer.Read(p) }
|
||||||
|
func (f fakeConn) Write(p []byte) (int, error) { return f.receiveBuffer.Write(p) }
|
||||||
|
func (fakeConn) Close() error { return nil }
|
||||||
|
func (fakeConn) LocalAddr() net.Addr { return nil }
|
||||||
|
func (fakeConn) RemoteAddr() net.Addr { return nil }
|
||||||
|
func (fakeConn) SetDeadline(t time.Time) error { return nil }
|
||||||
|
func (fakeConn) SetReadDeadline(t time.Time) error { return nil }
|
||||||
|
func (fakeConn) SetWriteDeadline(t time.Time) error { return nil }
|
||||||
|
|
||||||
|
func getRecorderAndContext() (*httptest.ResponseRecorder, *gin.Context) {
|
||||||
|
dummyConn := new(basenine.Connection)
|
||||||
|
dummyConn.Conn = fakeConn{
|
||||||
|
sendBuffer: bytes.NewBufferString("\n"),
|
||||||
|
receiveBuffer: bytes.NewBufferString("\n"),
|
||||||
|
}
|
||||||
|
dependency.RegisterGenerator(dependency.OasGeneratorDependency, func() interface{} {
|
||||||
|
return oas.GetDefaultOasGeneratorInstance(dummyConn)
|
||||||
|
})
|
||||||
|
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
c, _ := gin.CreateTestContext(recorder)
|
||||||
|
oas.GetDefaultOasGeneratorInstance(dummyConn).Start()
|
||||||
|
oas.GetDefaultOasGeneratorInstance(dummyConn).GetServiceSpecs().Store("some", oas.NewGen("some"))
|
||||||
|
return recorder, c
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ type DependencyContainerType string
|
|||||||
const (
|
const (
|
||||||
ServiceMapGeneratorDependency = "ServiceMapGeneratorDependency"
|
ServiceMapGeneratorDependency = "ServiceMapGeneratorDependency"
|
||||||
OasGeneratorDependency = "OasGeneratorDependency"
|
OasGeneratorDependency = "OasGeneratorDependency"
|
||||||
|
EntriesProvider = "EntriesProvider"
|
||||||
)
|
)
|
||||||
|
|||||||
98
agent/pkg/entries/entries_provider.go
Normal file
98
agent/pkg/entries/entries_provider.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package entries
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
basenine "github.com/up9inc/basenine/client/go"
|
||||||
|
"github.com/up9inc/mizu/agent/pkg/app"
|
||||||
|
"github.com/up9inc/mizu/agent/pkg/har"
|
||||||
|
"github.com/up9inc/mizu/agent/pkg/models"
|
||||||
|
"github.com/up9inc/mizu/shared"
|
||||||
|
"github.com/up9inc/mizu/shared/logger"
|
||||||
|
tapApi "github.com/up9inc/mizu/tap/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EntriesProvider interface {
|
||||||
|
GetEntries(entriesRequest *models.EntriesRequest) ([]*tapApi.EntryWrapper, *basenine.Metadata, error)
|
||||||
|
GetEntry(singleEntryRequest *models.SingleEntryRequest, entryId int) (*tapApi.EntryWrapper, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BasenineEntriesProvider struct{}
|
||||||
|
|
||||||
|
func (e *BasenineEntriesProvider) GetEntries(entriesRequest *models.EntriesRequest) ([]*tapApi.EntryWrapper, *basenine.Metadata, error) {
|
||||||
|
data, meta, err := basenine.Fetch(shared.BasenineHost, shared.BaseninePort,
|
||||||
|
entriesRequest.LeftOff, entriesRequest.Direction, entriesRequest.Query,
|
||||||
|
entriesRequest.Limit, time.Duration(entriesRequest.TimeoutMs)*time.Millisecond)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataSlice []*tapApi.EntryWrapper
|
||||||
|
|
||||||
|
for _, row := range data {
|
||||||
|
var entry *tapApi.Entry
|
||||||
|
err = json.Unmarshal(row, &entry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
extension := app.ExtensionsMap[entry.Protocol.Name]
|
||||||
|
base := extension.Dissector.Summarize(entry)
|
||||||
|
|
||||||
|
dataSlice = append(dataSlice, &tapApi.EntryWrapper{
|
||||||
|
Protocol: entry.Protocol,
|
||||||
|
Data: entry,
|
||||||
|
Base: base,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadata *basenine.Metadata
|
||||||
|
err = json.Unmarshal(meta, &metadata)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Debugf("Error recieving metadata: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataSlice, metadata, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *BasenineEntriesProvider) GetEntry(singleEntryRequest *models.SingleEntryRequest, entryId int) (*tapApi.EntryWrapper, error) {
|
||||||
|
var entry *tapApi.Entry
|
||||||
|
bytes, err := basenine.Single(shared.BasenineHost, shared.BaseninePort, entryId, singleEntryRequest.Query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(bytes, &entry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
extension := app.ExtensionsMap[entry.Protocol.Name]
|
||||||
|
base := extension.Dissector.Summarize(entry)
|
||||||
|
var representation []byte
|
||||||
|
representation, err = extension.Dissector.Represent(entry.Request, entry.Response)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var rules []map[string]interface{}
|
||||||
|
var isRulesEnabled bool
|
||||||
|
if entry.Protocol.Name == "http" {
|
||||||
|
harEntry, _ := har.NewEntry(entry.Request, entry.Response, entry.StartTime, entry.ElapsedTime)
|
||||||
|
_, rulesMatched, _isRulesEnabled := models.RunValidationRulesState(*harEntry, entry.Destination.Name)
|
||||||
|
isRulesEnabled = _isRulesEnabled
|
||||||
|
inrec, _ := json.Marshal(rulesMatched)
|
||||||
|
if err := json.Unmarshal(inrec, &rules); err != nil {
|
||||||
|
logger.Log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tapApi.EntryWrapper{
|
||||||
|
Protocol: entry.Protocol,
|
||||||
|
Representation: string(representation),
|
||||||
|
Data: entry,
|
||||||
|
Base: base,
|
||||||
|
Rules: rules,
|
||||||
|
IsRulesEnabled: isRulesEnabled,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -67,23 +67,23 @@ func fileSize(fname string) int64 {
|
|||||||
return fi.Size()
|
return fi.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
func feedEntries(fromFiles []string, isSync bool) (count int, err error) {
|
func feedEntries(fromFiles []string, isSync bool, gen *defaultOasGenerator) (count uint, err error) {
|
||||||
badFiles := make([]string, 0)
|
badFiles := make([]string, 0)
|
||||||
cnt := 0
|
cnt := uint(0)
|
||||||
for _, file := range fromFiles {
|
for _, file := range fromFiles {
|
||||||
logger.Log.Info("Processing file: " + file)
|
logger.Log.Info("Processing file: " + file)
|
||||||
ext := strings.ToLower(filepath.Ext(file))
|
ext := strings.ToLower(filepath.Ext(file))
|
||||||
eCnt := 0
|
eCnt := uint(0)
|
||||||
switch ext {
|
switch ext {
|
||||||
case ".har":
|
case ".har":
|
||||||
eCnt, err = feedFromHAR(file, isSync)
|
eCnt, err = feedFromHAR(file, isSync, gen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log.Warning("Failed processing file: " + err.Error())
|
logger.Log.Warning("Failed processing file: " + err.Error())
|
||||||
badFiles = append(badFiles, file)
|
badFiles = append(badFiles, file)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case ".ldjson":
|
case ".ldjson":
|
||||||
eCnt, err = feedFromLDJSON(file, isSync)
|
eCnt, err = feedFromLDJSON(file, isSync, gen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log.Warning("Failed processing file: " + err.Error())
|
logger.Log.Warning("Failed processing file: " + err.Error())
|
||||||
badFiles = append(badFiles, file)
|
badFiles = append(badFiles, file)
|
||||||
@@ -102,7 +102,7 @@ func feedEntries(fromFiles []string, isSync bool) (count int, err error) {
|
|||||||
return cnt, nil
|
return cnt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func feedFromHAR(file string, isSync bool) (int, error) {
|
func feedFromHAR(file string, isSync bool, gen *defaultOasGenerator) (uint, error) {
|
||||||
fd, err := os.Open(file)
|
fd, err := os.Open(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -121,16 +121,16 @@ func feedFromHAR(file string, isSync bool) (int, error) {
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cnt := 0
|
cnt := uint(0)
|
||||||
for _, entry := range harDoc.Log.Entries {
|
for _, entry := range harDoc.Log.Entries {
|
||||||
cnt += 1
|
cnt += 1
|
||||||
feedEntry(&entry, "", isSync, file)
|
feedEntry(&entry, "", file, gen, cnt)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cnt, nil
|
return cnt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func feedEntry(entry *har.Entry, source string, isSync bool, file string) {
|
func feedEntry(entry *har.Entry, source string, file string, gen *defaultOasGenerator, cnt uint) {
|
||||||
entry.Comment = file
|
entry.Comment = file
|
||||||
if entry.Response.Status == 302 {
|
if entry.Response.Status == 302 {
|
||||||
logger.Log.Debugf("Dropped traffic entry due to permanent redirect status: %s", entry.StartedDateTime)
|
logger.Log.Debugf("Dropped traffic entry due to permanent redirect status: %s", entry.StartedDateTime)
|
||||||
@@ -145,15 +145,11 @@ func feedEntry(entry *har.Entry, source string, isSync bool, file string) {
|
|||||||
logger.Log.Errorf("Failed to parse entry URL: %v, err: %v", entry.Request.URL, err)
|
logger.Log.Errorf("Failed to parse entry URL: %v, err: %v", entry.Request.URL, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ews := EntryWithSource{Entry: *entry, Source: source, Destination: u.Host, Id: uint(0)}
|
ews := EntryWithSource{Entry: *entry, Source: source, Destination: u.Host, Id: cnt}
|
||||||
if isSync {
|
gen.handleHARWithSource(&ews)
|
||||||
GetDefaultOasGeneratorInstance().entriesChan <- ews // blocking variant, right?
|
|
||||||
} else {
|
|
||||||
GetDefaultOasGeneratorInstance().PushEntry(&ews)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func feedFromLDJSON(file string, isSync bool) (int, error) {
|
func feedFromLDJSON(file string, isSync bool, gen *defaultOasGenerator) (uint, error) {
|
||||||
fd, err := os.Open(file)
|
fd, err := os.Open(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -165,7 +161,7 @@ func feedFromLDJSON(file string, isSync bool) (int, error) {
|
|||||||
|
|
||||||
var meta map[string]interface{}
|
var meta map[string]interface{}
|
||||||
buf := strings.Builder{}
|
buf := strings.Builder{}
|
||||||
cnt := 0
|
cnt := uint(0)
|
||||||
source := ""
|
source := ""
|
||||||
for {
|
for {
|
||||||
substr, isPrefix, err := reader.ReadLine()
|
substr, isPrefix, err := reader.ReadLine()
|
||||||
@@ -196,7 +192,7 @@ func feedFromLDJSON(file string, isSync bool) (int, error) {
|
|||||||
logger.Log.Warningf("Failed decoding entry: %s", line)
|
logger.Log.Warningf("Failed decoding entry: %s", line)
|
||||||
} else {
|
} else {
|
||||||
cnt += 1
|
cnt += 1
|
||||||
feedEntry(&entry, source, isSync, file)
|
feedEntry(&entry, source, file, gen, cnt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,13 @@ package oas
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
basenine "github.com/up9inc/basenine/client/go"
|
||||||
|
"github.com/up9inc/mizu/agent/pkg/har"
|
||||||
|
"github.com/up9inc/mizu/shared"
|
||||||
|
"github.com/up9inc/mizu/tap/api"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/up9inc/mizu/agent/pkg/har"
|
|
||||||
"github.com/up9inc/mizu/shared/logger"
|
"github.com/up9inc/mizu/shared/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,10 +18,6 @@ var (
|
|||||||
instance *defaultOasGenerator
|
instance *defaultOasGenerator
|
||||||
)
|
)
|
||||||
|
|
||||||
type OasGeneratorSink interface {
|
|
||||||
PushEntry(entryWithSource *EntryWithSource)
|
|
||||||
}
|
|
||||||
|
|
||||||
type OasGenerator interface {
|
type OasGenerator interface {
|
||||||
Start()
|
Start()
|
||||||
Stop()
|
Stop()
|
||||||
@@ -32,12 +31,20 @@ type defaultOasGenerator struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
serviceSpecs *sync.Map
|
serviceSpecs *sync.Map
|
||||||
entriesChan chan EntryWithSource
|
dbConn *basenine.Connection
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDefaultOasGeneratorInstance() *defaultOasGenerator {
|
func GetDefaultOasGeneratorInstance(conn *basenine.Connection) *defaultOasGenerator {
|
||||||
syncOnce.Do(func() {
|
syncOnce.Do(func() {
|
||||||
instance = NewDefaultOasGenerator()
|
if conn == nil {
|
||||||
|
c, err := basenine.NewConnection(shared.BasenineHost, shared.BaseninePort)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
conn = c
|
||||||
|
}
|
||||||
|
|
||||||
|
instance = NewDefaultOasGenerator(conn)
|
||||||
logger.Log.Debug("OAS Generator Initialized")
|
logger.Log.Debug("OAS Generator Initialized")
|
||||||
})
|
})
|
||||||
return instance
|
return instance
|
||||||
@@ -50,7 +57,6 @@ func (g *defaultOasGenerator) Start() {
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
g.cancel = cancel
|
g.cancel = cancel
|
||||||
g.ctx = ctx
|
g.ctx = ctx
|
||||||
g.entriesChan = make(chan EntryWithSource, 100) // buffer up to 100 entries for OAS processing
|
|
||||||
g.serviceSpecs = &sync.Map{}
|
g.serviceSpecs = &sync.Map{}
|
||||||
g.started = true
|
g.started = true
|
||||||
go g.runGenerator()
|
go g.runGenerator()
|
||||||
@@ -70,80 +76,117 @@ func (g *defaultOasGenerator) IsStarted() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *defaultOasGenerator) runGenerator() {
|
func (g *defaultOasGenerator) runGenerator() {
|
||||||
|
// Make []byte channels to recieve the data and the meta
|
||||||
|
dataChan := make(chan []byte)
|
||||||
|
metaChan := make(chan []byte)
|
||||||
|
|
||||||
|
g.dbConn.Query("", dataChan, metaChan)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-g.ctx.Done():
|
case <-g.ctx.Done():
|
||||||
logger.Log.Infof("OAS Generator was canceled")
|
logger.Log.Infof("OAS Generator was canceled")
|
||||||
return
|
return
|
||||||
|
|
||||||
case entryWithSource, ok := <-g.entriesChan:
|
case metaBytes, ok := <-metaChan:
|
||||||
|
if !ok {
|
||||||
|
logger.Log.Infof("OAS Generator - meta channel closed")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
logger.Log.Debugf("Meta: %s", metaBytes)
|
||||||
|
|
||||||
|
case dataBytes, ok := <-dataChan:
|
||||||
if !ok {
|
if !ok {
|
||||||
logger.Log.Infof("OAS Generator - entries channel closed")
|
logger.Log.Infof("OAS Generator - entries channel closed")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
entry := entryWithSource.Entry
|
|
||||||
u, err := url.Parse(entry.Request.URL)
|
logger.Log.Debugf("Data: %s", dataBytes)
|
||||||
|
e := new(api.Entry)
|
||||||
|
err := json.Unmarshal(dataBytes, e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log.Errorf("Failed to parse entry URL: %v, err: %v", entry.Request.URL, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
val, found := g.serviceSpecs.Load(entryWithSource.Destination)
|
|
||||||
var gen *SpecGen
|
|
||||||
if !found {
|
|
||||||
gen = NewGen(u.Scheme + "://" + entryWithSource.Destination)
|
|
||||||
g.serviceSpecs.Store(entryWithSource.Destination, gen)
|
|
||||||
} else {
|
|
||||||
gen = val.(*SpecGen)
|
|
||||||
}
|
|
||||||
|
|
||||||
opId, err := gen.feedEntry(entryWithSource)
|
|
||||||
if err != nil {
|
|
||||||
txt, suberr := json.Marshal(entry)
|
|
||||||
if suberr == nil {
|
|
||||||
logger.Log.Debugf("Problematic entry: %s", txt)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Log.Warningf("Failed processing entry: %s", err)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
g.handleEntry(e)
|
||||||
logger.Log.Debugf("Handled entry %s as opId: %s", entry.Request.URL, opId) // TODO: set opId back to entry?
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *defaultOasGenerator) handleEntry(mizuEntry *api.Entry) {
|
||||||
|
if mizuEntry.Protocol.Name == "http" {
|
||||||
|
entry, err := har.NewEntry(mizuEntry.Request, mizuEntry.Response, mizuEntry.StartTime, mizuEntry.ElapsedTime)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Warningf("Failed to turn MizuEntry %d into HAR Entry: %s", mizuEntry.Id, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dest := mizuEntry.Destination.Name
|
||||||
|
if dest == "" {
|
||||||
|
dest = mizuEntry.Destination.IP + ":" + mizuEntry.Destination.Port
|
||||||
|
}
|
||||||
|
|
||||||
|
entryWSource := &EntryWithSource{
|
||||||
|
Entry: *entry,
|
||||||
|
Source: mizuEntry.Source.Name,
|
||||||
|
Destination: dest,
|
||||||
|
Id: mizuEntry.Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
g.handleHARWithSource(entryWSource)
|
||||||
|
} else {
|
||||||
|
logger.Log.Debugf("OAS: Unsupported protocol in entry %d: %s", mizuEntry.Id, mizuEntry.Protocol.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *defaultOasGenerator) handleHARWithSource(entryWSource *EntryWithSource) {
|
||||||
|
entry := entryWSource.Entry
|
||||||
|
gen := g.getGen(entryWSource.Destination, entry.Request.URL)
|
||||||
|
|
||||||
|
opId, err := gen.feedEntry(entryWSource)
|
||||||
|
if err != nil {
|
||||||
|
txt, suberr := json.Marshal(entry)
|
||||||
|
if suberr == nil {
|
||||||
|
logger.Log.Debugf("Problematic entry: %s", txt)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Warningf("Failed processing entry %d: %s", entryWSource.Id, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Debugf("Handled entry %d as opId: %s", entryWSource.Id, opId) // TODO: set opId back to entry?
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *defaultOasGenerator) getGen(dest string, urlStr string) *SpecGen {
|
||||||
|
u, err := url.Parse(urlStr)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Errorf("Failed to parse entry URL: %v, err: %v", urlStr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
val, found := g.serviceSpecs.Load(dest)
|
||||||
|
var gen *SpecGen
|
||||||
|
if !found {
|
||||||
|
gen = NewGen(u.Scheme + "://" + dest)
|
||||||
|
g.serviceSpecs.Store(dest, gen)
|
||||||
|
} else {
|
||||||
|
gen = val.(*SpecGen)
|
||||||
|
}
|
||||||
|
return gen
|
||||||
|
}
|
||||||
|
|
||||||
func (g *defaultOasGenerator) Reset() {
|
func (g *defaultOasGenerator) Reset() {
|
||||||
g.serviceSpecs = &sync.Map{}
|
g.serviceSpecs = &sync.Map{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *defaultOasGenerator) PushEntry(entryWithSource *EntryWithSource) {
|
|
||||||
if !g.started {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case g.entriesChan <- *entryWithSource:
|
|
||||||
default:
|
|
||||||
logger.Log.Warningf("OAS Generator - entry wasn't sent to channel because the channel has no buffer or there is no receiver")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *defaultOasGenerator) GetServiceSpecs() *sync.Map {
|
func (g *defaultOasGenerator) GetServiceSpecs() *sync.Map {
|
||||||
return g.serviceSpecs
|
return g.serviceSpecs
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultOasGenerator() *defaultOasGenerator {
|
func NewDefaultOasGenerator(c *basenine.Connection) *defaultOasGenerator {
|
||||||
return &defaultOasGenerator{
|
return &defaultOasGenerator{
|
||||||
started: false,
|
started: false,
|
||||||
ctx: nil,
|
ctx: nil,
|
||||||
cancel: nil,
|
cancel: nil,
|
||||||
serviceSpecs: nil,
|
serviceSpecs: nil,
|
||||||
entriesChan: nil,
|
dbConn: c,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type EntryWithSource struct {
|
|
||||||
Source string
|
|
||||||
Destination string
|
|
||||||
Entry har.Entry
|
|
||||||
Id uint
|
|
||||||
}
|
|
||||||
|
|||||||
36
agent/pkg/oas/oas_generator_test.go
Normal file
36
agent/pkg/oas/oas_generator_test.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package oas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/up9inc/mizu/agent/pkg/har"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOASGen(t *testing.T) {
|
||||||
|
gen := new(defaultOasGenerator)
|
||||||
|
gen.serviceSpecs = &sync.Map{}
|
||||||
|
|
||||||
|
e := new(har.Entry)
|
||||||
|
err := json.Unmarshal([]byte(`{"startedDateTime": "20000101","request": {"url": "https://host/path", "method": "GET"}, "response": {"status": 200}}`), e)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ews := &EntryWithSource{
|
||||||
|
Destination: "some",
|
||||||
|
Entry: *e,
|
||||||
|
}
|
||||||
|
gen.handleHARWithSource(ews)
|
||||||
|
g, ok := gen.serviceSpecs.Load("some")
|
||||||
|
if !ok {
|
||||||
|
panic("Failed")
|
||||||
|
}
|
||||||
|
sg := g.(*SpecGen)
|
||||||
|
spec, err := sg.GetSpec()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
specText, _ := json.Marshal(spec)
|
||||||
|
t.Log(string(specText))
|
||||||
|
}
|
||||||
@@ -28,6 +28,13 @@ const CountersTotal = "x-counters-total"
|
|||||||
const CountersPerSource = "x-counters-per-source"
|
const CountersPerSource = "x-counters-per-source"
|
||||||
const SampleId = "x-sample-entry"
|
const SampleId = "x-sample-entry"
|
||||||
|
|
||||||
|
type EntryWithSource struct {
|
||||||
|
Source string
|
||||||
|
Destination string
|
||||||
|
Entry har.Entry
|
||||||
|
Id uint
|
||||||
|
}
|
||||||
|
|
||||||
type reqResp struct { // hello, generics in Go
|
type reqResp struct { // hello, generics in Go
|
||||||
Req *har.Request
|
Req *har.Request
|
||||||
Resp *har.Response
|
Resp *har.Response
|
||||||
@@ -60,7 +67,7 @@ func (g *SpecGen) StartFromSpec(oas *openapi.OpenAPI) {
|
|||||||
g.tree = new(Node)
|
g.tree = new(Node)
|
||||||
for pathStr, pathObj := range oas.Paths.Items {
|
for pathStr, pathObj := range oas.Paths.Items {
|
||||||
pathSplit := strings.Split(string(pathStr), "/")
|
pathSplit := strings.Split(string(pathStr), "/")
|
||||||
g.tree.getOrSet(pathSplit, pathObj)
|
g.tree.getOrSet(pathSplit, pathObj, 0)
|
||||||
|
|
||||||
// clean "last entry timestamp" markers from the past
|
// clean "last entry timestamp" markers from the past
|
||||||
for _, pathAndOp := range g.tree.listOps() {
|
for _, pathAndOp := range g.tree.listOps() {
|
||||||
@@ -69,11 +76,11 @@ func (g *SpecGen) StartFromSpec(oas *openapi.OpenAPI) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *SpecGen) feedEntry(entryWithSource EntryWithSource) (string, error) {
|
func (g *SpecGen) feedEntry(entryWithSource *EntryWithSource) (string, error) {
|
||||||
g.lock.Lock()
|
g.lock.Lock()
|
||||||
defer g.lock.Unlock()
|
defer g.lock.Unlock()
|
||||||
|
|
||||||
opId, err := g.handlePathObj(&entryWithSource)
|
opId, err := g.handlePathObj(entryWithSource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -219,7 +226,7 @@ func (g *SpecGen) handlePathObj(entryWithSource *EntryWithSource) (string, error
|
|||||||
} else {
|
} else {
|
||||||
split = strings.Split(urlParsed.Path, "/")
|
split = strings.Split(urlParsed.Path, "/")
|
||||||
}
|
}
|
||||||
node := g.tree.getOrSet(split, new(openapi.PathObj))
|
node := g.tree.getOrSet(split, new(openapi.PathObj), entryWithSource.Id)
|
||||||
opObj, err := handleOpObj(entryWithSource, node.pathObj)
|
opObj, err := handleOpObj(entryWithSource, node.pathObj)
|
||||||
|
|
||||||
if opObj != nil {
|
if opObj != nil {
|
||||||
@@ -242,12 +249,12 @@ func handleOpObj(entryWithSource *EntryWithSource, pathObj *openapi.PathObj) (*o
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handleRequest(&entry.Request, opObj, isSuccess)
|
err = handleRequest(&entry.Request, opObj, isSuccess, entryWithSource.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handleResponse(&entry.Response, opObj, isSuccess)
|
err = handleResponse(&entry.Response, opObj, isSuccess, entryWithSource.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -257,6 +264,8 @@ func handleOpObj(entryWithSource *EntryWithSource, pathObj *openapi.PathObj) (*o
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setSampleID(&opObj.Extensions, entryWithSource.Id)
|
||||||
|
|
||||||
return opObj, nil
|
return opObj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,15 +338,10 @@ func handleCounters(opObj *openapi.Operation, success bool, entryWithSource *Ent
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = opObj.Extensions.SetExtension(SampleId, entryWithSource.Id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleRequest(req *har.Request, opObj *openapi.Operation, isSuccess bool) error {
|
func handleRequest(req *har.Request, opObj *openapi.Operation, isSuccess bool, sampleId uint) error {
|
||||||
// TODO: we don't handle the situation when header/qstr param can be defined on pathObj level. Also the path param defined on opObj
|
// TODO: we don't handle the situation when header/qstr param can be defined on pathObj level. Also the path param defined on opObj
|
||||||
urlParsed, err := url.Parse(req.URL)
|
urlParsed, err := url.Parse(req.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -361,7 +365,7 @@ func handleRequest(req *har.Request, opObj *openapi.Operation, isSuccess bool) e
|
|||||||
IsIgnored: func(name string) bool { return false },
|
IsIgnored: func(name string) bool { return false },
|
||||||
GeneralizeName: func(name string) string { return name },
|
GeneralizeName: func(name string) string { return name },
|
||||||
}
|
}
|
||||||
handleNameVals(qstrGW, &opObj.Parameters, false)
|
handleNameVals(qstrGW, &opObj.Parameters, false, sampleId)
|
||||||
|
|
||||||
hdrGW := nvParams{
|
hdrGW := nvParams{
|
||||||
In: openapi.InHeader,
|
In: openapi.InHeader,
|
||||||
@@ -369,7 +373,7 @@ func handleRequest(req *har.Request, opObj *openapi.Operation, isSuccess bool) e
|
|||||||
IsIgnored: isHeaderIgnored,
|
IsIgnored: isHeaderIgnored,
|
||||||
GeneralizeName: strings.ToLower,
|
GeneralizeName: strings.ToLower,
|
||||||
}
|
}
|
||||||
handleNameVals(hdrGW, &opObj.Parameters, true)
|
handleNameVals(hdrGW, &opObj.Parameters, true, sampleId)
|
||||||
|
|
||||||
if isSuccess {
|
if isSuccess {
|
||||||
reqBody, err := getRequestBody(req, opObj)
|
reqBody, err := getRequestBody(req, opObj)
|
||||||
@@ -378,12 +382,14 @@ func handleRequest(req *har.Request, opObj *openapi.Operation, isSuccess bool) e
|
|||||||
}
|
}
|
||||||
|
|
||||||
if reqBody != nil {
|
if reqBody != nil {
|
||||||
|
setSampleID(&reqBody.Extensions, sampleId)
|
||||||
|
|
||||||
if req.PostData.Text == "" {
|
if req.PostData.Text == "" {
|
||||||
reqBody.Required = false
|
reqBody.Required = false
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
reqCtype, _ := getReqCtype(req)
|
reqCtype, _ := getReqCtype(req)
|
||||||
reqMedia, err := fillContent(reqResp{Req: req}, reqBody.Content, reqCtype)
|
reqMedia, err := fillContent(reqResp{Req: req}, reqBody.Content, reqCtype, sampleId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -395,18 +401,20 @@ func handleRequest(req *har.Request, opObj *openapi.Operation, isSuccess bool) e
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleResponse(resp *har.Response, opObj *openapi.Operation, isSuccess bool) error {
|
func handleResponse(resp *har.Response, opObj *openapi.Operation, isSuccess bool, sampleId uint) error {
|
||||||
// TODO: we don't support "default" response
|
// TODO: we don't support "default" response
|
||||||
respObj, err := getResponseObj(resp, opObj, isSuccess)
|
respObj, err := getResponseObj(resp, opObj, isSuccess)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRespHeaders(resp.Headers, respObj)
|
setSampleID(&respObj.Extensions, sampleId)
|
||||||
|
|
||||||
|
handleRespHeaders(resp.Headers, respObj, sampleId)
|
||||||
|
|
||||||
respCtype := getRespCtype(resp)
|
respCtype := getRespCtype(resp)
|
||||||
respContent := respObj.Content
|
respContent := respObj.Content
|
||||||
respMedia, err := fillContent(reqResp{Resp: resp}, respContent, respCtype)
|
respMedia, err := fillContent(reqResp{Resp: resp}, respContent, respCtype, sampleId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -414,7 +422,7 @@ func handleResponse(resp *har.Response, opObj *openapi.Operation, isSuccess bool
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleRespHeaders(reqHeaders []har.Header, respObj *openapi.ResponseObj) {
|
func handleRespHeaders(reqHeaders []har.Header, respObj *openapi.ResponseObj, sampleId uint) {
|
||||||
visited := map[string]*openapi.HeaderObj{}
|
visited := map[string]*openapi.HeaderObj{}
|
||||||
for _, pair := range reqHeaders {
|
for _, pair := range reqHeaders {
|
||||||
if isHeaderIgnored(pair.Name) {
|
if isHeaderIgnored(pair.Name) {
|
||||||
@@ -436,6 +444,8 @@ func handleRespHeaders(reqHeaders []har.Header, respObj *openapi.ResponseObj) {
|
|||||||
logger.Log.Warningf("Failed to add example to a parameter: %s", err)
|
logger.Log.Warningf("Failed to add example to a parameter: %s", err)
|
||||||
}
|
}
|
||||||
visited[nameGeneral] = param
|
visited[nameGeneral] = param
|
||||||
|
|
||||||
|
setSampleID(¶m.Extensions, sampleId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// maintain "required" flag
|
// maintain "required" flag
|
||||||
@@ -456,13 +466,15 @@ func handleRespHeaders(reqHeaders []har.Header, respObj *openapi.ResponseObj) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fillContent(reqResp reqResp, respContent openapi.Content, ctype string) (*openapi.MediaType, error) {
|
func fillContent(reqResp reqResp, respContent openapi.Content, ctype string, sampleId uint) (*openapi.MediaType, error) {
|
||||||
content, found := respContent[ctype]
|
content, found := respContent[ctype]
|
||||||
if !found {
|
if !found {
|
||||||
respContent[ctype] = &openapi.MediaType{}
|
respContent[ctype] = &openapi.MediaType{}
|
||||||
content = respContent[ctype]
|
content = respContent[ctype]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setSampleID(&content.Extensions, sampleId)
|
||||||
|
|
||||||
var text string
|
var text string
|
||||||
var isBinary bool
|
var isBinary bool
|
||||||
if reqResp.Req != nil {
|
if reqResp.Req != nil {
|
||||||
@@ -474,10 +486,10 @@ func fillContent(reqResp reqResp, respContent openapi.Content, ctype string) (*o
|
|||||||
if !isBinary && text != "" {
|
if !isBinary && text != "" {
|
||||||
var exampleMsg []byte
|
var exampleMsg []byte
|
||||||
// try treating it as json
|
// try treating it as json
|
||||||
any, isJSON := anyJSON(text)
|
anyVal, isJSON := anyJSON(text)
|
||||||
if isJSON {
|
if isJSON {
|
||||||
// re-marshal with forced indent
|
// re-marshal with forced indent
|
||||||
if msg, err := json.MarshalIndent(any, "", "\t"); err != nil {
|
if msg, err := json.MarshalIndent(anyVal, "", "\t"); err != nil {
|
||||||
panic("Failed to re-marshal value, super-strange")
|
panic("Failed to re-marshal value, super-strange")
|
||||||
} else {
|
} else {
|
||||||
exampleMsg = msg
|
exampleMsg = msg
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@ import (
|
|||||||
|
|
||||||
// if started via env, write file into subdir
|
// if started via env, write file into subdir
|
||||||
func outputSpec(label string, spec *openapi.OpenAPI, t *testing.T) string {
|
func outputSpec(label string, spec *openapi.OpenAPI, t *testing.T) string {
|
||||||
content, err := json.MarshalIndent(spec, "", "\t")
|
content, err := json.MarshalIndent(spec, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -48,14 +49,16 @@ func TestEntries(t *testing.T) {
|
|||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
GetDefaultOasGeneratorInstance().Start()
|
|
||||||
loadStartingOAS("test_artifacts/catalogue.json", "catalogue")
|
gen := NewDefaultOasGenerator(nil)
|
||||||
loadStartingOAS("test_artifacts/trcc.json", "trcc-api-service")
|
gen.serviceSpecs = new(sync.Map)
|
||||||
|
loadStartingOAS("test_artifacts/catalogue.json", "catalogue", gen.serviceSpecs)
|
||||||
|
loadStartingOAS("test_artifacts/trcc.json", "trcc-api-service", gen.serviceSpecs)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
GetDefaultOasGeneratorInstance().GetServiceSpecs().Range(func(key, val interface{}) bool {
|
gen.serviceSpecs.Range(func(key, val interface{}) bool {
|
||||||
svc := key.(string)
|
svc := key.(string)
|
||||||
t.Logf("Getting spec for %s", svc)
|
t.Logf("Getting spec for %s", svc)
|
||||||
gen := val.(*SpecGen)
|
gen := val.(*SpecGen)
|
||||||
@@ -68,16 +71,14 @@ func TestEntries(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
cnt, err := feedEntries(files, true)
|
cnt, err := feedEntries(files, true, gen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
|
||||||
waitQueueProcessed()
|
|
||||||
|
|
||||||
svcs := strings.Builder{}
|
svcs := strings.Builder{}
|
||||||
GetDefaultOasGeneratorInstance().GetServiceSpecs().Range(func(key, val interface{}) bool {
|
gen.serviceSpecs.Range(func(key, val interface{}) bool {
|
||||||
gen := val.(*SpecGen)
|
gen := val.(*SpecGen)
|
||||||
svc := key.(string)
|
svc := key.(string)
|
||||||
svcs.WriteString(svc + ",")
|
svcs.WriteString(svc + ",")
|
||||||
@@ -99,7 +100,7 @@ func TestEntries(t *testing.T) {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
GetDefaultOasGeneratorInstance().GetServiceSpecs().Range(func(key, val interface{}) bool {
|
gen.serviceSpecs.Range(func(key, val interface{}) bool {
|
||||||
svc := key.(string)
|
svc := key.(string)
|
||||||
gen := val.(*SpecGen)
|
gen := val.(*SpecGen)
|
||||||
spec, err := gen.GetSpec()
|
spec, err := gen.GetSpec()
|
||||||
@@ -123,20 +124,18 @@ func TestEntries(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileSingle(t *testing.T) {
|
func TestFileSingle(t *testing.T) {
|
||||||
GetDefaultOasGeneratorInstance().Start()
|
gen := NewDefaultOasGenerator(nil)
|
||||||
GetDefaultOasGeneratorInstance().Reset()
|
gen.serviceSpecs = new(sync.Map)
|
||||||
// loadStartingOAS()
|
// loadStartingOAS()
|
||||||
file := "test_artifacts/params.har"
|
file := "test_artifacts/params.har"
|
||||||
files := []string{file}
|
files := []string{file}
|
||||||
cnt, err := feedEntries(files, true)
|
cnt, err := feedEntries(files, true, gen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Log.Warning("Failed processing file: " + err.Error())
|
logger.Log.Warning("Failed processing file: " + err.Error())
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
|
||||||
waitQueueProcessed()
|
gen.serviceSpecs.Range(func(key, val interface{}) bool {
|
||||||
|
|
||||||
GetDefaultOasGeneratorInstance().GetServiceSpecs().Range(func(key, val interface{}) bool {
|
|
||||||
svc := key.(string)
|
svc := key.(string)
|
||||||
gen := val.(*SpecGen)
|
gen := val.(*SpecGen)
|
||||||
spec, err := gen.GetSpec()
|
spec, err := gen.GetSpec()
|
||||||
@@ -189,18 +188,7 @@ func TestFileSingle(t *testing.T) {
|
|||||||
logger.Log.Infof("Processed entries: %d", cnt)
|
logger.Log.Infof("Processed entries: %d", cnt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitQueueProcessed() {
|
func loadStartingOAS(file string, label string, specs *sync.Map) {
|
||||||
for {
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
queue := len(GetDefaultOasGeneratorInstance().entriesChan)
|
|
||||||
logger.Log.Infof("Queue: %d", queue)
|
|
||||||
if queue < 1 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadStartingOAS(file string, label string) {
|
|
||||||
fd, err := os.Open(file)
|
fd, err := os.Open(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -222,12 +210,14 @@ func loadStartingOAS(file string, label string) {
|
|||||||
gen := NewGen(label)
|
gen := NewGen(label)
|
||||||
gen.StartFromSpec(doc)
|
gen.StartFromSpec(doc)
|
||||||
|
|
||||||
GetDefaultOasGeneratorInstance().GetServiceSpecs().Store(label, gen)
|
specs.Store(label, gen)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEntriesNegative(t *testing.T) {
|
func TestEntriesNegative(t *testing.T) {
|
||||||
|
gen := NewDefaultOasGenerator(nil)
|
||||||
|
gen.serviceSpecs = new(sync.Map)
|
||||||
files := []string{"invalid"}
|
files := []string{"invalid"}
|
||||||
_, err := feedEntries(files, false)
|
_, err := feedEntries(files, false, gen)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Logf("Should have failed")
|
t.Logf("Should have failed")
|
||||||
t.Fail()
|
t.Fail()
|
||||||
@@ -235,8 +225,10 @@ func TestEntriesNegative(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEntriesPositive(t *testing.T) {
|
func TestEntriesPositive(t *testing.T) {
|
||||||
|
gen := NewDefaultOasGenerator(nil)
|
||||||
|
gen.serviceSpecs = new(sync.Map)
|
||||||
files := []string{"test_artifacts/params.har"}
|
files := []string{"test_artifacts/params.har"}
|
||||||
_, err := feedEntries(files, false)
|
_, err := feedEntries(files, false, gen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Logf("Failed")
|
t.Logf("Failed")
|
||||||
t.Fail()
|
t.Fail()
|
||||||
|
|||||||
@@ -21,9 +21,11 @@
|
|||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"example": null
|
"example": null,
|
||||||
|
"x-sample-entry": 4
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 4
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -45,7 +47,7 @@
|
|||||||
"sumDuration": 0
|
"sumDuration": 0
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750580.04,
|
"x-last-seen-ts": 1567750580.04,
|
||||||
"x-sample-entry": 0
|
"x-sample-entry": 4
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/appears-twice": {
|
"/appears-twice": {
|
||||||
@@ -58,9 +60,11 @@
|
|||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"example": null
|
"example": null,
|
||||||
|
"x-sample-entry": 6
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 6
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -82,7 +86,7 @@
|
|||||||
"sumDuration": 1
|
"sumDuration": 1
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750581.74,
|
"x-last-seen-ts": 1567750581.74,
|
||||||
"x-sample-entry": 0
|
"x-sample-entry": 6
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/body-optional": {
|
"/body-optional": {
|
||||||
@@ -94,8 +98,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"": {}
|
"": {
|
||||||
}
|
"x-sample-entry": 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-sample-entry": 12
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -117,14 +124,16 @@
|
|||||||
"sumDuration": 0.01
|
"sumDuration": 0.01
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750581.75,
|
"x-last-seen-ts": 1567750581.75,
|
||||||
"x-sample-entry": 0,
|
"x-sample-entry": 12,
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"description": "Generic request body",
|
"description": "Generic request body",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"example": "{\"key\", \"val\"}"
|
"example": "{\"key\", \"val\"}",
|
||||||
|
"x-sample-entry": 11
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 12
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -137,8 +146,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"": {}
|
"": {
|
||||||
}
|
"x-sample-entry": 13
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-sample-entry": 13
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -160,15 +172,17 @@
|
|||||||
"sumDuration": 0
|
"sumDuration": 0
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750581.75,
|
"x-last-seen-ts": 1567750581.75,
|
||||||
"x-sample-entry": 0,
|
"x-sample-entry": 13,
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"description": "Generic request body",
|
"description": "Generic request body",
|
||||||
"content": {
|
"content": {
|
||||||
"": {
|
"": {
|
||||||
"example": "body exists"
|
"example": "body exists",
|
||||||
|
"x-sample-entry": 13
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": true
|
"required": true,
|
||||||
|
"x-sample-entry": 13
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -182,9 +196,11 @@
|
|||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"": {
|
"": {
|
||||||
"example": {}
|
"example": {},
|
||||||
|
"x-sample-entry": 9
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 9
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -206,7 +222,7 @@
|
|||||||
"sumDuration": 0
|
"sumDuration": 0
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750582.74,
|
"x-last-seen-ts": 1567750582.74,
|
||||||
"x-sample-entry": 0,
|
"x-sample-entry": 9,
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"description": "Generic request body",
|
"description": "Generic request body",
|
||||||
"content": {
|
"content": {
|
||||||
@@ -233,10 +249,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"example": "--BOUNDARY\r\nContent-Disposition: form-data; name=\"file\"; filename=\"metadata.json\"\r\nContent-Type: application/json\r\n\r\n{\"functions\": 123}\r\n--BOUNDARY\r\nContent-Disposition: form-data; name=\"path\"\r\n\r\n/content/components\r\n--BOUNDARY--\r\n"
|
"example": "--BOUNDARY\r\nContent-Disposition: form-data; name=\"file\"; filename=\"metadata.json\"\r\nContent-Type: application/json\r\n\r\n{\"functions\": 123}\r\n--BOUNDARY\r\nContent-Disposition: form-data; name=\"path\"\r\n\r\n/content/components\r\n--BOUNDARY--\r\n",
|
||||||
|
"x-sample-entry": 9
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": true
|
"required": true,
|
||||||
|
"x-sample-entry": 9
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -249,8 +267,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"": {}
|
"": {
|
||||||
}
|
"x-sample-entry": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-sample-entry": 8
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -272,7 +293,7 @@
|
|||||||
"sumDuration": 1
|
"sumDuration": 1
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750581.74,
|
"x-last-seen-ts": 1567750581.74,
|
||||||
"x-sample-entry": 0,
|
"x-sample-entry": 8,
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"description": "Generic request body",
|
"description": "Generic request body",
|
||||||
"content": {
|
"content": {
|
||||||
@@ -312,10 +333,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"example": "agent-id=ade\u0026callback-url=\u0026token=sometoken"
|
"example": "agent-id=ade\u0026callback-url=\u0026token=sometoken",
|
||||||
|
"x-sample-entry": 8
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": true
|
"required": true,
|
||||||
|
"x-sample-entry": 8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -331,8 +354,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"": {}
|
"": {
|
||||||
}
|
"x-sample-entry": 14
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-sample-entry": 14
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -354,7 +380,7 @@
|
|||||||
"sumDuration": 0
|
"sumDuration": 0
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750582,
|
"x-last-seen-ts": 1567750582,
|
||||||
"x-sample-entry": 0
|
"x-sample-entry": 14
|
||||||
},
|
},
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -369,7 +395,8 @@
|
|||||||
"example #0": {
|
"example #0": {
|
||||||
"value": "234324"
|
"value": "234324"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 14
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -385,8 +412,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"": {}
|
"": {
|
||||||
}
|
"x-sample-entry": 18
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-sample-entry": 18
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -408,7 +438,7 @@
|
|||||||
"sumDuration": 9.53e-7
|
"sumDuration": 9.53e-7
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750582.00,
|
"x-last-seen-ts": 1567750582.00,
|
||||||
"x-sample-entry": 0
|
"x-sample-entry": 18
|
||||||
},
|
},
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -436,7 +466,8 @@
|
|||||||
"example #4": {
|
"example #4": {
|
||||||
"value": "prefix-gibberish-afterwards"
|
"value": "prefix-gibberish-afterwards"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 19
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -452,8 +483,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"": {}
|
"": {
|
||||||
}
|
"x-sample-entry": 15
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-sample-entry": 15
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -475,7 +509,7 @@
|
|||||||
"sumDuration": 0
|
"sumDuration": 0
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750582.00,
|
"x-last-seen-ts": 1567750582.00,
|
||||||
"x-sample-entry": 0
|
"x-sample-entry": 15
|
||||||
},
|
},
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -503,7 +537,8 @@
|
|||||||
"example #4": {
|
"example #4": {
|
||||||
"value": "prefix-gibberish-afterwards"
|
"value": "prefix-gibberish-afterwards"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 19
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -519,8 +554,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"": {}
|
"": {
|
||||||
}
|
"x-sample-entry": 16
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-sample-entry": 16
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -542,7 +580,7 @@
|
|||||||
"sumDuration": 0
|
"sumDuration": 0
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750582.00,
|
"x-last-seen-ts": 1567750582.00,
|
||||||
"x-sample-entry": 0
|
"x-sample-entry": 16
|
||||||
},
|
},
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -570,7 +608,8 @@
|
|||||||
"example #4": {
|
"example #4": {
|
||||||
"value": "prefix-gibberish-afterwards"
|
"value": "prefix-gibberish-afterwards"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 19
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -586,8 +625,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"": {}
|
"": {
|
||||||
}
|
"x-sample-entry": 19
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-sample-entry": 19
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -609,7 +651,7 @@
|
|||||||
"sumDuration": 0
|
"sumDuration": 0
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750582.00,
|
"x-last-seen-ts": 1567750582.00,
|
||||||
"x-sample-entry": 0
|
"x-sample-entry": 19
|
||||||
},
|
},
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -624,7 +666,8 @@
|
|||||||
"example #0": {
|
"example #0": {
|
||||||
"value": "23421"
|
"value": "23421"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 19
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "parampatternId",
|
"name": "parampatternId",
|
||||||
@@ -651,7 +694,8 @@
|
|||||||
"example #4": {
|
"example #4": {
|
||||||
"value": "prefix-gibberish-afterwards"
|
"value": "prefix-gibberish-afterwards"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 19
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -665,9 +709,11 @@
|
|||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"example": null
|
"example": null,
|
||||||
|
"x-sample-entry": 3
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 3
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -689,7 +735,7 @@
|
|||||||
"sumDuration": 0
|
"sumDuration": 0
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750579.74,
|
"x-last-seen-ts": 1567750579.74,
|
||||||
"x-sample-entry": 0
|
"x-sample-entry": 3
|
||||||
},
|
},
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -707,7 +753,8 @@
|
|||||||
"example #1": {
|
"example #1": {
|
||||||
"value": "<UUID4>"
|
"value": "<UUID4>"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 3
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -720,8 +767,11 @@
|
|||||||
"200": {
|
"200": {
|
||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"text/html": {}
|
"text/html": {
|
||||||
}
|
"x-sample-entry": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-sample-entry": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -743,7 +793,7 @@
|
|||||||
"sumDuration": 0
|
"sumDuration": 0
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750483.86,
|
"x-last-seen-ts": 1567750483.86,
|
||||||
"x-sample-entry": 0
|
"x-sample-entry": 1
|
||||||
},
|
},
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -761,7 +811,8 @@
|
|||||||
"example #1": {
|
"example #1": {
|
||||||
"value": "<UUID4>"
|
"value": "<UUID4>"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 3
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -775,9 +826,11 @@
|
|||||||
"description": "Successful call with status 200",
|
"description": "Successful call with status 200",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"example": null
|
"example": null,
|
||||||
|
"x-sample-entry": 2
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 2
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-counters-per-source": {
|
"x-counters-per-source": {
|
||||||
@@ -799,7 +852,7 @@
|
|||||||
"sumDuration": 0
|
"sumDuration": 0
|
||||||
},
|
},
|
||||||
"x-last-seen-ts": 1567750578.74,
|
"x-last-seen-ts": 1567750578.74,
|
||||||
"x-sample-entry": 0
|
"x-sample-entry": 2
|
||||||
},
|
},
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -817,7 +870,8 @@
|
|||||||
"example #1": {
|
"example #1": {
|
||||||
"value": "<UUID4>"
|
"value": "<UUID4>"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"x-sample-entry": 3
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type Node struct {
|
|||||||
children []*Node
|
children []*Node
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Node) getOrSet(path NodePath, existingPathObj *openapi.PathObj) (node *Node) {
|
func (n *Node) getOrSet(path NodePath, existingPathObj *openapi.PathObj, sampleId uint) (node *Node) {
|
||||||
if existingPathObj == nil {
|
if existingPathObj == nil {
|
||||||
panic("Invalid function call")
|
panic("Invalid function call")
|
||||||
}
|
}
|
||||||
@@ -70,6 +70,10 @@ func (n *Node) getOrSet(path NodePath, existingPathObj *openapi.PathObj) (node *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if node.pathParam != nil {
|
||||||
|
setSampleID(&node.pathParam.Extensions, sampleId)
|
||||||
|
}
|
||||||
|
|
||||||
// add example if it's a gibberish chunk
|
// add example if it's a gibberish chunk
|
||||||
if node.pathParam != nil && !chunkIsParam {
|
if node.pathParam != nil && !chunkIsParam {
|
||||||
exmp := &node.pathParam.Examples
|
exmp := &node.pathParam.Examples
|
||||||
@@ -85,7 +89,7 @@ func (n *Node) getOrSet(path NodePath, existingPathObj *openapi.PathObj) (node *
|
|||||||
|
|
||||||
// TODO: eat up trailing slash, in a smart way: node.pathObj!=nil && path[1]==""
|
// TODO: eat up trailing slash, in a smart way: node.pathObj!=nil && path[1]==""
|
||||||
if len(path) > 1 {
|
if len(path) > 1 {
|
||||||
return node.getOrSet(path[1:], existingPathObj)
|
return node.getOrSet(path[1:], existingPathObj, sampleId)
|
||||||
} else if node.pathObj == nil {
|
} else if node.pathObj == nil {
|
||||||
node.pathObj = existingPathObj
|
node.pathObj = existingPathObj
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ func TestTree(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tree := new(Node)
|
tree := new(Node)
|
||||||
for _, tc := range testCases {
|
for i, tc := range testCases {
|
||||||
split := strings.Split(tc.inp, "/")
|
split := strings.Split(tc.inp, "/")
|
||||||
pathObj := new(openapi.PathObj)
|
pathObj := new(openapi.PathObj)
|
||||||
node := tree.getOrSet(split, pathObj)
|
node := tree.getOrSet(split, pathObj, uint(i))
|
||||||
|
|
||||||
fillPathParams(node, pathObj)
|
fillPathParams(node, pathObj)
|
||||||
|
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ type nvParams struct {
|
|||||||
GeneralizeName func(name string) string
|
GeneralizeName func(name string) string
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleNameVals(gw nvParams, params **openapi.ParameterList, checkIgnore bool) {
|
func handleNameVals(gw nvParams, params **openapi.ParameterList, checkIgnore bool, sampleId uint) {
|
||||||
visited := map[string]*openapi.ParameterObj{}
|
visited := map[string]*openapi.ParameterObj{}
|
||||||
for _, pair := range gw.Pairs {
|
for _, pair := range gw.Pairs {
|
||||||
if (checkIgnore && gw.IsIgnored(pair.Name)) || pair.Name == "" {
|
if (checkIgnore && gw.IsIgnored(pair.Name)) || pair.Name == "" {
|
||||||
@@ -137,6 +137,8 @@ func handleNameVals(gw nvParams, params **openapi.ParameterList, checkIgnore boo
|
|||||||
logger.Log.Warningf("Failed to add example to a parameter: %s", err)
|
logger.Log.Warningf("Failed to add example to a parameter: %s", err)
|
||||||
}
|
}
|
||||||
visited[nameGeneral] = param
|
visited[nameGeneral] = param
|
||||||
|
|
||||||
|
setSampleID(¶m.Extensions, sampleId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// maintain "required" flag
|
// maintain "required" flag
|
||||||
@@ -474,3 +476,15 @@ func intersectSliceWithMap(required []string, names map[string]struct{}) []strin
|
|||||||
}
|
}
|
||||||
return required
|
return required
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setSampleID(extensions *openapi.Extensions, id uint) {
|
||||||
|
if id > 0 {
|
||||||
|
if *extensions == nil {
|
||||||
|
*extensions = openapi.Extensions{}
|
||||||
|
}
|
||||||
|
err := (extensions).SetExtension(SampleId, id)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Warningf("Failed to set sample ID: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -103,7 +103,8 @@ func StartPassiveTapper(opts *TapOpts, outputItems chan *api.OutputChannelItem,
|
|||||||
diagnose.StartMemoryProfiler(os.Getenv(MemoryProfilingDumpPath), os.Getenv(MemoryProfilingTimeIntervalSeconds))
|
diagnose.StartMemoryProfiler(os.Getenv(MemoryProfilingDumpPath), os.Getenv(MemoryProfilingTimeIntervalSeconds))
|
||||||
}
|
}
|
||||||
|
|
||||||
go startPassiveTapper(opts, outputItems)
|
streamsMap, assembler := initializePassiveTapper(opts, outputItems)
|
||||||
|
go startPassiveTapper(streamsMap, assembler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateTapTargets(newTapTargets []v1.Pod) {
|
func UpdateTapTargets(newTapTargets []v1.Pod) {
|
||||||
@@ -205,9 +206,8 @@ func initializePacketSources() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func startPassiveTapper(opts *TapOpts, outputItems chan *api.OutputChannelItem) {
|
func initializePassiveTapper(opts *TapOpts, outputItems chan *api.OutputChannelItem) (*tcpStreamMap, *tcpAssembler) {
|
||||||
streamsMap := NewTcpStreamMap()
|
streamsMap := NewTcpStreamMap()
|
||||||
go streamsMap.closeTimedoutTcpStreamChannels()
|
|
||||||
|
|
||||||
diagnose.InitializeErrorsMap(*debug, *verbose, *quiet)
|
diagnose.InitializeErrorsMap(*debug, *verbose, *quiet)
|
||||||
diagnose.InitializeTapperInternalStats()
|
diagnose.InitializeTapperInternalStats()
|
||||||
@@ -220,6 +220,12 @@ func startPassiveTapper(opts *TapOpts, outputItems chan *api.OutputChannelItem)
|
|||||||
|
|
||||||
assembler := NewTcpAssembler(outputItems, streamsMap, opts)
|
assembler := NewTcpAssembler(outputItems, streamsMap, opts)
|
||||||
|
|
||||||
|
return streamsMap, assembler
|
||||||
|
}
|
||||||
|
|
||||||
|
func startPassiveTapper(streamsMap *tcpStreamMap, assembler *tcpAssembler) {
|
||||||
|
go streamsMap.closeTimedoutTcpStreamChannels()
|
||||||
|
|
||||||
diagnose.AppStats.SetStartTime(time.Now())
|
diagnose.AppStats.SetStartTime(time.Now())
|
||||||
|
|
||||||
staleConnectionTimeout := time.Second * time.Duration(*staleTimeoutSeconds)
|
staleConnectionTimeout := time.Second * time.Duration(*staleTimeoutSeconds)
|
||||||
@@ -253,9 +259,10 @@ func startPassiveTapper(opts *TapOpts, outputItems chan *api.OutputChannelItem)
|
|||||||
|
|
||||||
func startTlsTapper(extension *api.Extension, outputItems chan *api.OutputChannelItem, options *api.TrafficFilteringOptions) *tlstapper.TlsTapper {
|
func startTlsTapper(extension *api.Extension, outputItems chan *api.OutputChannelItem, options *api.TrafficFilteringOptions) *tlstapper.TlsTapper {
|
||||||
tls := tlstapper.TlsTapper{}
|
tls := tlstapper.TlsTapper{}
|
||||||
tlsPerfBufferSize := os.Getpagesize() * 100
|
chunksBufferSize := os.Getpagesize() * 100
|
||||||
|
logBufferSize := os.Getpagesize()
|
||||||
|
|
||||||
if err := tls.Init(tlsPerfBufferSize, *procfs, extension); err != nil {
|
if err := tls.Init(chunksBufferSize, logBufferSize, *procfs, extension); err != nil {
|
||||||
tlstapper.LogError(err)
|
tlstapper.LogError(err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -279,6 +286,7 @@ func startTlsTapper(extension *api.Extension, outputItems chan *api.OutputChanne
|
|||||||
OutputChannel: outputItems,
|
OutputChannel: outputItems,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go tls.PollForLogging()
|
||||||
go tls.Poll(emitter, options)
|
go tls.Poll(emitter, options)
|
||||||
|
|
||||||
return &tls
|
return &tls
|
||||||
|
|||||||
@@ -7,8 +7,12 @@ Copyright (C) UP9 Inc.
|
|||||||
#include "include/headers.h"
|
#include "include/headers.h"
|
||||||
#include "include/util.h"
|
#include "include/util.h"
|
||||||
#include "include/maps.h"
|
#include "include/maps.h"
|
||||||
|
#include "include/log.h"
|
||||||
|
#include "include/logger_messages.h"
|
||||||
#include "include/pids.h"
|
#include "include/pids.h"
|
||||||
|
|
||||||
|
#define IPV4_ADDR_LEN (16)
|
||||||
|
|
||||||
struct accept_info {
|
struct accept_info {
|
||||||
__u64* sockaddr;
|
__u64* sockaddr;
|
||||||
__u32* addrlen;
|
__u32* addrlen;
|
||||||
@@ -41,9 +45,7 @@ void sys_enter_accept4(struct sys_enter_accept4_ctx *ctx) {
|
|||||||
long err = bpf_map_update_elem(&accept_syscall_context, &id, &info, BPF_ANY);
|
long err = bpf_map_update_elem(&accept_syscall_context, &id, &info, BPF_ANY);
|
||||||
|
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
char msg[] = "Error putting accept info (id: %ld) (err: %ld)";
|
log_error(ctx, LOG_ERROR_PUTTING_ACCEPT_INFO, id, err, 0l);
|
||||||
bpf_trace_printk(msg, sizeof(msg), id, err);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +72,7 @@ void sys_exit_accept4(struct sys_exit_accept4_ctx *ctx) {
|
|||||||
struct accept_info *infoPtr = bpf_map_lookup_elem(&accept_syscall_context, &id);
|
struct accept_info *infoPtr = bpf_map_lookup_elem(&accept_syscall_context, &id);
|
||||||
|
|
||||||
if (infoPtr == NULL) {
|
if (infoPtr == NULL) {
|
||||||
|
log_error(ctx, LOG_ERROR_GETTING_ACCEPT_INFO, id, 0l, 0l);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,15 +82,14 @@ void sys_exit_accept4(struct sys_exit_accept4_ctx *ctx) {
|
|||||||
bpf_map_delete_elem(&accept_syscall_context, &id);
|
bpf_map_delete_elem(&accept_syscall_context, &id);
|
||||||
|
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
char msg[] = "Error reading accept info from accept syscall (id: %ld) (err: %ld)";
|
log_error(ctx, LOG_ERROR_READING_ACCEPT_INFO, id, err, 0l);
|
||||||
bpf_trace_printk(msg, sizeof(msg), id, err);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
__u32 addrlen;
|
__u32 addrlen;
|
||||||
bpf_probe_read(&addrlen, sizeof(__u32), info.addrlen);
|
bpf_probe_read(&addrlen, sizeof(__u32), info.addrlen);
|
||||||
|
|
||||||
if (addrlen != 16) {
|
if (addrlen != IPV4_ADDR_LEN) {
|
||||||
// Currently only ipv4 is supported linux-src/include/linux/inet.h
|
// Currently only ipv4 is supported linux-src/include/linux/inet.h
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -105,9 +107,7 @@ void sys_exit_accept4(struct sys_exit_accept4_ctx *ctx) {
|
|||||||
err = bpf_map_update_elem(&file_descriptor_to_ipv4, &key, &fdinfo, BPF_ANY);
|
err = bpf_map_update_elem(&file_descriptor_to_ipv4, &key, &fdinfo, BPF_ANY);
|
||||||
|
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
char msg[] = "Error putting fd to address mapping from accept (key: %ld) (err: %ld)";
|
log_error(ctx, LOG_ERROR_PUTTING_FD_MAPPING, id, err, ORIGIN_SYS_EXIT_ACCEPT4_CODE);
|
||||||
bpf_trace_printk(msg, sizeof(msg), key, err);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,9 +145,7 @@ void sys_enter_connect(struct sys_enter_connect_ctx *ctx) {
|
|||||||
long err = bpf_map_update_elem(&connect_syscall_info, &id, &info, BPF_ANY);
|
long err = bpf_map_update_elem(&connect_syscall_info, &id, &info, BPF_ANY);
|
||||||
|
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
char msg[] = "Error putting connect info (id: %ld) (err: %ld)";
|
log_error(ctx, LOG_ERROR_PUTTING_CONNECT_INFO, id, err, 0l);
|
||||||
bpf_trace_printk(msg, sizeof(msg), id, err);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,6 +174,7 @@ void sys_exit_connect(struct sys_exit_connect_ctx *ctx) {
|
|||||||
struct connect_info *infoPtr = bpf_map_lookup_elem(&connect_syscall_info, &id);
|
struct connect_info *infoPtr = bpf_map_lookup_elem(&connect_syscall_info, &id);
|
||||||
|
|
||||||
if (infoPtr == NULL) {
|
if (infoPtr == NULL) {
|
||||||
|
log_error(ctx, LOG_ERROR_GETTING_CONNECT_INFO, id, 0l, 0l);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,12 +184,11 @@ void sys_exit_connect(struct sys_exit_connect_ctx *ctx) {
|
|||||||
bpf_map_delete_elem(&connect_syscall_info, &id);
|
bpf_map_delete_elem(&connect_syscall_info, &id);
|
||||||
|
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
char msg[] = "Error reading connect info from connect syscall (id: %ld) (err: %ld)";
|
log_error(ctx, LOG_ERROR_READING_CONNECT_INFO, id, err, 0l);
|
||||||
bpf_trace_printk(msg, sizeof(msg), id, err);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info.addrlen != 16) {
|
if (info.addrlen != IPV4_ADDR_LEN) {
|
||||||
// Currently only ipv4 is supported linux-src/include/linux/inet.h
|
// Currently only ipv4 is supported linux-src/include/linux/inet.h
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -208,8 +206,6 @@ void sys_exit_connect(struct sys_exit_connect_ctx *ctx) {
|
|||||||
err = bpf_map_update_elem(&file_descriptor_to_ipv4, &key, &fdinfo, BPF_ANY);
|
err = bpf_map_update_elem(&file_descriptor_to_ipv4, &key, &fdinfo, BPF_ANY);
|
||||||
|
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
char msg[] = "Error putting fd to address mapping from connect (key: %ld) (err: %ld)";
|
log_error(ctx, LOG_ERROR_PUTTING_FD_MAPPING, id, err, ORIGIN_SYS_EXIT_CONNECT_CODE);
|
||||||
bpf_trace_printk(msg, sizeof(msg), key, err);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ Copyright (C) UP9 Inc.
|
|||||||
#include "include/headers.h"
|
#include "include/headers.h"
|
||||||
#include "include/util.h"
|
#include "include/util.h"
|
||||||
#include "include/maps.h"
|
#include "include/maps.h"
|
||||||
|
#include "include/log.h"
|
||||||
|
#include "include/logger_messages.h"
|
||||||
#include "include/pids.h"
|
#include "include/pids.h"
|
||||||
|
|
||||||
struct sys_enter_read_ctx {
|
struct sys_enter_read_ctx {
|
||||||
@@ -36,8 +38,7 @@ void sys_enter_read(struct sys_enter_read_ctx *ctx) {
|
|||||||
long err = bpf_probe_read(&info, sizeof(struct ssl_info), infoPtr);
|
long err = bpf_probe_read(&info, sizeof(struct ssl_info), infoPtr);
|
||||||
|
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
char msg[] = "Error reading read info from read syscall (id: %ld) (err: %ld)";
|
log_error(ctx, LOG_ERROR_READING_SSL_CONTEXT, id, err, ORIGIN_SYS_ENTER_READ_CODE);
|
||||||
bpf_trace_printk(msg, sizeof(msg), id, err);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,9 +47,7 @@ void sys_enter_read(struct sys_enter_read_ctx *ctx) {
|
|||||||
err = bpf_map_update_elem(&ssl_read_context, &id, &info, BPF_ANY);
|
err = bpf_map_update_elem(&ssl_read_context, &id, &info, BPF_ANY);
|
||||||
|
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
char msg[] = "Error putting file descriptor from read syscall (id: %ld) (err: %ld)";
|
log_error(ctx, LOG_ERROR_PUTTING_FILE_DESCRIPTOR, id, err, ORIGIN_SYS_ENTER_READ_CODE);
|
||||||
bpf_trace_printk(msg, sizeof(msg), id, err);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,8 +78,7 @@ void sys_enter_write(struct sys_enter_write_ctx *ctx) {
|
|||||||
long err = bpf_probe_read(&info, sizeof(struct ssl_info), infoPtr);
|
long err = bpf_probe_read(&info, sizeof(struct ssl_info), infoPtr);
|
||||||
|
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
char msg[] = "Error reading write context from write syscall (id: %ld) (err: %ld)";
|
log_error(ctx, LOG_ERROR_READING_SSL_CONTEXT, id, err, ORIGIN_SYS_ENTER_WRITE_CODE);
|
||||||
bpf_trace_printk(msg, sizeof(msg), id, err);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,8 +87,6 @@ void sys_enter_write(struct sys_enter_write_ctx *ctx) {
|
|||||||
err = bpf_map_update_elem(&ssl_write_context, &id, &info, BPF_ANY);
|
err = bpf_map_update_elem(&ssl_write_context, &id, &info, BPF_ANY);
|
||||||
|
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
char msg[] = "Error putting file descriptor from write syscall (id: %ld) (err: %ld)";
|
log_error(ctx, LOG_ERROR_PUTTING_FILE_DESCRIPTOR, id, err, ORIGIN_SYS_ENTER_WRITE_CODE);
|
||||||
bpf_trace_printk(msg, sizeof(msg), id, err);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
79
tap/tlstapper/bpf/include/log.h
Normal file
79
tap/tlstapper/bpf/include/log.h
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
Note: This file is licenced differently from the rest of the project
|
||||||
|
SPDX-License-Identifier: GPL-2.0
|
||||||
|
Copyright (C) UP9 Inc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __LOG__
|
||||||
|
#define __LOG__
|
||||||
|
|
||||||
|
// The same consts defined in bpf_logger.go
|
||||||
|
//
|
||||||
|
#define LOG_LEVEL_ERROR (0)
|
||||||
|
#define LOG_LEVEL_INFO (1)
|
||||||
|
#define LOG_LEVEL_DEBUG (2)
|
||||||
|
|
||||||
|
// The same struct can be found in bpf_logger.go
|
||||||
|
//
|
||||||
|
// Be careful when editing, alignment and padding should be exactly the same in go/c.
|
||||||
|
//
|
||||||
|
struct log_message {
|
||||||
|
__u32 level;
|
||||||
|
__u32 message_code;
|
||||||
|
__u64 arg1;
|
||||||
|
__u64 arg2;
|
||||||
|
__u64 arg3;
|
||||||
|
};
|
||||||
|
|
||||||
|
static __always_inline void log_error(void* ctx, __u16 message_code, __u64 arg1, __u64 arg2, __u64 arg3) {
|
||||||
|
struct log_message entry = {};
|
||||||
|
|
||||||
|
entry.level = LOG_LEVEL_ERROR;
|
||||||
|
entry.message_code = message_code;
|
||||||
|
entry.arg1 = arg1;
|
||||||
|
entry.arg2 = arg2;
|
||||||
|
entry.arg3 = arg3;
|
||||||
|
|
||||||
|
long err = bpf_perf_event_output(ctx, &log_buffer, BPF_F_CURRENT_CPU, &entry, sizeof(struct log_message));
|
||||||
|
|
||||||
|
if (err != 0) {
|
||||||
|
char msg[] = "Error writing log error to perf buffer - %ld";
|
||||||
|
bpf_trace_printk(msg, sizeof(msg), err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static __always_inline void log_info(void* ctx, __u16 message_code, __u64 arg1, __u64 arg2, __u64 arg3) {
|
||||||
|
struct log_message entry = {};
|
||||||
|
|
||||||
|
entry.level = LOG_LEVEL_INFO;
|
||||||
|
entry.message_code = message_code;
|
||||||
|
entry.arg1 = arg1;
|
||||||
|
entry.arg2 = arg2;
|
||||||
|
entry.arg3 = arg3;
|
||||||
|
|
||||||
|
long err = bpf_perf_event_output(ctx, &log_buffer, BPF_F_CURRENT_CPU, &entry, sizeof(struct log_message));
|
||||||
|
|
||||||
|
if (err != 0) {
|
||||||
|
char msg[] = "Error writing log info to perf buffer - %ld";
|
||||||
|
bpf_trace_printk(msg, sizeof(msg), arg1, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static __always_inline void log_debug(void* ctx, __u16 message_code, __u64 arg1, __u64 arg2, __u64 arg3) {
|
||||||
|
struct log_message entry = {};
|
||||||
|
|
||||||
|
entry.level = LOG_LEVEL_DEBUG;
|
||||||
|
entry.message_code = message_code;
|
||||||
|
entry.arg1 = arg1;
|
||||||
|
entry.arg2 = arg2;
|
||||||
|
entry.arg3 = arg3;
|
||||||
|
|
||||||
|
long err = bpf_perf_event_output(ctx, &log_buffer, BPF_F_CURRENT_CPU, &entry, sizeof(struct log_message));
|
||||||
|
|
||||||
|
if (err != 0) {
|
||||||
|
char msg[] = "Error writing log debug to perf buffer - %ld";
|
||||||
|
bpf_trace_printk(msg, sizeof(msg), arg1, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* __LOG__ */
|
||||||
42
tap/tlstapper/bpf/include/logger_messages.h
Normal file
42
tap/tlstapper/bpf/include/logger_messages.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
Note: This file is licenced differently from the rest of the project
|
||||||
|
SPDX-License-Identifier: GPL-2.0
|
||||||
|
Copyright (C) UP9 Inc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __LOG_MESSAGES__
|
||||||
|
#define __LOG_MESSAGES__
|
||||||
|
|
||||||
|
// Must be synced with bpf_logger_messages.go
|
||||||
|
//
|
||||||
|
#define LOG_ERROR_READING_BYTES_COUNT (0)
|
||||||
|
#define LOG_ERROR_READING_FD_ADDRESS (1)
|
||||||
|
#define LOG_ERROR_READING_FROM_SSL_BUFFER (2)
|
||||||
|
#define LOG_ERROR_BUFFER_TOO_BIG (3)
|
||||||
|
#define LOG_ERROR_ALLOCATING_CHUNK (4)
|
||||||
|
#define LOG_ERROR_READING_SSL_CONTEXT (5)
|
||||||
|
#define LOG_ERROR_PUTTING_SSL_CONTEXT (6)
|
||||||
|
#define LOG_ERROR_GETTING_SSL_CONTEXT (7)
|
||||||
|
#define LOG_ERROR_MISSING_FILE_DESCRIPTOR (8)
|
||||||
|
#define LOG_ERROR_PUTTING_FILE_DESCRIPTOR (9)
|
||||||
|
#define LOG_ERROR_PUTTING_ACCEPT_INFO (10)
|
||||||
|
#define LOG_ERROR_GETTING_ACCEPT_INFO (11)
|
||||||
|
#define LOG_ERROR_READING_ACCEPT_INFO (12)
|
||||||
|
#define LOG_ERROR_PUTTING_FD_MAPPING (13)
|
||||||
|
#define LOG_ERROR_PUTTING_CONNECT_INFO (14)
|
||||||
|
#define LOG_ERROR_GETTING_CONNECT_INFO (15)
|
||||||
|
#define LOG_ERROR_READING_CONNECT_INFO (16)
|
||||||
|
|
||||||
|
// Sometimes we have the same error, happening from different locations.
|
||||||
|
// in order to be able to distinct between them in the log, we add an
|
||||||
|
// extra number that identify the location. The number can be anything,
|
||||||
|
// but do not give the same number to different origins.
|
||||||
|
//
|
||||||
|
#define ORIGIN_SSL_UPROBE_CODE (0l)
|
||||||
|
#define ORIGIN_SSL_URETPROBE_CODE (1l)
|
||||||
|
#define ORIGIN_SYS_ENTER_READ_CODE (2l)
|
||||||
|
#define ORIGIN_SYS_ENTER_WRITE_CODE (3l)
|
||||||
|
#define ORIGIN_SYS_EXIT_ACCEPT4_CODE (4l)
|
||||||
|
#define ORIGIN_SYS_EXIT_CONNECT_CODE (5l)
|
||||||
|
|
||||||
|
#endif /* __LOG_MESSAGES__ */
|
||||||
@@ -70,5 +70,6 @@ BPF_LRU_HASH(ssl_write_context, __u64, struct ssl_info);
|
|||||||
BPF_LRU_HASH(ssl_read_context, __u64, struct ssl_info);
|
BPF_LRU_HASH(ssl_read_context, __u64, struct ssl_info);
|
||||||
BPF_HASH(file_descriptor_to_ipv4, __u64, struct fd_info);
|
BPF_HASH(file_descriptor_to_ipv4, __u64, struct fd_info);
|
||||||
BPF_PERF_OUTPUT(chunks_buffer);
|
BPF_PERF_OUTPUT(chunks_buffer);
|
||||||
|
BPF_PERF_OUTPUT(log_buffer);
|
||||||
|
|
||||||
#endif /* __MAPS__ */
|
#endif /* __MAPS__ */
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ Copyright (C) UP9 Inc.
|
|||||||
#include "include/headers.h"
|
#include "include/headers.h"
|
||||||
#include "include/util.h"
|
#include "include/util.h"
|
||||||
#include "include/maps.h"
|
#include "include/maps.h"
|
||||||
|
#include "include/log.h"
|
||||||
|
#include "include/logger_messages.h"
|
||||||
#include "include/pids.h"
|
#include "include/pids.h"
|
||||||
|
|
||||||
// Heap-like area for eBPF programs - stack size limited to 512 bytes, we must use maps for bigger (chunk) objects.
|
// Heap-like area for eBPF programs - stack size limited to 512 bytes, we must use maps for bigger (chunk) objects.
|
||||||
@@ -39,15 +41,14 @@ static __always_inline int get_count_bytes(struct pt_regs *ctx, struct ssl_info*
|
|||||||
long err = bpf_probe_read(&countBytes, sizeof(size_t), (void*) info->count_ptr);
|
long err = bpf_probe_read(&countBytes, sizeof(size_t), (void*) info->count_ptr);
|
||||||
|
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
char msg[] = "Error reading bytes count of _ex (id: %ld) (err: %ld)";
|
log_error(ctx, LOG_ERROR_READING_BYTES_COUNT, id, err, 0l);
|
||||||
bpf_trace_printk(msg, sizeof(msg), id, err);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return countBytes;
|
return countBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
static __always_inline void add_address_to_chunk(struct tlsChunk* chunk, __u64 id, __u32 fd) {
|
static __always_inline void add_address_to_chunk(struct pt_regs *ctx, struct tlsChunk* chunk, __u64 id, __u32 fd) {
|
||||||
__u32 pid = id >> 32;
|
__u32 pid = id >> 32;
|
||||||
__u64 key = (__u64) pid << 32 | fd;
|
__u64 key = (__u64) pid << 32 | fd;
|
||||||
|
|
||||||
@@ -61,8 +62,7 @@ static __always_inline void add_address_to_chunk(struct tlsChunk* chunk, __u64 i
|
|||||||
chunk->flags |= (fdinfo->flags & FLAGS_IS_CLIENT_BIT);
|
chunk->flags |= (fdinfo->flags & FLAGS_IS_CLIENT_BIT);
|
||||||
|
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
char msg[] = "Error reading from fd address %ld - %ld";
|
log_error(ctx, LOG_ERROR_READING_FD_ADDRESS, id, err, 0l);
|
||||||
bpf_trace_printk(msg, sizeof(msg), id, err);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,8 +88,7 @@ static __always_inline void send_chunk_part(struct pt_regs *ctx, __u8* buffer, _
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
char msg[] = "Error reading from ssl buffer %ld - %ld";
|
log_error(ctx, LOG_ERROR_READING_FROM_SSL_BUFFER, id, err, 0l);
|
||||||
bpf_trace_printk(msg, sizeof(msg), id, err);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,8 +100,9 @@ static __always_inline void send_chunk(struct pt_regs *ctx, __u8* buffer, __u64
|
|||||||
//
|
//
|
||||||
// https://lwn.net/Articles/794934/
|
// https://lwn.net/Articles/794934/
|
||||||
//
|
//
|
||||||
// If we want to compile in kernel older than 5.3, we should add "#pragma unroll" to this loop
|
// However we want to run in kernel older than 5.3, hence we use "#pragma unroll" anyway
|
||||||
//
|
//
|
||||||
|
#pragma unroll
|
||||||
for (int i = 0; i < MAX_CHUNKS_PER_OPERATION; i++) {
|
for (int i = 0; i < MAX_CHUNKS_PER_OPERATION; i++) {
|
||||||
if (chunk->len <= (CHUNK_SIZE * i)) {
|
if (chunk->len <= (CHUNK_SIZE * i)) {
|
||||||
break;
|
break;
|
||||||
@@ -120,8 +120,7 @@ static __always_inline void output_ssl_chunk(struct pt_regs *ctx, struct ssl_inf
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (countBytes > (CHUNK_SIZE * MAX_CHUNKS_PER_OPERATION)) {
|
if (countBytes > (CHUNK_SIZE * MAX_CHUNKS_PER_OPERATION)) {
|
||||||
char msg[] = "Buffer too big %d (id: %ld)";
|
log_error(ctx, LOG_ERROR_BUFFER_TOO_BIG, id, countBytes, 0l);
|
||||||
bpf_trace_printk(msg, sizeof(msg), countBytes, id);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,8 +133,7 @@ static __always_inline void output_ssl_chunk(struct pt_regs *ctx, struct ssl_inf
|
|||||||
chunk = bpf_map_lookup_elem(&heap, &zero);
|
chunk = bpf_map_lookup_elem(&heap, &zero);
|
||||||
|
|
||||||
if (!chunk) {
|
if (!chunk) {
|
||||||
char msg[] = "Unable to allocate chunk (id: %ld)";
|
log_error(ctx, LOG_ERROR_ALLOCATING_CHUNK, id, 0l, 0l);
|
||||||
bpf_trace_printk(msg, sizeof(msg), id);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,11 +143,11 @@ static __always_inline void output_ssl_chunk(struct pt_regs *ctx, struct ssl_inf
|
|||||||
chunk->len = countBytes;
|
chunk->len = countBytes;
|
||||||
chunk->fd = info->fd;
|
chunk->fd = info->fd;
|
||||||
|
|
||||||
add_address_to_chunk(chunk, id, chunk->fd);
|
add_address_to_chunk(ctx, chunk, id, chunk->fd);
|
||||||
send_chunk(ctx, info->buffer, id, chunk);
|
send_chunk(ctx, info->buffer, id, chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
static __always_inline void ssl_uprobe(void* ssl, void* buffer, int num, struct bpf_map_def* map_fd, size_t *count_ptr) {
|
static __always_inline void ssl_uprobe(struct pt_regs *ctx, void* ssl, void* buffer, int num, struct bpf_map_def* map_fd, size_t *count_ptr) {
|
||||||
__u64 id = bpf_get_current_pid_tgid();
|
__u64 id = bpf_get_current_pid_tgid();
|
||||||
|
|
||||||
if (!should_tap(id >> 32)) {
|
if (!should_tap(id >> 32)) {
|
||||||
@@ -166,8 +164,7 @@ static __always_inline void ssl_uprobe(void* ssl, void* buffer, int num, struct
|
|||||||
long err = bpf_probe_read(&info, sizeof(struct ssl_info), infoPtr);
|
long err = bpf_probe_read(&info, sizeof(struct ssl_info), infoPtr);
|
||||||
|
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
char msg[] = "Error reading old ssl context (id: %ld) (err: %ld)";
|
log_error(ctx, LOG_ERROR_READING_SSL_CONTEXT, id, err, ORIGIN_SSL_UPROBE_CODE);
|
||||||
bpf_trace_printk(msg, sizeof(msg), id, err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((bpf_ktime_get_ns() - info.created_at_nano) > SSL_INFO_MAX_TTL_NANO) {
|
if ((bpf_ktime_get_ns() - info.created_at_nano) > SSL_INFO_MAX_TTL_NANO) {
|
||||||
@@ -184,8 +181,7 @@ static __always_inline void ssl_uprobe(void* ssl, void* buffer, int num, struct
|
|||||||
long err = bpf_map_update_elem(map_fd, &id, &info, BPF_ANY);
|
long err = bpf_map_update_elem(map_fd, &id, &info, BPF_ANY);
|
||||||
|
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
char msg[] = "Error putting ssl context (id: %ld) (err: %ld)";
|
log_error(ctx, LOG_ERROR_PUTTING_SSL_CONTEXT, id, err, 0l);
|
||||||
bpf_trace_printk(msg, sizeof(msg), id, err);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,8 +195,7 @@ static __always_inline void ssl_uretprobe(struct pt_regs *ctx, struct bpf_map_de
|
|||||||
struct ssl_info *infoPtr = bpf_map_lookup_elem(map_fd, &id);
|
struct ssl_info *infoPtr = bpf_map_lookup_elem(map_fd, &id);
|
||||||
|
|
||||||
if (infoPtr == NULL) {
|
if (infoPtr == NULL) {
|
||||||
char msg[] = "Error getting ssl context info (id: %ld)";
|
log_error(ctx, LOG_ERROR_GETTING_SSL_CONTEXT, id, 0l, 0l);
|
||||||
bpf_trace_printk(msg, sizeof(msg), id);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,14 +215,12 @@ static __always_inline void ssl_uretprobe(struct pt_regs *ctx, struct bpf_map_de
|
|||||||
// bpf_map_delete_elem(map_fd, &id);
|
// bpf_map_delete_elem(map_fd, &id);
|
||||||
|
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
char msg[] = "Error reading ssl context (id: %ld) (err: %ld)";
|
log_error(ctx, LOG_ERROR_READING_SSL_CONTEXT, id, err, ORIGIN_SSL_URETPROBE_CODE);
|
||||||
bpf_trace_printk(msg, sizeof(msg), id, err);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info.fd == -1) {
|
if (info.fd == -1) {
|
||||||
char msg[] = "File descriptor is missing from ssl info (id: %ld)";
|
log_error(ctx, LOG_ERROR_MISSING_FILE_DESCRIPTOR, id, 0l, 0l);
|
||||||
bpf_trace_printk(msg, sizeof(msg), id);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +229,7 @@ static __always_inline void ssl_uretprobe(struct pt_regs *ctx, struct bpf_map_de
|
|||||||
|
|
||||||
SEC("uprobe/ssl_write")
|
SEC("uprobe/ssl_write")
|
||||||
void BPF_KPROBE(ssl_write, void* ssl, void* buffer, int num) {
|
void BPF_KPROBE(ssl_write, void* ssl, void* buffer, int num) {
|
||||||
ssl_uprobe(ssl, buffer, num, &ssl_write_context, 0);
|
ssl_uprobe(ctx, ssl, buffer, num, &ssl_write_context, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
SEC("uretprobe/ssl_write")
|
SEC("uretprobe/ssl_write")
|
||||||
@@ -246,7 +239,7 @@ void BPF_KPROBE(ssl_ret_write) {
|
|||||||
|
|
||||||
SEC("uprobe/ssl_read")
|
SEC("uprobe/ssl_read")
|
||||||
void BPF_KPROBE(ssl_read, void* ssl, void* buffer, int num) {
|
void BPF_KPROBE(ssl_read, void* ssl, void* buffer, int num) {
|
||||||
ssl_uprobe(ssl, buffer, num, &ssl_read_context, 0);
|
ssl_uprobe(ctx, ssl, buffer, num, &ssl_read_context, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
SEC("uretprobe/ssl_read")
|
SEC("uretprobe/ssl_read")
|
||||||
@@ -256,7 +249,7 @@ void BPF_KPROBE(ssl_ret_read) {
|
|||||||
|
|
||||||
SEC("uprobe/ssl_write_ex")
|
SEC("uprobe/ssl_write_ex")
|
||||||
void BPF_KPROBE(ssl_write_ex, void* ssl, void* buffer, size_t num, size_t *written) {
|
void BPF_KPROBE(ssl_write_ex, void* ssl, void* buffer, size_t num, size_t *written) {
|
||||||
ssl_uprobe(ssl, buffer, num, &ssl_write_context, written);
|
ssl_uprobe(ctx, ssl, buffer, num, &ssl_write_context, written);
|
||||||
}
|
}
|
||||||
|
|
||||||
SEC("uretprobe/ssl_write_ex")
|
SEC("uretprobe/ssl_write_ex")
|
||||||
@@ -266,7 +259,7 @@ void BPF_KPROBE(ssl_ret_write_ex) {
|
|||||||
|
|
||||||
SEC("uprobe/ssl_read_ex")
|
SEC("uprobe/ssl_read_ex")
|
||||||
void BPF_KPROBE(ssl_read_ex, void* ssl, void* buffer, size_t num, size_t *readbytes) {
|
void BPF_KPROBE(ssl_read_ex, void* ssl, void* buffer, size_t num, size_t *readbytes) {
|
||||||
ssl_uprobe(ssl, buffer, num, &ssl_read_context, readbytes);
|
ssl_uprobe(ctx, ssl, buffer, num, &ssl_read_context, readbytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
SEC("uretprobe/ssl_read_ex")
|
SEC("uretprobe/ssl_read_ex")
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ Copyright (C) UP9 Inc.
|
|||||||
#include "include/headers.h"
|
#include "include/headers.h"
|
||||||
#include "include/util.h"
|
#include "include/util.h"
|
||||||
#include "include/maps.h"
|
#include "include/maps.h"
|
||||||
|
#include "include/log.h"
|
||||||
|
#include "include/logger_messages.h"
|
||||||
#include "include/pids.h"
|
#include "include/pids.h"
|
||||||
|
|
||||||
// To avoid multiple .o files
|
// To avoid multiple .o files
|
||||||
|
|||||||
116
tap/tlstapper/bpf_logger.go
Normal file
116
tap/tlstapper/bpf_logger.go
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
package tlstapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cilium/ebpf/perf"
|
||||||
|
"github.com/go-errors/errors"
|
||||||
|
"github.com/up9inc/mizu/shared/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
const logPrefix = "[bpf] "
|
||||||
|
|
||||||
|
// The same consts defined in log.h
|
||||||
|
//
|
||||||
|
const logLevelError = 0
|
||||||
|
const logLevelInfo = 1
|
||||||
|
const logLevelDebug = 2
|
||||||
|
|
||||||
|
type logMessage struct {
|
||||||
|
Level uint32
|
||||||
|
MessageCode uint32
|
||||||
|
Arg1 uint64
|
||||||
|
Arg2 uint64
|
||||||
|
Arg3 uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type bpfLogger struct {
|
||||||
|
logReader *perf.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBpfLogger() *bpfLogger {
|
||||||
|
return &bpfLogger{
|
||||||
|
logReader: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *bpfLogger) init(bpfObjects *tlsTapperObjects, bufferSize int) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
p.logReader, err = perf.NewReader(bpfObjects.LogBuffer, bufferSize)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *bpfLogger) close() error {
|
||||||
|
return p.logReader.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *bpfLogger) poll() {
|
||||||
|
logger.Log.Infof("Start polling for bpf logs")
|
||||||
|
|
||||||
|
for {
|
||||||
|
record, err := p.logReader.Read()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, perf.ErrClosed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
LogError(errors.Errorf("Error reading from bpf logger perf buffer, aboring logger! %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if record.LostSamples != 0 {
|
||||||
|
logger.Log.Infof("Log buffer is full, dropped %d logs", record.LostSamples)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := bytes.NewReader(record.RawSample)
|
||||||
|
|
||||||
|
var log logMessage
|
||||||
|
|
||||||
|
if err := binary.Read(buffer, binary.LittleEndian, &log); err != nil {
|
||||||
|
LogError(errors.Errorf("Error parsing log %v", err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
p.log(&log)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *bpfLogger) log(log *logMessage) {
|
||||||
|
if int(log.MessageCode) >= len(bpfLogMessages) {
|
||||||
|
logger.Log.Errorf("Unknown message code from bpf logger %d", log.MessageCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
format := bpfLogMessages[log.MessageCode]
|
||||||
|
tokensCount := strings.Count(format, "%")
|
||||||
|
|
||||||
|
if tokensCount == 0 {
|
||||||
|
p.logLevel(log.Level, format)
|
||||||
|
} else if tokensCount == 1 {
|
||||||
|
p.logLevel(log.Level, format, log.Arg1)
|
||||||
|
} else if tokensCount == 2 {
|
||||||
|
p.logLevel(log.Level, format, log.Arg1, log.Arg2)
|
||||||
|
} else if tokensCount == 3 {
|
||||||
|
p.logLevel(log.Level, format, log.Arg1, log.Arg2, log.Arg3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *bpfLogger) logLevel(level uint32, format string, args ...interface{}) {
|
||||||
|
if level == logLevelError {
|
||||||
|
logger.Log.Errorf(logPrefix+format, args...)
|
||||||
|
} else if level == logLevelInfo {
|
||||||
|
logger.Log.Infof(logPrefix+format, args...)
|
||||||
|
} else if level == logLevelDebug {
|
||||||
|
logger.Log.Debugf(logPrefix+format, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
25
tap/tlstapper/bpf_logger_messages.go
Normal file
25
tap/tlstapper/bpf_logger_messages.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package tlstapper
|
||||||
|
|
||||||
|
// Must be synced with logger_messages.h
|
||||||
|
//
|
||||||
|
var bpfLogMessages = []string {
|
||||||
|
/*0000*/ "[%d] Unable to read bytes count from _ex methods [err: %d]",
|
||||||
|
/*0001*/ "[%d] Unable to read ipv4 address [err: %d]",
|
||||||
|
/*0002*/ "[%d] Unable to read ssl buffer [err: %d]",
|
||||||
|
/*0003*/ "[%d] Buffer is too big [size: %d]",
|
||||||
|
/*0004*/ "[%d] Unable to allocate chunk in bpf heap",
|
||||||
|
/*0005*/ "[%d] Unable to read ssl context [err: %d] [origin: %d]",
|
||||||
|
/*0006*/ "[%d] Unable to put ssl context [err: %d]",
|
||||||
|
/*0007*/ "[%d] Unable to get ssl context",
|
||||||
|
/*0008*/ "[%d] File descriptor is missing for tls chunk",
|
||||||
|
/*0009*/ "[%d] Unable to put file descriptor [err: %d] [origin: %d]",
|
||||||
|
/*0010*/ "[%d] Unable to put accept info [err: %d]",
|
||||||
|
/*0011*/ "[%d] Unable to get accept info",
|
||||||
|
/*0012*/ "[%d] Unable to read accept info [err: %d]",
|
||||||
|
/*0013*/ "[%d] Unable to put file descriptor to address mapping [err: %d] [origin: %d]",
|
||||||
|
/*0014*/ "[%d] Unable to put connect info [err: %d]",
|
||||||
|
/*0015*/ "[%d] Unable to get connect info",
|
||||||
|
/*0016*/ "[%d] Unable to read connect info [err: %d]",
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const GLOABL_TAP_PID = 0
|
||||||
|
|
||||||
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go tlsTapper bpf/tls_tapper.c -- -O2 -g -D__TARGET_ARCH_x86
|
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go tlsTapper bpf/tls_tapper.c -- -O2 -g -D__TARGET_ARCH_x86
|
||||||
|
|
||||||
type TlsTapper struct {
|
type TlsTapper struct {
|
||||||
@@ -15,11 +17,12 @@ type TlsTapper struct {
|
|||||||
syscallHooks syscallHooks
|
syscallHooks syscallHooks
|
||||||
sslHooksStructs []sslHooks
|
sslHooksStructs []sslHooks
|
||||||
poller *tlsPoller
|
poller *tlsPoller
|
||||||
|
bpfLogger *bpfLogger
|
||||||
registeredPids sync.Map
|
registeredPids sync.Map
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TlsTapper) Init(bufferSize int, procfs string, extension *api.Extension) error {
|
func (t *TlsTapper) Init(chunksBufferSize int, logBufferSize int, procfs string, extension *api.Extension) error {
|
||||||
logger.Log.Infof("Initializing tls tapper (bufferSize: %v)", bufferSize)
|
logger.Log.Infof("Initializing tls tapper (chunksSize: %d) (logSize: %d)", chunksBufferSize, logBufferSize)
|
||||||
|
|
||||||
if err := setupRLimit(); err != nil {
|
if err := setupRLimit(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -37,16 +40,25 @@ func (t *TlsTapper) Init(bufferSize int, procfs string, extension *api.Extension
|
|||||||
|
|
||||||
t.sslHooksStructs = make([]sslHooks, 0)
|
t.sslHooksStructs = make([]sslHooks, 0)
|
||||||
|
|
||||||
|
t.bpfLogger = newBpfLogger()
|
||||||
|
if err := t.bpfLogger.init(&t.bpfObjects, logBufferSize); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
t.poller = newTlsPoller(t, extension, procfs)
|
t.poller = newTlsPoller(t, extension, procfs)
|
||||||
return t.poller.init(&t.bpfObjects, bufferSize)
|
return t.poller.init(&t.bpfObjects, chunksBufferSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TlsTapper) Poll(emitter api.Emitter, options *api.TrafficFilteringOptions) {
|
func (t *TlsTapper) Poll(emitter api.Emitter, options *api.TrafficFilteringOptions) {
|
||||||
t.poller.poll(emitter, options)
|
t.poller.poll(emitter, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *TlsTapper) PollForLogging() {
|
||||||
|
t.bpfLogger.poll()
|
||||||
|
}
|
||||||
|
|
||||||
func (t *TlsTapper) GlobalTap(sslLibrary string) error {
|
func (t *TlsTapper) GlobalTap(sslLibrary string) error {
|
||||||
return t.tapPid(0, sslLibrary)
|
return t.tapPid(GLOABL_TAP_PID, sslLibrary)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TlsTapper) AddPid(procfs string, pid uint32) error {
|
func (t *TlsTapper) AddPid(procfs string, pid uint32) error {
|
||||||
@@ -74,7 +86,12 @@ func (t *TlsTapper) RemovePid(pid uint32) error {
|
|||||||
|
|
||||||
func (t *TlsTapper) ClearPids() {
|
func (t *TlsTapper) ClearPids() {
|
||||||
t.registeredPids.Range(func(key, v interface{}) bool {
|
t.registeredPids.Range(func(key, v interface{}) bool {
|
||||||
if err := t.RemovePid(key.(uint32)); err != nil {
|
pid := key.(uint32)
|
||||||
|
if pid == GLOABL_TAP_PID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := t.RemovePid(pid); err != nil {
|
||||||
LogError(err)
|
LogError(err)
|
||||||
}
|
}
|
||||||
t.registeredPids.Delete(key)
|
t.registeredPids.Delete(key)
|
||||||
@@ -95,6 +112,10 @@ func (t *TlsTapper) Close() []error {
|
|||||||
errors = append(errors, sslHooks.close()...)
|
errors = append(errors, sslHooks.close()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := t.bpfLogger.close(); err != nil {
|
||||||
|
errors = append(errors, err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := t.poller.close(); err != nil {
|
if err := t.poller.close(); err != nil {
|
||||||
errors = append(errors, err)
|
errors = append(errors, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ type tlsTapperMapSpecs struct {
|
|||||||
ConnectSyscallInfo *ebpf.MapSpec `ebpf:"connect_syscall_info"`
|
ConnectSyscallInfo *ebpf.MapSpec `ebpf:"connect_syscall_info"`
|
||||||
FileDescriptorToIpv4 *ebpf.MapSpec `ebpf:"file_descriptor_to_ipv4"`
|
FileDescriptorToIpv4 *ebpf.MapSpec `ebpf:"file_descriptor_to_ipv4"`
|
||||||
Heap *ebpf.MapSpec `ebpf:"heap"`
|
Heap *ebpf.MapSpec `ebpf:"heap"`
|
||||||
|
LogBuffer *ebpf.MapSpec `ebpf:"log_buffer"`
|
||||||
PidsMap *ebpf.MapSpec `ebpf:"pids_map"`
|
PidsMap *ebpf.MapSpec `ebpf:"pids_map"`
|
||||||
SslReadContext *ebpf.MapSpec `ebpf:"ssl_read_context"`
|
SslReadContext *ebpf.MapSpec `ebpf:"ssl_read_context"`
|
||||||
SslWriteContext *ebpf.MapSpec `ebpf:"ssl_write_context"`
|
SslWriteContext *ebpf.MapSpec `ebpf:"ssl_write_context"`
|
||||||
@@ -107,6 +108,7 @@ type tlsTapperMaps struct {
|
|||||||
ConnectSyscallInfo *ebpf.Map `ebpf:"connect_syscall_info"`
|
ConnectSyscallInfo *ebpf.Map `ebpf:"connect_syscall_info"`
|
||||||
FileDescriptorToIpv4 *ebpf.Map `ebpf:"file_descriptor_to_ipv4"`
|
FileDescriptorToIpv4 *ebpf.Map `ebpf:"file_descriptor_to_ipv4"`
|
||||||
Heap *ebpf.Map `ebpf:"heap"`
|
Heap *ebpf.Map `ebpf:"heap"`
|
||||||
|
LogBuffer *ebpf.Map `ebpf:"log_buffer"`
|
||||||
PidsMap *ebpf.Map `ebpf:"pids_map"`
|
PidsMap *ebpf.Map `ebpf:"pids_map"`
|
||||||
SslReadContext *ebpf.Map `ebpf:"ssl_read_context"`
|
SslReadContext *ebpf.Map `ebpf:"ssl_read_context"`
|
||||||
SslWriteContext *ebpf.Map `ebpf:"ssl_write_context"`
|
SslWriteContext *ebpf.Map `ebpf:"ssl_write_context"`
|
||||||
@@ -119,6 +121,7 @@ func (m *tlsTapperMaps) Close() error {
|
|||||||
m.ConnectSyscallInfo,
|
m.ConnectSyscallInfo,
|
||||||
m.FileDescriptorToIpv4,
|
m.FileDescriptorToIpv4,
|
||||||
m.Heap,
|
m.Heap,
|
||||||
|
m.LogBuffer,
|
||||||
m.PidsMap,
|
m.PidsMap,
|
||||||
m.SslReadContext,
|
m.SslReadContext,
|
||||||
m.SslWriteContext,
|
m.SslWriteContext,
|
||||||
|
|||||||
Binary file not shown.
@@ -78,6 +78,7 @@ type tlsTapperMapSpecs struct {
|
|||||||
ConnectSyscallInfo *ebpf.MapSpec `ebpf:"connect_syscall_info"`
|
ConnectSyscallInfo *ebpf.MapSpec `ebpf:"connect_syscall_info"`
|
||||||
FileDescriptorToIpv4 *ebpf.MapSpec `ebpf:"file_descriptor_to_ipv4"`
|
FileDescriptorToIpv4 *ebpf.MapSpec `ebpf:"file_descriptor_to_ipv4"`
|
||||||
Heap *ebpf.MapSpec `ebpf:"heap"`
|
Heap *ebpf.MapSpec `ebpf:"heap"`
|
||||||
|
LogBuffer *ebpf.MapSpec `ebpf:"log_buffer"`
|
||||||
PidsMap *ebpf.MapSpec `ebpf:"pids_map"`
|
PidsMap *ebpf.MapSpec `ebpf:"pids_map"`
|
||||||
SslReadContext *ebpf.MapSpec `ebpf:"ssl_read_context"`
|
SslReadContext *ebpf.MapSpec `ebpf:"ssl_read_context"`
|
||||||
SslWriteContext *ebpf.MapSpec `ebpf:"ssl_write_context"`
|
SslWriteContext *ebpf.MapSpec `ebpf:"ssl_write_context"`
|
||||||
@@ -107,6 +108,7 @@ type tlsTapperMaps struct {
|
|||||||
ConnectSyscallInfo *ebpf.Map `ebpf:"connect_syscall_info"`
|
ConnectSyscallInfo *ebpf.Map `ebpf:"connect_syscall_info"`
|
||||||
FileDescriptorToIpv4 *ebpf.Map `ebpf:"file_descriptor_to_ipv4"`
|
FileDescriptorToIpv4 *ebpf.Map `ebpf:"file_descriptor_to_ipv4"`
|
||||||
Heap *ebpf.Map `ebpf:"heap"`
|
Heap *ebpf.Map `ebpf:"heap"`
|
||||||
|
LogBuffer *ebpf.Map `ebpf:"log_buffer"`
|
||||||
PidsMap *ebpf.Map `ebpf:"pids_map"`
|
PidsMap *ebpf.Map `ebpf:"pids_map"`
|
||||||
SslReadContext *ebpf.Map `ebpf:"ssl_read_context"`
|
SslReadContext *ebpf.Map `ebpf:"ssl_read_context"`
|
||||||
SslWriteContext *ebpf.Map `ebpf:"ssl_write_context"`
|
SslWriteContext *ebpf.Map `ebpf:"ssl_write_context"`
|
||||||
@@ -119,6 +121,7 @@ func (m *tlsTapperMaps) Close() error {
|
|||||||
m.ConnectSyscallInfo,
|
m.ConnectSyscallInfo,
|
||||||
m.FileDescriptorToIpv4,
|
m.FileDescriptorToIpv4,
|
||||||
m.Heap,
|
m.Heap,
|
||||||
|
m.LogBuffer,
|
||||||
m.PidsMap,
|
m.PidsMap,
|
||||||
m.SslReadContext,
|
m.SslReadContext,
|
||||||
m.SslWriteContext,
|
m.SslWriteContext,
|
||||||
|
|||||||
Binary file not shown.
4
ui-common/package-lock.json
generated
4
ui-common/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@up9/mizu-common",
|
"name": "@up9/mizu-common",
|
||||||
"version": "1.0.135",
|
"version": "0.0.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@up9/mizu-common",
|
"name": "@up9/mizu-common",
|
||||||
"version": "1.0.135",
|
"version": "0.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@craco/craco": "^6.4.3",
|
"@craco/craco": "^6.4.3",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@up9/mizu-common",
|
"name": "@up9/mizu-common",
|
||||||
"version": "1.0.137",
|
"version": "0.0.0",
|
||||||
"description": "Made with create-react-library",
|
"description": "Made with create-react-library",
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
"@material-ui/icons": "^4.11.2",
|
"@material-ui/icons": "^4.11.2",
|
||||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||||
"node-sass": "^6.0.0",
|
"node-sass": "^6.0.0",
|
||||||
"react":"^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"recoil": "^0.5.2",
|
"recoil": "^0.5.2",
|
||||||
"react-copy-to-clipboard": "^5.0.3",
|
"react-copy-to-clipboard": "^5.0.3",
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
"rollup-plugin-postcss": "^4.0.2",
|
"rollup-plugin-postcss": "^4.0.2",
|
||||||
"rollup-plugin-sass": "^1.2.10",
|
"rollup-plugin-sass": "^1.2.10",
|
||||||
"rollup-plugin-scss": "^3.0.0",
|
"rollup-plugin-scss": "^3.0.0",
|
||||||
"react":"^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"typescript": "^4.2.4"
|
"typescript": "^4.2.4"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Box, Fade, FormControl, MenuItem, Modal, Backdrop, ListSubheader } from "@material-ui/core";
|
import { Box, Fade, FormControl, MenuItem, Modal, Backdrop } from "@material-ui/core";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { RedocStandalone } from "redoc";
|
import { RedocStandalone } from "redoc";
|
||||||
import closeIcon from "assets/closeIcon.svg";
|
import closeIcon from "assets/closeIcon.svg";
|
||||||
@@ -7,7 +7,8 @@ import style from './OasModal.module.sass';
|
|||||||
import openApiLogo from 'assets/openApiLogo.png'
|
import openApiLogo from 'assets/openApiLogo.png'
|
||||||
import { redocThemeOptions } from "./redocThemeOptions";
|
import { redocThemeOptions } from "./redocThemeOptions";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { UI } from "../..";
|
import { Select } from "../UI/Select";
|
||||||
|
|
||||||
|
|
||||||
const modalStyle = {
|
const modalStyle = {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@@ -23,68 +24,44 @@ const modalStyle = {
|
|||||||
color: '#000',
|
color: '#000',
|
||||||
};
|
};
|
||||||
|
|
||||||
const ipAddressWithPortRegex = new RegExp('([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}):([0-9]{1,5})');
|
|
||||||
|
|
||||||
const OasModal = ({ openModal, handleCloseModal, getOasServices, getOasByService }) => {
|
const OasModal = ({ openModal, handleCloseModal, getOasServices, getOasByService }) => {
|
||||||
const [oasServices, setOasServices] = useState([] as string[])
|
const [oasServices, setOasServices] = useState([] as string[])
|
||||||
const [selectedServiceName, setSelectedServiceName] = useState("");
|
const [selectedServiceName, setSelectedServiceName] = useState("");
|
||||||
const [selectedServiceSpec, setSelectedServiceSpec] = useState(null);
|
const [selectedServiceSpec, setSelectedServiceSpec] = useState(null);
|
||||||
const [resolvedServices, setResolvedServices] = useState([]);
|
|
||||||
const [unResolvedServices, setUnResolvedServices] = useState([]);
|
|
||||||
|
|
||||||
const onSelectedOASService = useCallback(async (selectedService) => {
|
const onSelectedOASService = async (selectedService) => {
|
||||||
if (!!selectedService) {
|
if (oasServices.length === 0) {
|
||||||
setSelectedServiceName(selectedService);
|
setSelectedServiceSpec(null);
|
||||||
if (oasServices.length === 0) {
|
setSelectedServiceName("");
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
else {
|
||||||
const data = await getOasByService(selectedService);
|
setSelectedServiceName(selectedService ? selectedService : oasServices[0]);
|
||||||
setSelectedServiceSpec(data);
|
}
|
||||||
|
try {
|
||||||
|
const data = await getOasByService(selectedService ? selectedService : oasServices[0]);
|
||||||
|
setSelectedServiceSpec(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error("Error occurred while fetching service OAS spec");
|
toast.error("Error occurred while fetching service OAS spec");
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}, [oasServices.length])
|
|
||||||
|
|
||||||
const resolvedArrayBuilder = useCallback(async (services) => {
|
|
||||||
const resServices = [];
|
|
||||||
const unResServices = [];
|
|
||||||
services.forEach(s => {
|
|
||||||
if (ipAddressWithPortRegex.test(s)) {
|
|
||||||
unResServices.push(s);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
resServices.push(s);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
resServices.sort();
|
|
||||||
unResServices.sort();
|
|
||||||
if (resServices.length > 0) {
|
|
||||||
onSelectedOASService(resServices[0]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
onSelectedOASService(unResServices[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
setResolvedServices(resServices);
|
|
||||||
setUnResolvedServices(unResServices);
|
|
||||||
}, [onSelectedOASService])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const services = await getOasServices();
|
const services = await getOasServices();
|
||||||
resolvedArrayBuilder(services);
|
|
||||||
setOasServices(services);
|
setOasServices(services);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [openModal, resolvedArrayBuilder]);
|
}, [openModal]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onSelectedOASService(null);
|
||||||
|
},[oasServices])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -102,34 +79,27 @@ const OasModal = ({ openModal, handleCloseModal, getOasServices, getOasByService
|
|||||||
<Box sx={modalStyle}>
|
<Box sx={modalStyle}>
|
||||||
<div className={style.boxContainer}>
|
<div className={style.boxContainer}>
|
||||||
<div className={style.selectHeader}>
|
<div className={style.selectHeader}>
|
||||||
<div><img src={openApiLogo} alt="openApi" className={style.openApilogo} /></div>
|
<div><img src={openApiLogo} alt="openAPI" className={style.openApilogo} /></div>
|
||||||
<div className={style.title}>OpenApi </div>
|
<div className={style.title}>OpenAPI</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ cursor: "pointer" }}>
|
<div style={{ cursor: "pointer" }}>
|
||||||
<img src={closeIcon} alt="close" onClick={handleCloseModal} />
|
<img src={closeIcon} alt="close" onClick={handleCloseModal} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.selectContainer} >
|
<div className={style.selectContainer} >
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<UI.Select
|
<Select
|
||||||
labelId="service-select-label"
|
labelId="service-select-label"
|
||||||
id="service-select"
|
id="service-select"
|
||||||
value={selectedServiceName}
|
value={selectedServiceName}
|
||||||
onChangeCb={onSelectedOASService}
|
onChangeCb={onSelectedOASService}
|
||||||
>
|
>
|
||||||
<ListSubheader disableSticky={true}>Resolved</ListSubheader>
|
{oasServices.map((service) => (
|
||||||
{resolvedServices.map((service) => (
|
|
||||||
<MenuItem key={service} value={service}>
|
<MenuItem key={service} value={service}>
|
||||||
{service}
|
{service}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
<ListSubheader disableSticky={true}>UnResolved</ListSubheader>
|
</Select>
|
||||||
{unResolvedServices.map((service) => (
|
|
||||||
<MenuItem key={service} value={service}>
|
|
||||||
{service}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</UI.Select>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.borderLine}></div>
|
<div className={style.borderLine}></div>
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import React, {useEffect, useState} from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import EntryViewer from "./EntryDetailed/EntryViewer";
|
import EntryViewer from "./EntryDetailed/EntryViewer";
|
||||||
import {EntryItem} from "./EntryListItem/EntryListItem";
|
import { EntryItem } from "./EntryListItem/EntryListItem";
|
||||||
import {makeStyles} from "@material-ui/core";
|
import { makeStyles } from "@material-ui/core";
|
||||||
import Protocol from "../UI/Protocol"
|
import Protocol from "../UI/Protocol"
|
||||||
import Queryable from "../UI/Queryable";
|
import Queryable from "../UI/Queryable";
|
||||||
import {toast} from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import {RecoilState, useRecoilState, useRecoilValue} from "recoil";
|
import { RecoilState, useRecoilState, useRecoilValue } from "recoil";
|
||||||
import focusedEntryIdAtom from "../../recoil/focusedEntryId";
|
import focusedEntryIdAtom from "../../recoil/focusedEntryId";
|
||||||
import trafficViewerApi from "../../recoil/TrafficViewerApi";
|
import trafficViewerApi from "../../recoil/TrafficViewerApi";
|
||||||
import TrafficViewerApi from "./TrafficViewerApi";
|
import TrafficViewerApi from "./TrafficViewerApi";
|
||||||
import TrafficViewerApiAtom from "../../recoil/TrafficViewerApi/atom";
|
import TrafficViewerApiAtom from "../../recoil/TrafficViewerApi/atom";
|
||||||
import queryAtom from "../../recoil/query/atom";
|
import queryAtom from "../../recoil/query/atom";
|
||||||
import useWindowDimensions, { useRequestTextByWidth } from "../../hooks/WindowDimensionsHook";
|
import useWindowDimensions, { useRequestTextByWidth } from "../../hooks/WindowDimensionsHook";
|
||||||
|
import { TOAST_CONTAINER_ID } from "../../configs/Consts";
|
||||||
|
|
||||||
const useStyles = makeStyles(() => ({
|
const useStyles = makeStyles(() => ({
|
||||||
entryTitle: {
|
entryTitle: {
|
||||||
@@ -37,24 +38,24 @@ const useStyles = makeStyles(() => ({
|
|||||||
|
|
||||||
export const formatSize = (n: number) => n > 1000 ? `${Math.round(n / 1000)}KB` : `${n} B`;
|
export const formatSize = (n: number) => n > 1000 ? `${Math.round(n / 1000)}KB` : `${n} B`;
|
||||||
const minSizeDisplayRequestSize = 880;
|
const minSizeDisplayRequestSize = 880;
|
||||||
const EntryTitle: React.FC<any> = ({protocol, data, elapsedTime}) => {
|
const EntryTitle: React.FC<any> = ({ protocol, data, elapsedTime }) => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const request = data.request;
|
const request = data.request;
|
||||||
const response = data.response;
|
const response = data.response;
|
||||||
|
|
||||||
const { width } = useWindowDimensions();
|
const { width } = useWindowDimensions();
|
||||||
const {requestText, responseText, elapsedTimeText} = useRequestTextByWidth(width)
|
const { requestText, responseText, elapsedTimeText } = useRequestTextByWidth(width)
|
||||||
|
|
||||||
return <div className={classes.entryTitle}>
|
return <div className={classes.entryTitle}>
|
||||||
<Protocol protocol={protocol} horizontal={true}/>
|
<Protocol protocol={protocol} horizontal={true} />
|
||||||
{(width > minSizeDisplayRequestSize) && <div style={{right: "30px", position: "absolute", display: "flex"}}>
|
{(width > minSizeDisplayRequestSize) && <div style={{ right: "30px", position: "absolute", display: "flex" }}>
|
||||||
{request && <Queryable
|
{request && <Queryable
|
||||||
query={`requestSize == ${data.requestSize}`}
|
query={`requestSize == ${data.requestSize}`}
|
||||||
style={{margin: "0 18px"}}
|
style={{ margin: "0 18px" }}
|
||||||
displayIconOnMouseOver={true}
|
displayIconOnMouseOver={true}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{opacity: 0.5}}
|
style={{ opacity: 0.5 }}
|
||||||
id="entryDetailedTitleRequestSize"
|
id="entryDetailedTitleRequestSize"
|
||||||
>
|
>
|
||||||
{`${requestText}${formatSize(data.requestSize)}`}
|
{`${requestText}${formatSize(data.requestSize)}`}
|
||||||
@@ -62,11 +63,11 @@ const EntryTitle: React.FC<any> = ({protocol, data, elapsedTime}) => {
|
|||||||
</Queryable>}
|
</Queryable>}
|
||||||
{response && <Queryable
|
{response && <Queryable
|
||||||
query={`responseSize == ${data.responseSize}`}
|
query={`responseSize == ${data.responseSize}`}
|
||||||
style={{margin: "0 18px"}}
|
style={{ margin: "0 18px" }}
|
||||||
displayIconOnMouseOver={true}
|
displayIconOnMouseOver={true}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{opacity: 0.5}}
|
style={{ opacity: 0.5 }}
|
||||||
id="entryDetailedTitleResponseSize"
|
id="entryDetailedTitleResponseSize"
|
||||||
>
|
>
|
||||||
{`${responseText}${formatSize(data.responseSize)}`}
|
{`${responseText}${formatSize(data.responseSize)}`}
|
||||||
@@ -74,11 +75,11 @@ const EntryTitle: React.FC<any> = ({protocol, data, elapsedTime}) => {
|
|||||||
</Queryable>}
|
</Queryable>}
|
||||||
{response && <Queryable
|
{response && <Queryable
|
||||||
query={`elapsedTime >= ${elapsedTime}`}
|
query={`elapsedTime >= ${elapsedTime}`}
|
||||||
style={{margin: "0 0 0 18px"}}
|
style={{ margin: "0 0 0 18px" }}
|
||||||
displayIconOnMouseOver={true}
|
displayIconOnMouseOver={true}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{opacity: 0.5}}
|
style={{ opacity: 0.5 }}
|
||||||
id="entryDetailedTitleElapsedTime"
|
id="entryDetailedTitleElapsedTime"
|
||||||
>
|
>
|
||||||
{`${elapsedTimeText}${Math.round(elapsedTime)}ms`}
|
{`${elapsedTimeText}${Math.round(elapsedTime)}ms`}
|
||||||
@@ -88,7 +89,7 @@ const EntryTitle: React.FC<any> = ({protocol, data, elapsedTime}) => {
|
|||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const EntrySummary: React.FC<any> = ({entry}) => {
|
const EntrySummary: React.FC<any> = ({ entry }) => {
|
||||||
return <EntryItem
|
return <EntryItem
|
||||||
key={`entry-${entry.id}`}
|
key={`entry-${entry.id}`}
|
||||||
entry={entry}
|
entry={entry}
|
||||||
@@ -117,14 +118,10 @@ export const EntryDetailed = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.response?.data?.type) {
|
if (error.response?.data?.type) {
|
||||||
toast[error.response.data.type](`Entry[${focusedEntryId}]: ${error.response.data.msg}`, {
|
toast[error.response.data.type](`Entry[${focusedEntryId}]: ${error.response.data.msg}`, {
|
||||||
position: "bottom-right",
|
|
||||||
theme: "colored",
|
theme: "colored",
|
||||||
autoClose: error.response.data.autoClose,
|
autoClose: error.response.data.autoClose,
|
||||||
hideProgressBar: false,
|
|
||||||
closeOnClick: true,
|
|
||||||
pauseOnHover: true,
|
|
||||||
draggable: true,
|
|
||||||
progress: undefined,
|
progress: undefined,
|
||||||
|
containerId: TOAST_CONTAINER_ID
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -139,7 +136,7 @@ export const EntryDetailed = () => {
|
|||||||
data={entryData.data}
|
data={entryData.data}
|
||||||
elapsedTime={entryData.data.elapsedTime}
|
elapsedTime={entryData.data.elapsedTime}
|
||||||
/>}
|
/>}
|
||||||
{entryData && <EntrySummary entry={entryData.base}/>}
|
{entryData && <EntrySummary entry={entryData.base} />}
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{entryData && <EntryViewer
|
{entryData && <EntryViewer
|
||||||
representation={entryData.representation}
|
representation={entryData.representation}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import SwapHorizIcon from '@material-ui/icons/SwapHoriz';
|
|||||||
import styles from './EntryListItem.module.sass';
|
import styles from './EntryListItem.module.sass';
|
||||||
import StatusCode, {getClassification, StatusCodeClassification} from "../../UI/StatusCode";
|
import StatusCode, {getClassification, StatusCodeClassification} from "../../UI/StatusCode";
|
||||||
import Protocol, {ProtocolInterface} from "../../UI/Protocol"
|
import Protocol, {ProtocolInterface} from "../../UI/Protocol"
|
||||||
import eBPFLogo from '../../assets/ebpf.png';
|
import eBPFLogo from 'assets/lock.svg';
|
||||||
import {Summary} from "../../UI/Summary";
|
import {Summary} from "../../UI/Summary";
|
||||||
import Queryable from "../../UI/Queryable";
|
import Queryable from "../../UI/Queryable";
|
||||||
import ingoingIconSuccess from "assets/ingoing-traffic-success.svg"
|
import ingoingIconSuccess from "assets/ingoing-traffic-success.svg"
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="12" height="14" viewBox="0 0 12 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.33333 6.36364H8.66667V6.36011H10.6667V6.36364H11C11.2778 6.36364 11.5139 6.45644 11.7083 6.64205C11.9028 6.82765 12 7.05303 12 7.31818V13.0455C12 13.3106 11.9028 13.536 11.7083 13.7216C11.5139 13.9072 11.2778 14 11 14H1C0.722222 14 0.486111 13.9072 0.291666 13.7216C0.0972223 13.536 0 13.3106 0 13.0455V7.31818C0 7.05303 0.0972223 6.82765 0.291666 6.64205C0.486111 6.45644 0.722222 6.36364 1 6.36364H1.33333V4.45455C1.33333 3.23485 1.79167 2.1875 2.70833 1.3125C3.625 0.4375 4.72222 0 6 0C7.27778 0 8.375 0.4375 9.29167 1.3125C9.92325 1.91538 10.3373 2.60007 10.5337 3.36658L8.59659 3.85085C8.48731 3.40176 8.25026 3.00309 7.88542 2.65483C7.36458 2.15767 6.73611 1.90909 6 1.90909C5.26389 1.90909 4.63542 2.15767 4.11458 2.65483C3.59375 3.15199 3.33333 3.75189 3.33333 4.45455V6.36364Z" fill="#BCCEFD"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 959 B |
@@ -8,8 +8,7 @@ import { EntryDetailed } from "./EntryDetailed";
|
|||||||
import playIcon from 'assets/run.svg';
|
import playIcon from 'assets/run.svg';
|
||||||
import pauseIcon from 'assets/pause.svg';
|
import pauseIcon from 'assets/pause.svg';
|
||||||
import variables from '../../variables.module.scss';
|
import variables from '../../variables.module.scss';
|
||||||
import { toast,ToastContainer } from 'react-toastify';
|
import { toast, ToastContainer } from 'react-toastify';
|
||||||
import 'react-toastify/dist/ReactToastify.css';
|
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { RecoilRoot, RecoilState, useRecoilState, useRecoilValue, useSetRecoilState } from "recoil";
|
import { RecoilRoot, RecoilState, useRecoilState, useRecoilValue, useSetRecoilState } from "recoil";
|
||||||
import entriesAtom from "../../recoil/entries";
|
import entriesAtom from "../../recoil/entries";
|
||||||
@@ -20,6 +19,7 @@ import trafficViewerApiAtom from "../../recoil/TrafficViewerApi"
|
|||||||
import TrafficViewerApi from "./TrafficViewerApi";
|
import TrafficViewerApi from "./TrafficViewerApi";
|
||||||
import { StatusBar } from "../UI/StatusBar";
|
import { StatusBar } from "../UI/StatusBar";
|
||||||
import tappingStatusAtom from "../../recoil/tappingStatus/atom";
|
import tappingStatusAtom from "../../recoil/tappingStatus/atom";
|
||||||
|
import { TOAST_CONTAINER_ID } from "../../configs/Consts";
|
||||||
|
|
||||||
const useLayoutStyles = makeStyles(() => ({
|
const useLayoutStyles = makeStyles(() => ({
|
||||||
details: {
|
details: {
|
||||||
@@ -47,14 +47,14 @@ interface TrafficViewerProps {
|
|||||||
trafficViewerApiProp: TrafficViewerApi,
|
trafficViewerApiProp: TrafficViewerApi,
|
||||||
actionButtons?: JSX.Element,
|
actionButtons?: JSX.Element,
|
||||||
isShowStatusBar?: boolean,
|
isShowStatusBar?: boolean,
|
||||||
webSocketUrl : string,
|
webSocketUrl: string,
|
||||||
isCloseWebSocket : boolean,
|
isCloseWebSocket: boolean,
|
||||||
isDemoBannerView : boolean
|
isDemoBannerView: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TrafficViewer : React.FC<TrafficViewerProps> = ({setAnalyzeStatus, trafficViewerApiProp,
|
export const TrafficViewer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus, trafficViewerApiProp,
|
||||||
actionButtons,isShowStatusBar,webSocketUrl,
|
actionButtons, isShowStatusBar, webSocketUrl,
|
||||||
isCloseWebSocket, isDemoBannerView}) => {
|
isCloseWebSocket, isDemoBannerView }) => {
|
||||||
|
|
||||||
const classes = useLayoutStyles();
|
const classes = useLayoutStyles();
|
||||||
|
|
||||||
@@ -106,12 +106,31 @@ export const TrafficViewer : React.FC<TrafficViewerProps> = ({setAnalyzeStatus,
|
|||||||
handleQueryChange(query);
|
handleQueryChange(query);
|
||||||
}, [query, handleQueryChange]);
|
}, [query, handleQueryChange]);
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(() => {
|
||||||
isCloseWebSocket && closeWebSocket()
|
isCloseWebSocket && closeWebSocket()
|
||||||
},[isCloseWebSocket])
|
}, [isCloseWebSocket])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reopenConnection()
|
||||||
|
}, [webSocketUrl])
|
||||||
|
|
||||||
const ws = useRef(null);
|
const ws = useRef(null);
|
||||||
|
|
||||||
|
const openEmptyWebSocket = () => {
|
||||||
|
if (query) {
|
||||||
|
openWebSocket(`(${query}) and leftOff(-1)`, true);
|
||||||
|
} else {
|
||||||
|
openWebSocket(`leftOff(-1)`, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeWebSocket = () => {
|
||||||
|
if(ws?.current?.readyState === WebSocket.OPEN) {
|
||||||
|
ws.current.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const listEntry = useRef(null);
|
const listEntry = useRef(null);
|
||||||
const openWebSocket = (query: string, resetEntries: boolean) => {
|
const openWebSocket = (query: string, resetEntries: boolean) => {
|
||||||
if (resetEntries) {
|
if (resetEntries) {
|
||||||
@@ -126,7 +145,7 @@ export const TrafficViewer : React.FC<TrafficViewerProps> = ({setAnalyzeStatus,
|
|||||||
sendQueryWhenWsOpen(query);
|
sendQueryWhenWsOpen(query);
|
||||||
|
|
||||||
ws.current.onclose = () => {
|
ws.current.onclose = () => {
|
||||||
if(window.location.pathname === "/")
|
if (window.location.pathname === "/")
|
||||||
setForceRender(forceRender + 1);
|
setForceRender(forceRender + 1);
|
||||||
}
|
}
|
||||||
ws.current.onerror = (event) => {
|
ws.current.onerror = (event) => {
|
||||||
@@ -140,25 +159,19 @@ export const TrafficViewer : React.FC<TrafficViewerProps> = ({setAnalyzeStatus,
|
|||||||
openWebSocket(`leftOff(${leftOffBottom})`, false);
|
openWebSocket(`leftOff(${leftOffBottom})`, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendQueryWhenWsOpen = (query) => {
|
const sendQueryWhenWsOpen = (query) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (ws?.current?.readyState === WebSocket.OPEN) {
|
if (ws?.current?.readyState === WebSocket.OPEN) {
|
||||||
ws.current.send(JSON.stringify({"query": query, "enableFullEntries": false}));
|
ws.current.send(JSON.stringify({ "query": query, "enableFullEntries": false }));
|
||||||
} else {
|
} else {
|
||||||
sendQueryWhenWsOpen(query);
|
sendQueryWhenWsOpen(query);
|
||||||
}
|
}
|
||||||
}, 500)
|
}, 500)
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeWebSocket = () => {
|
|
||||||
if(ws?.current?.readyState === WebSocket.OPEN) {
|
|
||||||
ws.current.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ws.current) {
|
if (ws.current) {
|
||||||
ws.current.onmessage = (e) => {
|
ws.current.onmessage = (e) => {
|
||||||
if (!e?.data) return;
|
if (!e?.data) return;
|
||||||
@@ -186,14 +199,11 @@ export const TrafficViewer : React.FC<TrafficViewerProps> = ({setAnalyzeStatus,
|
|||||||
break;
|
break;
|
||||||
case "toast":
|
case "toast":
|
||||||
toast[message.data.type](message.data.text, {
|
toast[message.data.type](message.data.text, {
|
||||||
position: "bottom-right",
|
|
||||||
theme: "colored",
|
theme: "colored",
|
||||||
autoClose: message.data.autoClose,
|
autoClose: message.data.autoClose,
|
||||||
hideProgressBar: false,
|
|
||||||
closeOnClick: true,
|
|
||||||
pauseOnHover: true,
|
pauseOnHover: true,
|
||||||
draggable: true,
|
|
||||||
progress: undefined,
|
progress: undefined,
|
||||||
|
containerId: TOAST_CONTAINER_ID
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "queryMetadata":
|
case "queryMetadata":
|
||||||
@@ -217,13 +227,12 @@ export const TrafficViewer : React.FC<TrafficViewerProps> = ({setAnalyzeStatus,
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTrafficViewerApiState({...trafficViewerApiProp, webSocket : {close : closeWebSocket}});
|
setTrafficViewerApiState({ ...trafficViewerApiProp, webSocket: { close: closeWebSocket } });
|
||||||
(async () => {
|
(async () => {
|
||||||
openWebSocket("leftOff(-1)", true);
|
|
||||||
try{
|
try{
|
||||||
const tapStatusResponse = await trafficViewerApiProp.tapStatus();
|
const tapStatusResponse = await trafficViewerApiProp.tapStatus();
|
||||||
setTappingStatus(tapStatusResponse);
|
setTappingStatus(tapStatusResponse);
|
||||||
if(setAnalyzeStatus) {
|
if (setAnalyzeStatus) {
|
||||||
const analyzeStatusResponse = await trafficViewerApiProp.analyzeStatus();
|
const analyzeStatusResponse = await trafficViewerApiProp.analyzeStatus();
|
||||||
setAnalyzeStatus(analyzeStatusResponse);
|
setAnalyzeStatus(analyzeStatusResponse);
|
||||||
}
|
}
|
||||||
@@ -235,19 +244,18 @@ export const TrafficViewer : React.FC<TrafficViewerProps> = ({setAnalyzeStatus,
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const toggleConnection = () => {
|
const toggleConnection = () => {
|
||||||
if(ws?.current?.readyState === WebSocket.OPEN) {
|
if(!closeWebSocket()) {
|
||||||
ws?.current?.close();
|
openEmptyWebSocket();
|
||||||
} else {
|
|
||||||
if (query) {
|
|
||||||
openWebSocket(`(${query}) and leftOff(-1)`, true);
|
|
||||||
} else {
|
|
||||||
openWebSocket(`leftOff(-1)`, true);
|
|
||||||
}
|
|
||||||
scrollableRef.current.jumpToBottom();
|
scrollableRef.current.jumpToBottom();
|
||||||
setIsSnappedToBottom(true);
|
setIsSnappedToBottom(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reopenConnection = async () => {
|
||||||
|
closeWebSocket()
|
||||||
|
openEmptyWebSocket();
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
ws.current.close();
|
ws.current.close();
|
||||||
@@ -349,19 +357,28 @@ export const TrafficViewer : React.FC<TrafficViewerProps> = ({setAnalyzeStatus,
|
|||||||
setAddressesWithTLS={setAddressesWithTLS}
|
setAddressesWithTLS={setAddressesWithTLS}
|
||||||
userDismissedTLSWarning={userDismissedTLSWarning}
|
userDismissedTLSWarning={userDismissedTLSWarning}
|
||||||
setUserDismissedTLSWarning={setUserDismissedTLSWarning} />
|
setUserDismissedTLSWarning={setUserDismissedTLSWarning} />
|
||||||
<ToastContainer/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const MemoiedTrafficViewer = React.memo(TrafficViewer)
|
const MemoiedTrafficViewer = React.memo(TrafficViewer)
|
||||||
const TrafficViewerContainer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus, trafficViewerApiProp,
|
const TrafficViewerContainer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus, trafficViewerApiProp,
|
||||||
actionButtons, isShowStatusBar = true ,
|
actionButtons, isShowStatusBar = true,
|
||||||
webSocketUrl, isCloseWebSocket, isDemoBannerView}) => {
|
webSocketUrl, isCloseWebSocket, isDemoBannerView }) => {
|
||||||
return <RecoilRoot>
|
return <RecoilRoot>
|
||||||
<MemoiedTrafficViewer actionButtons={actionButtons} isShowStatusBar={isShowStatusBar} webSocketUrl={webSocketUrl}
|
<MemoiedTrafficViewer actionButtons={actionButtons} isShowStatusBar={isShowStatusBar} webSocketUrl={webSocketUrl}
|
||||||
isCloseWebSocket={isCloseWebSocket} trafficViewerApiProp={trafficViewerApiProp}
|
isCloseWebSocket={isCloseWebSocket} trafficViewerApiProp={trafficViewerApiProp}
|
||||||
setAnalyzeStatus={setAnalyzeStatus} isDemoBannerView={isDemoBannerView} />
|
setAnalyzeStatus={setAnalyzeStatus} isDemoBannerView={isDemoBannerView} />
|
||||||
|
<ToastContainer enableMultiContainer containerId={TOAST_CONTAINER_ID}
|
||||||
|
position="bottom-right"
|
||||||
|
autoClose={5000}
|
||||||
|
hideProgressBar={false}
|
||||||
|
newestOnTop={false}
|
||||||
|
closeOnClick
|
||||||
|
rtl={false}
|
||||||
|
pauseOnFocusLoss
|
||||||
|
draggable
|
||||||
|
pauseOnHover />
|
||||||
</RecoilRoot>
|
</RecoilRoot>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 21 KiB |
@@ -6,11 +6,11 @@ export interface Props {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Checkbox: React.FC<Props> = ({checked, onToggle, disabled}) => {
|
const Checkbox: React.FC<Props> = ({checked, onToggle, disabled, ...props}) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<input style={!disabled ? {cursor: "pointer"}: {}} type="checkbox" checked={checked} disabled={disabled} onChange={(event) => onToggle(event.target.checked)}/>
|
<input style={!disabled ? {cursor: "pointer"}: {}} type="checkbox" checked={checked} disabled={disabled} onChange={(event) => onToggle(event.target.checked)} {...props}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export interface InformationIconProps{
|
|||||||
|
|
||||||
export const InformationIcon: React.FC<InformationIconProps> = ({link,style}) => {
|
export const InformationIcon: React.FC<InformationIconProps> = ({link,style}) => {
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
<a href={DEFUALT_LINK ? DEFUALT_LINK : link} style={style} className={styles.flex} title="documentation">
|
<a href={DEFUALT_LINK ? DEFUALT_LINK : link} style={style} className={styles.flex} title="documentation" target="_blank">
|
||||||
<img className="headerIcon" src={infoImg} alt="Info icon"/>
|
<img className="headerIcon" src={infoImg} alt="Info icon"/>
|
||||||
</a>
|
</a>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
.highlighterContainer
|
||||||
|
&.fitScreen
|
||||||
|
pre
|
||||||
|
max-height: 90vh
|
||||||
|
overflow: auto
|
||||||
|
|
||||||
|
pre
|
||||||
|
code
|
||||||
|
font-size: 0.75rem
|
||||||
|
|
||||||
|
&:first-child
|
||||||
|
margin-right: 0.75rem
|
||||||
|
background: #F7F9FC
|
||||||
|
|
||||||
|
.react-syntax-highlighter-line-number
|
||||||
|
color: rgb(98, 126, 247)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
&:last-child
|
||||||
|
display: block
|
||||||
|
|
||||||
|
code.hljs
|
||||||
|
white-space: pre-wrap
|
||||||
|
|
||||||
|
code.hljs:before
|
||||||
|
counter-reset: listing
|
||||||
|
|
||||||
|
.hljsMarkerLine
|
||||||
|
counter-increment: listing
|
||||||
|
|
||||||
|
.hljsMarkerLine:before
|
||||||
|
content: counter(listing) " "
|
||||||
|
display: inline-block
|
||||||
|
width: 3rem
|
||||||
|
padding-left: auto
|
||||||
|
margin-left: auto
|
||||||
|
text-align: right
|
||||||
|
opacity: .5
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
.highlighterContainer {
|
|
||||||
&.fitScreen {
|
|
||||||
pre {
|
|
||||||
max-height: 90vh;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
code {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
margin-right: 0.75rem;
|
|
||||||
background: #F7F9FC;
|
|
||||||
|
|
||||||
.react-syntax-highlighter-line-number {
|
|
||||||
color: rgb(98, 126, 247);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
code.hljs {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
code.hljs:before {
|
|
||||||
counter-reset: listing;
|
|
||||||
}
|
|
||||||
|
|
||||||
code.hljs .hljs-marker-line {
|
|
||||||
counter-increment: listing;
|
|
||||||
}
|
|
||||||
|
|
||||||
code.hljs .hljs-marker-line:before {
|
|
||||||
content: counter(listing) " ";
|
|
||||||
display: inline-block;
|
|
||||||
width: 3rem;
|
|
||||||
padding-left: auto;
|
|
||||||
margin-left: auto;
|
|
||||||
text-align: right;
|
|
||||||
opacity: .5;
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Lowlight from 'react-lowlight'
|
import Lowlight from 'react-lowlight'
|
||||||
import 'highlight.js/styles/atom-one-light.css'
|
import 'highlight.js/styles/atom-one-light.css'
|
||||||
import './index.scss';
|
import styles from './index.module.sass';
|
||||||
|
|
||||||
import xml from 'highlight.js/lib/languages/xml'
|
import xml from 'highlight.js/lib/languages/xml'
|
||||||
import json from 'highlight.js/lib/languages/json'
|
import json from 'highlight.js/lib/languages/json'
|
||||||
@@ -37,11 +37,11 @@ export const SyntaxHighlighter: React.FC<Props> = ({
|
|||||||
const markers = showLineNumbers ? code.split("\n").map((item, i) => {
|
const markers = showLineNumbers ? code.split("\n").map((item, i) => {
|
||||||
return {
|
return {
|
||||||
line: i + 1,
|
line: i + 1,
|
||||||
className: 'hljs-marker-line'
|
className: styles.hljsMarkerLine
|
||||||
}
|
}
|
||||||
}) : [];
|
}) : [];
|
||||||
|
|
||||||
return <div style={{fontSize: ".75rem"}}><Lowlight language={language ? language : ""} value={code} markers={markers}/></div>;
|
return <div style={{fontSize: ".75rem"}} className={styles.highlighterContainer}><Lowlight language={language ? language : ""} value={code} markers={markers}/></div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SyntaxHighlighter;
|
export default SyntaxHighlighter;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill="#627EF7" d="M12 4.707c-2.938-1.83-7.416-2.567-12-2.707v17.714c3.937.12 7.795.681 10.667 1.995.846.388 1.817.388 2.667 0 2.872-1.314 6.729-1.875 10.666-1.995v-17.714c-4.584.14-9.062.877-12 2.707zm-10 13.104v-13.704c5.157.389 7.527 1.463 9 2.334v13.168c-1.525-.546-4.716-1.505-9-1.798zm20 0c-4.283.293-7.475 1.252-9 1.799v-13.171c1.453-.861 3.83-1.942 9-2.332v13.704z"/>
|
<path d="M19 21H6.14286C5.07143 21 4 20.32 4 18.96C4 17.6 5.07143 16.92 6.14286 16.92H19V4H6.14286C5.07143 4 4 5.02 4 6.04V18.96M16.8571 17.6V20.32V17.6Z" stroke="#627EF7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
<rect x="8" y="7" width="7" height="2" fill="#627EF7"/>
|
||||||
|
<rect x="8" y="11" width="4" height="2" fill="#627EF7"/>
|
||||||
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 471 B After Width: | Height: | Size: 454 B |
Binary file not shown.
|
Before Width: | Height: | Size: 21 KiB |
1
ui-common/src/configs/Consts.ts
Normal file
1
ui-common/src/configs/Consts.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const TOAST_CONTAINER_ID = "Common";
|
||||||
4657
ui/package-lock.json
generated
4657
ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,7 @@
|
|||||||
"@types/jest": "^26.0.22",
|
"@types/jest": "^26.0.22",
|
||||||
"@types/node": "^12.20.10",
|
"@types/node": "^12.20.10",
|
||||||
"@uiw/react-textarea-code-editor": "^1.4.12",
|
"@uiw/react-textarea-code-editor": "^1.4.12",
|
||||||
"@up9/mizu-common": "1.0.137",
|
"@up9/mizu-common": "file:up9-mizu-common-0.0.0.tgz",
|
||||||
"axios": "^0.25.0",
|
"axios": "^0.25.0",
|
||||||
"core-js": "^3.20.2",
|
"core-js": "^3.20.2",
|
||||||
"craco-babel-loader": "^1.0.3",
|
"craco-babel-loader": "^1.0.3",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, {useEffect, useState} from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
import { Button } from "@material-ui/core";
|
import { Button } from "@material-ui/core";
|
||||||
import Api,{getWebsocketUrl} from "../../../helpers/api";
|
import Api, { MizuWebsocketURL } from "../../../helpers/api";
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import {useSetRecoilState, useRecoilState} from "recoil";
|
import {useSetRecoilState, useRecoilState} from "recoil";
|
||||||
import {useCommonStyles} from "../../../helpers/commonStyle"
|
import {useCommonStyles} from "../../../helpers/commonStyle"
|
||||||
@@ -44,7 +44,7 @@ const trafficViewerApi = {...api}
|
|||||||
className={commonClasses.outlinedButton + " " + commonClasses.imagedButton}
|
className={commonClasses.outlinedButton + " " + commonClasses.imagedButton}
|
||||||
style={{ marginRight: 25, textTransform: 'unset' }}
|
style={{ marginRight: 25, textTransform: 'unset' }}
|
||||||
onClick={handleOpenOasModal}>
|
onClick={handleOpenOasModal}>
|
||||||
OpenApi Specs
|
OpenAPI Specs
|
||||||
</Button>}
|
</Button>}
|
||||||
{window["isServiceMapEnabled"] && <Button
|
{window["isServiceMapEnabled"] && <Button
|
||||||
startIcon={<img src={serviceMap} className="custom" alt="service-map" style={{marginRight:"8%"}}></img>}
|
startIcon={<img src={serviceMap} className="custom" alt="service-map" style={{marginRight:"8%"}}></img>}
|
||||||
@@ -65,7 +65,7 @@ const trafficViewerApi = {...api}
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TrafficViewer setAnalyzeStatus={setAnalyzeStatus} webSocketUrl={getWebsocketUrl()} isCloseWebSocket={!openWebSocket}
|
<TrafficViewer setAnalyzeStatus={setAnalyzeStatus} webSocketUrl={MizuWebsocketURL} isCloseWebSocket={!openWebSocket}
|
||||||
trafficViewerApiProp={trafficViewerApi} actionButtons={actionButtons} isShowStatusBar={!openOasModal} isDemoBannerView={false}/>
|
trafficViewerApiProp={trafficViewerApi} actionButtons={actionButtons} isShowStatusBar={!openOasModal} isDemoBannerView={false}/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import ServiceMapOptions from './ServiceMapOptions'
|
|||||||
import { useCommonStyles } from "../../helpers/commonStyle";
|
import { useCommonStyles } from "../../helpers/commonStyle";
|
||||||
import refresh from "../assets/refresh.svg";
|
import refresh from "../assets/refresh.svg";
|
||||||
import close from "../assets/close.svg";
|
import close from "../assets/close.svg";
|
||||||
|
import { TOAST_CONTAINER_ID } from "../../consts";
|
||||||
|
|
||||||
interface GraphData {
|
interface GraphData {
|
||||||
nodes: Node[];
|
nodes: Node[];
|
||||||
@@ -140,7 +141,7 @@ export const ServiceMapModal: React.FC<ServiceMapModalProps> = ({ isOpen, onOpen
|
|||||||
setGraphData(newGraphData)
|
setGraphData(newGraphData)
|
||||||
|
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
toast.error("An error occurred while loading Mizu Service Map, see console for mode details");
|
toast.error("An error occurred while loading Mizu Service Map, see console for mode details", { containerId: TOAST_CONTAINER_ID });
|
||||||
console.error(ex);
|
console.error(ex);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
@@ -176,20 +177,20 @@ export const ServiceMapModal: React.FC<ServiceMapModalProps> = ({ isOpen, onOpen
|
|||||||
<img alt="spinner" src={spinnerImg} style={{ height: 50 }} />
|
<img alt="spinner" src={spinnerImg} style={{ height: 50 }} />
|
||||||
</div>}
|
</div>}
|
||||||
{!isLoading && <div style={{ height: "100%", width: "100%" }}>
|
{!isLoading && <div style={{ height: "100%", width: "100%" }}>
|
||||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
startIcon={<img src={refresh} className="custom" alt="refresh" style={{ marginRight:"8%"}}></img>}
|
startIcon={<img src={refresh} className="custom" alt="refresh" style={{ marginRight: "8%" }}></img>}
|
||||||
size="medium"
|
size="medium"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
className={commonClasses.outlinedButton + " " + commonClasses.imagedButton}
|
className={commonClasses.outlinedButton + " " + commonClasses.imagedButton}
|
||||||
onClick={refreshServiceMap}
|
onClick={refreshServiceMap}
|
||||||
>
|
>
|
||||||
Refresh
|
Refresh
|
||||||
</Button>
|
</Button>
|
||||||
|
</div>
|
||||||
|
<img src={close} alt="close" onClick={() => onClose()} style={{ cursor: "pointer" }}></img>
|
||||||
</div>
|
</div>
|
||||||
<img src={close} alt="close" onClick={() => onClose()} style={{cursor:"pointer"}}></img>
|
|
||||||
</div>
|
|
||||||
<Graph
|
<Graph
|
||||||
graph={graphData}
|
graph={graphData}
|
||||||
options={ServiceMapOptions}
|
options={ServiceMapOptions}
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
export const adminUsername = "admin";
|
export const adminUsername = "admin";
|
||||||
|
export const TOAST_CONTAINER_ID = "Community";
|
||||||
|
|||||||
@@ -3,13 +3,10 @@ import * as axios from "axios";
|
|||||||
export const MizuWebsocketURL = process.env.REACT_APP_OVERRIDE_WS_URL ? process.env.REACT_APP_OVERRIDE_WS_URL :
|
export const MizuWebsocketURL = process.env.REACT_APP_OVERRIDE_WS_URL ? process.env.REACT_APP_OVERRIDE_WS_URL :
|
||||||
window.location.protocol === 'https:' ? `wss://${window.location.host}/ws` : `ws://${window.location.host}/ws`;
|
window.location.protocol === 'https:' ? `wss://${window.location.host}/ws` : `ws://${window.location.host}/ws`;
|
||||||
|
|
||||||
export const FormValidationErrorType = "formError";
|
|
||||||
|
|
||||||
const CancelToken = axios.CancelToken;
|
const CancelToken = axios.CancelToken;
|
||||||
|
|
||||||
const apiURL = process.env.REACT_APP_OVERRIDE_API_URL ? process.env.REACT_APP_OVERRIDE_API_URL : `${window.location.origin}/`;
|
const apiURL = process.env.REACT_APP_OVERRIDE_API_URL ? process.env.REACT_APP_OVERRIDE_API_URL : `${window.location.origin}/`;
|
||||||
|
|
||||||
let token = ""
|
|
||||||
let client = null
|
let client = null
|
||||||
let source = null
|
let source = null
|
||||||
|
|
||||||
@@ -24,8 +21,6 @@ export default class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
token = localStorage.getItem("token");
|
|
||||||
|
|
||||||
client = this.getAxiosClient();
|
client = this.getAxiosClient();
|
||||||
source = null;
|
source = null;
|
||||||
}
|
}
|
||||||
@@ -125,20 +120,10 @@ export default class Api {
|
|||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
persistToken = (tk) => {
|
|
||||||
token = tk;
|
|
||||||
client = this.getAxiosClient();
|
|
||||||
localStorage.setItem('token', token);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAxiosClient = () => {
|
getAxiosClient = () => {
|
||||||
const headers = {
|
const headers = {
|
||||||
Accept: "application/json"
|
Accept: "application/json"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (token) {
|
|
||||||
headers['x-session-token'] = `${token}`; // we use `x-session-token` instead of `Authorization` because the latter is reserved by kubectl proxy, making mizu view not work
|
|
||||||
}
|
|
||||||
return axios.create({
|
return axios.create({
|
||||||
baseURL: apiURL,
|
baseURL: apiURL,
|
||||||
timeout: 31000,
|
timeout: 31000,
|
||||||
@@ -146,12 +131,3 @@ export default class Api {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getWebsocketUrl() {
|
|
||||||
let websocketUrl = MizuWebsocketURL;
|
|
||||||
if (token) {
|
|
||||||
websocketUrl += `/${token}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return websocketUrl;
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import './index.sass';
|
import './index.sass';
|
||||||
import {ToastContainer} from "react-toastify";
|
import {ToastContainer} from "react-toastify";
|
||||||
import 'react-toastify/dist/ReactToastify.css';
|
import 'react-toastify/dist/ReactToastify.min.css';
|
||||||
import {RecoilRoot} from "recoil";
|
import {RecoilRoot} from "recoil";
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
import { TOAST_CONTAINER_ID } from './consts';
|
||||||
|
|
||||||
ReactDOM.render( <>
|
ReactDOM.render( <>
|
||||||
<RecoilRoot>
|
<RecoilRoot>
|
||||||
<App/>
|
<App/>
|
||||||
<ToastContainer
|
<ToastContainer enableMultiContainer containerId={TOAST_CONTAINER_ID}
|
||||||
position="bottom-right"
|
position="bottom-right"
|
||||||
autoClose={5000}
|
autoClose={5000}
|
||||||
hideProgressBar={false}
|
hideProgressBar={false}
|
||||||
|
|||||||
Reference in New Issue
Block a user