mirror of
https://github.com/kubeshark/kubeshark.git
synced 2026-02-19 20:40:17 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68c4ee9a4f | ||
|
|
bfbbc27e62 | ||
|
|
e2df973fe6 | ||
|
|
656809512b | ||
|
|
b96542a8ed | ||
|
|
a55f51f0e7 | ||
|
|
f102079e3c | ||
|
|
80e881fee2 | ||
|
|
1ba444dba1 | ||
|
|
0b7d535a81 | ||
|
|
9d0c2a693e | ||
|
|
2b2c7687a1 | ||
|
|
4708998f54 | ||
|
|
7570df3828 | ||
|
|
44c8908358 | ||
|
|
0ca5482946 | ||
|
|
c20f74f582 |
19
Makefile
19
Makefile
@@ -8,7 +8,7 @@ SHELL=/bin/bash
|
||||
# HELP
|
||||
# This will output the help for each task
|
||||
# thanks to https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
|
||||
.PHONY: help ui agent cli tap docker
|
||||
.PHONY: help ui extensions extensions-debug agent agent-debug cli tap docker
|
||||
|
||||
help: ## This help.
|
||||
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||
@@ -28,6 +28,9 @@ ui: ## Build UI.
|
||||
cli: ## Build CLI.
|
||||
@echo "building cli"; cd cli && $(MAKE) build
|
||||
|
||||
cli-debug: ## Build CLI.
|
||||
@echo "building cli"; cd cli && $(MAKE) build-debug
|
||||
|
||||
build-cli-ci: ## Build CLI for CI.
|
||||
@echo "building cli for ci"; cd cli && $(MAKE) build GIT_BRANCH=ci SUFFIX=ci
|
||||
|
||||
@@ -37,6 +40,12 @@ agent: ## Build agent.
|
||||
${MAKE} extensions
|
||||
@ls -l agent/build
|
||||
|
||||
agent-debug: ## Build agent for debug.
|
||||
@(echo "building mizu agent for debug.." )
|
||||
@(cd agent; go build -gcflags="all=-N -l" -o build/mizuagent main.go)
|
||||
${MAKE} extensions-debug
|
||||
@ls -l agent/build
|
||||
|
||||
docker: ## Build and publish agent docker image.
|
||||
$(MAKE) push-docker
|
||||
|
||||
@@ -62,7 +71,7 @@ push-cli: ## Build and publish CLI.
|
||||
gsutil cp -r ./cli/bin/* gs://${BUCKET_PATH}/
|
||||
gsutil setmeta -r -h "Cache-Control:public, max-age=30" gs://${BUCKET_PATH}/\*
|
||||
|
||||
clean: clean-ui clean-agent clean-cli clean-docker ## Clean all build artifacts.
|
||||
clean: clean-ui clean-agent clean-cli clean-docker clean-extensions ## Clean all build artifacts.
|
||||
|
||||
clean-ui: ## Clean UI.
|
||||
@(rm -rf ui/build ; echo "UI cleanup done" )
|
||||
@@ -73,9 +82,15 @@ clean-agent: ## Clean agent.
|
||||
clean-cli: ## Clean CLI.
|
||||
@(cd cli; make clean ; echo "CLI cleanup done" )
|
||||
|
||||
clean-extensions: ## Clean extensions
|
||||
@(rm -rf tap/extensions/*.so ; echo "Extensions cleanup done" )
|
||||
|
||||
clean-docker:
|
||||
@(echo "DOCKER cleanup - NOT IMPLEMENTED YET " )
|
||||
|
||||
extensions-debug:
|
||||
devops/build_extensions_debug.sh
|
||||
|
||||
extensions:
|
||||
devops/build_extensions.sh
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
"screenshotOnRunFailure": false,
|
||||
"testFiles":
|
||||
["tests/GuiPort.js",
|
||||
"tests/MultipleNamespaces.js"],
|
||||
"tests/MultipleNamespaces.js",
|
||||
"tests/Regex.js"],
|
||||
"env": {
|
||||
"testUrl": "http://localhost:8899/"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
const columns = {podName : 1, namespace : 2, tapping : 3};
|
||||
const greenStatusImageSrc = '/static/media/success.662997eb.svg';
|
||||
|
||||
function getDomPathInStatusBar(line, column) {
|
||||
return `.expandedStatusBar > :nth-child(2) > > :nth-child(2) > :nth-child(${line}) > :nth-child(${column})`;
|
||||
}
|
||||
|
||||
export function checkLine(line, expectedValues) {
|
||||
cy.get(getDomPathInStatusBar(line, columns.podName)).invoke('text').then(podValue => {
|
||||
const podName = getOnlyPodName(podValue);
|
||||
expect(podName).to.equal(expectedValues.podName);
|
||||
|
||||
cy.get(getDomPathInStatusBar(line, columns.namespace)).invoke('text').then(namespaceValue => {
|
||||
expect(namespaceValue).to.equal(expectedValues.namespace);
|
||||
cy.get(getDomPathInStatusBar(line, columns.tapping)).children().should('have.attr', 'src', greenStatusImageSrc);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function findLineAndCheck(expectedValues) {
|
||||
cy.get('.expandedStatusBar > :nth-child(2) > > :nth-child(2) > > :nth-child(1)').then(pods => {
|
||||
cy.get('.expandedStatusBar > :nth-child(2) > > :nth-child(2) > > :nth-child(2)').then(namespaces => {
|
||||
// organizing namespaces array
|
||||
const podObjectsArray = Object.values(pods ?? {});
|
||||
const namespacesObjectsArray = Object.values(namespaces ?? {});
|
||||
let lineNumber = -1;
|
||||
namespacesObjectsArray.forEach((namespaceObj, index) => {
|
||||
const currentLine = index + 1;
|
||||
lineNumber = (namespaceObj.getAttribute && namespaceObj.innerHTML === expectedValues.namespace && (getOnlyPodName(podObjectsArray[index].innerHTML)) === expectedValues.podName) ? currentLine : lineNumber;
|
||||
});
|
||||
lineNumber === -1 ? throwError(expectedValues) : checkLine(lineNumber, expectedValues);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function throwError(expectedValues) {
|
||||
throw new Error(`The pod named ${expectedValues.podName} doesn't match any namespace named ${expectedValues.namespace}`);
|
||||
}
|
||||
|
||||
export function getExpectedDetailsDict(podName, namespace) {
|
||||
return {podName : podName, namespace : namespace};
|
||||
}
|
||||
|
||||
function getOnlyPodName(podElementFullStr) {
|
||||
return podElementFullStr.substring(0, podElementFullStr.indexOf('-'));
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
it('check', function () {
|
||||
cy.visit(`http://localhost:${Cypress.env('port')}/`)
|
||||
cy.visit(`http://localhost:${Cypress.env('port')}/`);
|
||||
|
||||
cy.get('.header').should('be.visible')
|
||||
cy.get('.TrafficPageHeader').should('be.visible')
|
||||
cy.get('.TrafficPage-ListContainer').should('be.visible')
|
||||
cy.get('.TrafficPage-Container').should('be.visible')
|
||||
})
|
||||
cy.get('.header').should('be.visible');
|
||||
cy.get('.TrafficPageHeader').should('be.visible');
|
||||
cy.get('.TrafficPage-ListContainer').should('be.visible');
|
||||
cy.get('.TrafficPage-Container').should('be.visible');
|
||||
});
|
||||
|
||||
@@ -1,67 +1,18 @@
|
||||
const columns = {"podName" : 1, "namespace" : 2, "tapping" : 3}
|
||||
const greenStatusImageSrc = "/static/media/success.662997eb.svg"
|
||||
import {findLineAndCheck, getExpectedDetailsDict} from '../page_objects/StatusBar';
|
||||
|
||||
it('opening', function () {
|
||||
cy.visit(Cypress.env('testUrl'))
|
||||
cy.get('.podsCount').trigger('mouseover')
|
||||
cy.visit(Cypress.env('testUrl'));
|
||||
cy.get('.podsCount').trigger('mouseover');
|
||||
});
|
||||
|
||||
[1, 2, 3].map(doItFunc)
|
||||
[1, 2, 3].map(doItFunc);
|
||||
|
||||
function doItFunc(number) {
|
||||
const podName = Cypress.env(`name${number}`)
|
||||
const namespace = Cypress.env(`namespace${number}`)
|
||||
const podName = Cypress.env(`name${number}`);
|
||||
const namespace = Cypress.env(`namespace${number}`);
|
||||
|
||||
it(`verifying the pod (${podName}, ${namespace})`, function () {
|
||||
findLineAndCheck({"podName" : podName, "namespace" : namespace})
|
||||
})
|
||||
findLineAndCheck(getExpectedDetailsDict(podName, namespace));
|
||||
});
|
||||
}
|
||||
|
||||
function getDomPathInStatusBar(line, column) {
|
||||
return `.expandedStatusBar > :nth-child(2) > > :nth-child(2) > :nth-child(${line}) > :nth-child(${column})`
|
||||
}
|
||||
|
||||
function checkLine(line, expectedValues) {
|
||||
cy.get(getDomPathInStatusBar(line, columns.podName)).invoke('text').then(podValue => {
|
||||
const podName = podValue.substring(0, podValue.indexOf('-'))
|
||||
expect(podName).to.equal(expectedValues.podName)
|
||||
|
||||
cy.get(getDomPathInStatusBar(line, columns.namespace)).invoke('text').then(namespaceValue => {
|
||||
expect(namespaceValue).to.equal(expectedValues.namespace)
|
||||
cy.get(getDomPathInStatusBar(line, columns.tapping)).children().should('have.attr', 'src', greenStatusImageSrc)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function findLineAndCheck(expectedValues) {
|
||||
cy.get('.expandedStatusBar > :nth-child(2) > > :nth-child(2) > > :nth-child(1)').then(pods => {
|
||||
cy.get('.expandedStatusBar > :nth-child(2) > > :nth-child(2) > > :nth-child(2)').then(namespaces => {
|
||||
|
||||
// organizing namespaces array
|
||||
const namespacesObjectsArray = Object.values(namespaces)
|
||||
let namespacesArray = []
|
||||
namespacesObjectsArray.forEach(line => {
|
||||
line.getAttribute ? namespacesArray.push(line.innerHTML) : null
|
||||
})
|
||||
|
||||
// organizing pods array
|
||||
const podObjectsArray = Object.values(pods)
|
||||
let podsArray = []
|
||||
podObjectsArray.forEach(line => {
|
||||
line.getAttribute ? podsArray.push(line.innerHTML.substring(0, line.innerHTML.indexOf('-'))) : null
|
||||
})
|
||||
|
||||
let rightIndex = -1
|
||||
podsArray.forEach((element, index) => {
|
||||
if (element === expectedValues.podName && namespacesArray[index] === expectedValues.namespace) {
|
||||
rightIndex = index + 1
|
||||
}
|
||||
})
|
||||
rightIndex === -1 ? throwError(expectedValues.podName, expectedValues.namespace) : checkLine(rightIndex, expectedValues)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function throwError(pod, namespace) {
|
||||
throw new Error(`The pod named ${pod} doesn't match any namespace named ${namespace}`)
|
||||
}
|
||||
|
||||
11
acceptanceTests/cypress/integration/tests/Regex.js
Normal file
11
acceptanceTests/cypress/integration/tests/Regex.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import {getExpectedDetailsDict, checkLine} from '../page_objects/StatusBar';
|
||||
|
||||
|
||||
it('opening', function () {
|
||||
cy.visit(Cypress.env('testUrl'));
|
||||
cy.get('.podsCount').trigger('mouseover');
|
||||
|
||||
cy.get('.expandedStatusBar > :nth-child(2) > > :nth-child(2) >').should('have.length', 1); // one line
|
||||
|
||||
checkLine(1, getExpectedDetailsDict(Cypress.env('name'), Cypress.env('namespace')));
|
||||
});
|
||||
@@ -280,30 +280,8 @@ func TestTapRegex(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
podsUrl := fmt.Sprintf("%v/status/tap", apiServerUrl)
|
||||
requestResult, requestErr := executeHttpGetRequest(podsUrl)
|
||||
if requestErr != nil {
|
||||
t.Errorf("failed to get tap status, err: %v", requestErr)
|
||||
return
|
||||
}
|
||||
|
||||
pods, err := getPods(requestResult)
|
||||
if err != nil {
|
||||
t.Errorf("failed to get pods, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(expectedPods) != len(pods) {
|
||||
t.Errorf("unexpected result - expected pods length: %v, actual pods length: %v", len(expectedPods), len(pods))
|
||||
return
|
||||
}
|
||||
|
||||
for _, expectedPod := range expectedPods {
|
||||
if !isPodDescriptorInPodArray(pods, expectedPod) {
|
||||
t.Errorf("unexpected result - expected pod not found, pod namespace: %v, pod name: %v", expectedPod.Namespace, expectedPod.Name)
|
||||
return
|
||||
}
|
||||
}
|
||||
runCypressTests(t, fmt.Sprintf("npx cypress run --spec \"cypress/integration/tests/Regex.js\" --env name=%v,namespace=%v",
|
||||
expectedPods[0].Name, expectedPods[0].Namespace))
|
||||
}
|
||||
|
||||
func TestTapDryRun(t *testing.T) {
|
||||
|
||||
@@ -17,7 +17,7 @@ require (
|
||||
github.com/orcaman/concurrent-map v0.0.0-20210106121528-16402b402231
|
||||
github.com/ory/kratos-client-go v0.8.2-alpha.1
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/up9inc/basenine/client/go v0.0.0-20220107003657-7c0578359920
|
||||
github.com/up9inc/basenine/client/go v0.0.0-20220110083745-04fbc6c2068d
|
||||
github.com/up9inc/mizu/shared v0.0.0
|
||||
github.com/up9inc/mizu/tap v0.0.0
|
||||
github.com/up9inc/mizu/tap/api v0.0.0
|
||||
|
||||
@@ -472,8 +472,8 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/up9inc/basenine/client/go v0.0.0-20220107003657-7c0578359920 h1:QQpgRleNNpxxAG/rKmk4dwJh0jHyRaQz4QOVlPmqv1c=
|
||||
github.com/up9inc/basenine/client/go v0.0.0-20220107003657-7c0578359920/go.mod h1:SvJGPoa/6erhUQV7kvHBwM/0x5LyO6XaG2lUaCaKiUI=
|
||||
github.com/up9inc/basenine/client/go v0.0.0-20220110083745-04fbc6c2068d h1:WTz53dcfqCIWZpZLQoHbIcNc21s0ZHEZH7EqMPp99qQ=
|
||||
github.com/up9inc/basenine/client/go v0.0.0-20220110083745-04fbc6c2068d/go.mod h1:SvJGPoa/6erhUQV7kvHBwM/0x5LyO6XaG2lUaCaKiUI=
|
||||
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA=
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
|
||||
@@ -119,15 +119,12 @@ func startReadingChannel(outputItems <-chan *tapApi.OutputChannelItem, extension
|
||||
extension := extensionsMap[item.Protocol.Name]
|
||||
resolvedSource, resolvedDestionation := resolveIP(item.ConnectionInfo)
|
||||
mizuEntry := extension.Dissector.Analyze(item, resolvedSource, resolvedDestionation)
|
||||
baseEntry := extension.Dissector.Summarize(mizuEntry)
|
||||
mizuEntry.Base = baseEntry
|
||||
if extension.Protocol.Name == "http" {
|
||||
if !disableOASValidation {
|
||||
var httpPair tapApi.HTTPRequestResponsePair
|
||||
json.Unmarshal([]byte(mizuEntry.HTTPPair), &httpPair)
|
||||
|
||||
contract := handleOAS(ctx, doc, router, httpPair.Request.Payload.RawRequest, httpPair.Response.Payload.RawResponse, contractContent)
|
||||
baseEntry.ContractStatus = contract.Status
|
||||
mizuEntry.ContractStatus = contract.Status
|
||||
mizuEntry.ContractRequestReason = contract.RequestReason
|
||||
mizuEntry.ContractResponseReason = contract.ResponseReason
|
||||
@@ -137,7 +134,7 @@ func startReadingChannel(outputItems <-chan *tapApi.OutputChannelItem, extension
|
||||
harEntry, err := utils.NewEntry(mizuEntry.Request, mizuEntry.Response, mizuEntry.StartTime, mizuEntry.ElapsedTime)
|
||||
if err == nil {
|
||||
rules, _, _ := models.RunValidationRulesState(*harEntry, mizuEntry.Destination.Name)
|
||||
baseEntry.Rules = rules
|
||||
mizuEntry.Rules = rules
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/shared/debounce"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
tapApi "github.com/up9inc/mizu/tap/api"
|
||||
)
|
||||
|
||||
type EventHandlers interface {
|
||||
@@ -131,18 +132,10 @@ func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers Even
|
||||
return
|
||||
}
|
||||
|
||||
var dataMap map[string]interface{}
|
||||
err = json.Unmarshal(bytes, &dataMap)
|
||||
var entry *tapApi.Entry
|
||||
err = json.Unmarshal(bytes, &entry)
|
||||
|
||||
var base map[string]interface{}
|
||||
switch dataMap["base"].(type) {
|
||||
case map[string]interface{}:
|
||||
base = dataMap["base"].(map[string]interface{})
|
||||
base["id"] = uint(dataMap["id"].(float64))
|
||||
default:
|
||||
logger.Log.Debugf("Base field has an unrecognized type: %+v", dataMap)
|
||||
continue
|
||||
}
|
||||
base := tapApi.Summarize(entry)
|
||||
|
||||
baseEntryBytes, _ := models.CreateBaseEntryWebSocketMessage(base)
|
||||
SendToSocket(socketId, baseEntryBytes)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"mizuserver/pkg/models"
|
||||
"mizuserver/pkg/providers"
|
||||
"mizuserver/pkg/providers/tappersCount"
|
||||
"mizuserver/pkg/up9"
|
||||
"sync"
|
||||
|
||||
@@ -29,7 +30,7 @@ func init() {
|
||||
func (h *RoutesEventHandlers) WebSocketConnect(socketId int, isTapper bool) {
|
||||
if isTapper {
|
||||
logger.Log.Infof("Websocket event - Tapper connected, socket ID: %d", socketId)
|
||||
providers.TapperAdded()
|
||||
tappersCount.Add()
|
||||
} else {
|
||||
logger.Log.Infof("Websocket event - Browser socket connected, socket ID: %d", socketId)
|
||||
socketListLock.Lock()
|
||||
@@ -41,7 +42,7 @@ func (h *RoutesEventHandlers) WebSocketConnect(socketId int, isTapper bool) {
|
||||
func (h *RoutesEventHandlers) WebSocketDisconnect(socketId int, isTapper bool) {
|
||||
if isTapper {
|
||||
logger.Log.Infof("Websocket event - Tapper disconnected, socket ID: %d", socketId)
|
||||
providers.TapperRemoved()
|
||||
tappersCount.Remove()
|
||||
} else {
|
||||
logger.Log.Infof("Websocket event - Browser socket disconnected, socket ID: %d", socketId)
|
||||
socketListLock.Lock()
|
||||
|
||||
@@ -11,18 +11,20 @@ import (
|
||||
"mizuserver/pkg/config"
|
||||
"mizuserver/pkg/models"
|
||||
"mizuserver/pkg/providers"
|
||||
"mizuserver/pkg/providers/tapConfig"
|
||||
"mizuserver/pkg/providers/tappedPods"
|
||||
"mizuserver/pkg/providers/tappersStatus"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
var globalTapConfig = &models.TapConfig{TappedNamespaces: make(map[string]bool)}
|
||||
var cancelTapperSyncer context.CancelFunc
|
||||
|
||||
func PostTapConfig(c *gin.Context) {
|
||||
tapConfig := &models.TapConfig{}
|
||||
requestTapConfig := &models.TapConfig{}
|
||||
|
||||
if err := c.Bind(tapConfig); err != nil {
|
||||
if err := c.Bind(requestTapConfig); err != nil {
|
||||
c.JSON(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
@@ -30,14 +32,14 @@ func PostTapConfig(c *gin.Context) {
|
||||
if cancelTapperSyncer != nil {
|
||||
cancelTapperSyncer()
|
||||
|
||||
providers.TapStatus = shared.TapStatus{}
|
||||
providers.TappersStatus = make(map[string]shared.TapperStatus)
|
||||
tappedPods.Set([]*shared.PodInfo{})
|
||||
tappersStatus.Reset()
|
||||
|
||||
broadcastTappedPodsStatus()
|
||||
}
|
||||
|
||||
var tappedNamespaces []string
|
||||
for namespace, tapped := range tapConfig.TappedNamespaces {
|
||||
for namespace, tapped := range requestTapConfig.TappedNamespaces {
|
||||
if tapped {
|
||||
tappedNamespaces = append(tappedNamespaces, namespace)
|
||||
}
|
||||
@@ -60,7 +62,7 @@ func PostTapConfig(c *gin.Context) {
|
||||
}
|
||||
|
||||
cancelTapperSyncer = cancel
|
||||
globalTapConfig = tapConfig
|
||||
tapConfig.Save(requestTapConfig)
|
||||
|
||||
c.JSON(http.StatusOK, "OK")
|
||||
}
|
||||
@@ -81,17 +83,19 @@ func GetTapConfig(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
savedTapConfig := tapConfig.Get()
|
||||
|
||||
tappedNamespaces := make(map[string]bool)
|
||||
for _, namespace := range namespaces {
|
||||
if namespace.Name == config.Config.MizuResourcesNamespace {
|
||||
continue
|
||||
}
|
||||
|
||||
tappedNamespaces[namespace.Name] = globalTapConfig.TappedNamespaces[namespace.Name]
|
||||
tappedNamespaces[namespace.Name] = savedTapConfig.TappedNamespaces[namespace.Name]
|
||||
}
|
||||
|
||||
tapConfig := models.TapConfig{TappedNamespaces: tappedNamespaces}
|
||||
c.JSON(http.StatusOK, tapConfig)
|
||||
tapConfigToReturn := models.TapConfig{TappedNamespaces: tappedNamespaces}
|
||||
c.JSON(http.StatusOK, tapConfigToReturn)
|
||||
}
|
||||
|
||||
func startMizuTapperSyncer(ctx context.Context, provider *kubernetes.Provider, targetNamespaces []string, podFilterRegex regexp.Regexp, ignoredUserAgents []string, mizuApiFilteringOptions tapApi.TrafficFilteringOptions, serviceMesh bool) (*kubernetes.MizuTapperSyncer, error) {
|
||||
@@ -129,7 +133,7 @@ func startMizuTapperSyncer(ctx context.Context, provider *kubernetes.Provider, t
|
||||
return
|
||||
}
|
||||
|
||||
providers.TapStatus = shared.TapStatus{Pods: kubernetes.GetPodInfosForPods(tapperSyncer.CurrentlyTappedPods)}
|
||||
tappedPods.Set(kubernetes.GetPodInfosForPods(tapperSyncer.CurrentlyTappedPods))
|
||||
broadcastTappedPodsStatus()
|
||||
case tapperStatus, ok := <-tapperSyncer.TapperStatusChangedOut:
|
||||
if !ok {
|
||||
@@ -137,7 +141,7 @@ func startMizuTapperSyncer(ctx context.Context, provider *kubernetes.Provider, t
|
||||
return
|
||||
}
|
||||
|
||||
addTapperStatus(tapperStatus)
|
||||
tappersStatus.Set(&tapperStatus)
|
||||
broadcastTappedPodsStatus()
|
||||
case <-ctx.Done():
|
||||
logger.Log.Debug("mizuTapperSyncer event listener loop exiting due to context done")
|
||||
|
||||
@@ -64,8 +64,8 @@ func GetEntries(c *gin.Context) {
|
||||
var dataSlice []interface{}
|
||||
|
||||
for _, row := range data {
|
||||
var dataMap map[string]interface{}
|
||||
err = json.Unmarshal(row, &dataMap)
|
||||
var entry *tapApi.Entry
|
||||
err = json.Unmarshal(row, &entry)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": true,
|
||||
@@ -76,8 +76,7 @@ func GetEntries(c *gin.Context) {
|
||||
return // exit
|
||||
}
|
||||
|
||||
base := dataMap["base"].(map[string]interface{})
|
||||
base["id"] = uint(dataMap["id"].(float64))
|
||||
base := tapApi.Summarize(entry)
|
||||
|
||||
dataSlice = append(dataSlice, base)
|
||||
}
|
||||
@@ -95,9 +94,19 @@ func GetEntries(c *gin.Context) {
|
||||
}
|
||||
|
||||
func GetEntry(c *gin.Context) {
|
||||
singleEntryRequest := &models.SingleEntryRequest{}
|
||||
|
||||
if err := c.BindQuery(singleEntryRequest); err != nil {
|
||||
c.JSON(http.StatusBadRequest, err)
|
||||
}
|
||||
validationError := validation.Validate(singleEntryRequest)
|
||||
if validationError != nil {
|
||||
c.JSON(http.StatusBadRequest, validationError)
|
||||
}
|
||||
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
var entry tapApi.MizuEntry
|
||||
bytes, err := basenine.Single(shared.BasenineHost, shared.BaseninePort, id)
|
||||
var entry *tapApi.Entry
|
||||
bytes, err := basenine.Single(shared.BasenineHost, shared.BaseninePort, id, singleEntryRequest.Query)
|
||||
if Error(c, err) {
|
||||
return // exit
|
||||
}
|
||||
@@ -125,7 +134,7 @@ func GetEntry(c *gin.Context) {
|
||||
json.Unmarshal(inrec, &rules)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, tapApi.MizuEntryWrapper{
|
||||
c.JSON(http.StatusOK, tapApi.EntryWrapper{
|
||||
Protocol: entry.Protocol,
|
||||
Representation: string(representation),
|
||||
BodySize: bodySize,
|
||||
|
||||
@@ -8,43 +8,42 @@ import (
|
||||
"mizuserver/pkg/api"
|
||||
"mizuserver/pkg/holder"
|
||||
"mizuserver/pkg/providers"
|
||||
"mizuserver/pkg/providers/tappedPods"
|
||||
"mizuserver/pkg/providers/tappersCount"
|
||||
"mizuserver/pkg/providers/tappersStatus"
|
||||
"mizuserver/pkg/up9"
|
||||
"mizuserver/pkg/utils"
|
||||
"mizuserver/pkg/validation"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func HealthCheck(c *gin.Context) {
|
||||
tappers := make([]shared.TapperStatus, 0)
|
||||
for _, value := range providers.TappersStatus {
|
||||
tappers := make([]*shared.TapperStatus, 0)
|
||||
for _, value := range tappersStatus.Get() {
|
||||
tappers = append(tappers, value)
|
||||
}
|
||||
|
||||
response := shared.HealthResponse{
|
||||
TapStatus: providers.TapStatus,
|
||||
TappersCount: providers.TappersCount,
|
||||
TappedPods: tappedPods.Get(),
|
||||
TappersCount: tappersCount.Get(),
|
||||
TappersStatus: tappers,
|
||||
}
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
func PostTappedPods(c *gin.Context) {
|
||||
tapStatus := &shared.TapStatus{}
|
||||
if err := c.Bind(tapStatus); err != nil {
|
||||
var requestTappedPods []*shared.PodInfo
|
||||
if err := c.Bind(&requestTappedPods); err != nil {
|
||||
c.JSON(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
if err := validation.Validate(tapStatus); err != nil {
|
||||
c.JSON(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
logger.Log.Infof("[Status] POST request: %d tapped pods", len(tapStatus.Pods))
|
||||
providers.TapStatus.Pods = tapStatus.Pods
|
||||
|
||||
logger.Log.Infof("[Status] POST request: %d tapped pods", len(requestTappedPods))
|
||||
tappedPods.Set(requestTappedPods)
|
||||
broadcastTappedPodsStatus()
|
||||
}
|
||||
|
||||
func broadcastTappedPodsStatus() {
|
||||
tappedPodsStatus := utils.GetTappedPodsStatus()
|
||||
tappedPodsStatus := tappedPods.GetTappedPodsStatus()
|
||||
|
||||
message := shared.CreateWebSocketStatusMessage(tappedPodsStatus)
|
||||
if jsonBytes, err := json.Marshal(message); err != nil {
|
||||
@@ -54,14 +53,6 @@ func broadcastTappedPodsStatus() {
|
||||
}
|
||||
}
|
||||
|
||||
func addTapperStatus(tapperStatus shared.TapperStatus) {
|
||||
if providers.TappersStatus == nil {
|
||||
providers.TappersStatus = make(map[string]shared.TapperStatus)
|
||||
}
|
||||
|
||||
providers.TappersStatus[tapperStatus.NodeName] = tapperStatus
|
||||
}
|
||||
|
||||
func PostTapperStatus(c *gin.Context) {
|
||||
tapperStatus := &shared.TapperStatus{}
|
||||
if err := c.Bind(tapperStatus); err != nil {
|
||||
@@ -75,12 +66,12 @@ func PostTapperStatus(c *gin.Context) {
|
||||
}
|
||||
|
||||
logger.Log.Infof("[Status] POST request, tapper status: %v", tapperStatus)
|
||||
addTapperStatus(*tapperStatus)
|
||||
tappersStatus.Set(tapperStatus)
|
||||
broadcastTappedPodsStatus()
|
||||
}
|
||||
|
||||
func GetTappersCount(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, providers.TappersCount)
|
||||
c.JSON(http.StatusOK, tappersCount.Get())
|
||||
}
|
||||
|
||||
func GetAuthStatus(c *gin.Context) {
|
||||
@@ -94,7 +85,7 @@ func GetAuthStatus(c *gin.Context) {
|
||||
}
|
||||
|
||||
func GetTappingStatus(c *gin.Context) {
|
||||
tappedPodsStatus := utils.GetTappedPodsStatus()
|
||||
tappedPodsStatus := tappedPods.GetTappedPodsStatus()
|
||||
c.JSON(http.StatusOK, tappedPodsStatus)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/up9inc/mizu/tap"
|
||||
)
|
||||
|
||||
func GetEntry(r *tapApi.MizuEntry, v tapApi.DataUnmarshaler) error {
|
||||
func GetEntry(r *tapApi.Entry, v tapApi.DataUnmarshaler) error {
|
||||
return v.UnmarshalData(r)
|
||||
}
|
||||
|
||||
@@ -28,6 +28,10 @@ type EntriesRequest struct {
|
||||
TimeoutMs int `form:"timeoutMs" validate:"min=1"`
|
||||
}
|
||||
|
||||
type SingleEntryRequest struct {
|
||||
Query string `form:"query"`
|
||||
}
|
||||
|
||||
type EntriesResponse struct {
|
||||
Data []interface{} `json:"data"`
|
||||
Meta *basenine.Metadata `json:"meta"`
|
||||
@@ -35,7 +39,7 @@ type EntriesResponse struct {
|
||||
|
||||
type WebSocketEntryMessage struct {
|
||||
*shared.WebSocketMessageMetadata
|
||||
Data map[string]interface{} `json:"data,omitempty"`
|
||||
Data *tapApi.BaseEntry `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type WebSocketTappedEntryMessage struct {
|
||||
@@ -74,7 +78,7 @@ type WebSocketStartTimeMessage struct {
|
||||
Data int64 `json:"data"`
|
||||
}
|
||||
|
||||
func CreateBaseEntryWebSocketMessage(base map[string]interface{}) ([]byte, error) {
|
||||
func CreateBaseEntryWebSocketMessage(base *tapApi.BaseEntry) ([]byte, error) {
|
||||
message := &WebSocketEntryMessage{
|
||||
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
|
||||
MessageType: shared.WebSocketMessageTypeEntry,
|
||||
|
||||
@@ -8,19 +8,14 @@ import (
|
||||
"github.com/up9inc/mizu/tap"
|
||||
"mizuserver/pkg/models"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const tlsLinkRetainmentTime = time.Minute * 15
|
||||
|
||||
var (
|
||||
TappersCount int
|
||||
TapStatus shared.TapStatus
|
||||
TappersStatus map[string]shared.TapperStatus
|
||||
authStatus *models.AuthStatus
|
||||
RecentTLSLinks = cache.New(tlsLinkRetainmentTime, tlsLinkRetainmentTime)
|
||||
tappersCountLock = sync.Mutex{}
|
||||
authStatus *models.AuthStatus
|
||||
RecentTLSLinks = cache.New(tlsLinkRetainmentTime, tlsLinkRetainmentTime)
|
||||
)
|
||||
|
||||
func GetAuthStatus() (*models.AuthStatus, error) {
|
||||
@@ -68,15 +63,3 @@ func GetAllRecentTLSAddresses() []string {
|
||||
|
||||
return recentTLSLinks
|
||||
}
|
||||
|
||||
func TapperAdded() {
|
||||
tappersCountLock.Lock()
|
||||
TappersCount++
|
||||
tappersCountLock.Unlock()
|
||||
}
|
||||
|
||||
func TapperRemoved() {
|
||||
tappersCountLock.Lock()
|
||||
TappersCount--
|
||||
tappersCountLock.Unlock()
|
||||
}
|
||||
|
||||
54
agent/pkg/providers/tapConfig/tap_config_provider.go
Normal file
54
agent/pkg/providers/tapConfig/tap_config_provider.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package tapConfig
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"github.com/up9inc/mizu/shared/logger"
|
||||
"io/ioutil"
|
||||
"mizuserver/pkg/models"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const FilePath = shared.DataDirPath + "tap-config.json"
|
||||
|
||||
var lock = &sync.Mutex{}
|
||||
|
||||
var config *models.TapConfig
|
||||
|
||||
func Get() *models.TapConfig {
|
||||
if config == nil {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
if config == nil {
|
||||
if content, err := ioutil.ReadFile(FilePath); err != nil {
|
||||
config = &models.TapConfig{TappedNamespaces: make(map[string]bool)}
|
||||
if !os.IsNotExist(err) {
|
||||
logger.Log.Errorf("Error loading tap config from file, err: %v", err)
|
||||
}
|
||||
} else {
|
||||
if err = json.Unmarshal(content, &config); err != nil {
|
||||
config = &models.TapConfig{TappedNamespaces: make(map[string]bool)}
|
||||
logger.Log.Errorf("Error while unmarshal tap config, err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func Save(tapConfigToSave *models.TapConfig) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
config = tapConfigToSave
|
||||
if data, err := json.Marshal(config); err != nil {
|
||||
logger.Log.Errorf("Error while marshal tap config, err: %v", err)
|
||||
} else {
|
||||
if err := ioutil.WriteFile(FilePath, data, 0644); err != nil {
|
||||
logger.Log.Errorf("Error writing tap config to file, err: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
32
agent/pkg/providers/tappedPods/tapped_pods_provider.go
Normal file
32
agent/pkg/providers/tappedPods/tapped_pods_provider.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package tappedPods
|
||||
|
||||
import (
|
||||
"github.com/up9inc/mizu/shared"
|
||||
"mizuserver/pkg/providers/tappersStatus"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var tappedPods []*shared.PodInfo
|
||||
|
||||
func Get() []*shared.PodInfo {
|
||||
return tappedPods
|
||||
}
|
||||
|
||||
func Set(tappedPodsToSet []*shared.PodInfo) {
|
||||
tappedPods = tappedPodsToSet
|
||||
}
|
||||
|
||||
func GetTappedPodsStatus() []shared.TappedPodStatus {
|
||||
tappedPodsStatus := make([]shared.TappedPodStatus, 0)
|
||||
for _, pod := range Get() {
|
||||
var status string
|
||||
if tapperStatus, ok := tappersStatus.Get()[pod.NodeName]; ok {
|
||||
status = strings.ToLower(tapperStatus.Status)
|
||||
}
|
||||
|
||||
isTapped := status == "running"
|
||||
tappedPodsStatus = append(tappedPodsStatus, shared.TappedPodStatus{Name: pod.Name, Namespace: pod.Namespace, IsTapped: isTapped})
|
||||
}
|
||||
|
||||
return tappedPodsStatus
|
||||
}
|
||||
25
agent/pkg/providers/tappersCount/tappers_count_provider.go
Normal file
25
agent/pkg/providers/tappersCount/tappers_count_provider.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package tappersCount
|
||||
|
||||
import "sync"
|
||||
|
||||
var lock = &sync.Mutex{}
|
||||
|
||||
var tappersCount int
|
||||
|
||||
func Add() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
tappersCount++
|
||||
}
|
||||
|
||||
func Remove() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
tappersCount--
|
||||
}
|
||||
|
||||
func Get() int {
|
||||
return tappersCount
|
||||
}
|
||||
25
agent/pkg/providers/tappersStatus/tappers_status_provider.go
Normal file
25
agent/pkg/providers/tappersStatus/tappers_status_provider.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package tappersStatus
|
||||
|
||||
import "github.com/up9inc/mizu/shared"
|
||||
|
||||
var tappersStatus map[string]*shared.TapperStatus
|
||||
|
||||
func Get() map[string]*shared.TapperStatus {
|
||||
if tappersStatus == nil {
|
||||
tappersStatus = make(map[string]*shared.TapperStatus)
|
||||
}
|
||||
|
||||
return tappersStatus
|
||||
}
|
||||
|
||||
func Set(tapperStatus *shared.TapperStatus) {
|
||||
if tappersStatus == nil {
|
||||
tappersStatus = make(map[string]*shared.TapperStatus)
|
||||
}
|
||||
|
||||
tappersStatus[tapperStatus.NodeName] = tapperStatus
|
||||
}
|
||||
|
||||
func Reset() {
|
||||
tappersStatus = make(map[string]*shared.TapperStatus)
|
||||
}
|
||||
@@ -243,7 +243,7 @@ func syncEntriesImpl(token string, model string, envPrefix string, uploadInterva
|
||||
var dataMap map[string]interface{}
|
||||
err = json.Unmarshal(dataBytes, &dataMap)
|
||||
|
||||
var entry tapApi.MizuEntry
|
||||
var entry tapApi.Entry
|
||||
if err := json.Unmarshal([]byte(dataBytes), &entry); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -3,12 +3,10 @@ package utils
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"mizuserver/pkg/providers"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -45,16 +43,6 @@ func StartServer(app *gin.Engine) {
|
||||
}
|
||||
}
|
||||
|
||||
func GetTappedPodsStatus() []shared.TappedPodStatus {
|
||||
tappedPodsStatus := make([]shared.TappedPodStatus, 0)
|
||||
for _, pod := range providers.TapStatus.Pods {
|
||||
status := strings.ToLower(providers.TappersStatus[pod.NodeName].Status)
|
||||
isTapped := status == "running"
|
||||
tappedPodsStatus = append(tappedPodsStatus, shared.TappedPodStatus{Name: pod.Name, Namespace: pod.Namespace, IsTapped: isTapped})
|
||||
}
|
||||
return tappedPodsStatus
|
||||
}
|
||||
|
||||
func CheckErr(e error) {
|
||||
if e != nil {
|
||||
logger.Log.Errorf("%v", e)
|
||||
|
||||
@@ -14,8 +14,12 @@ help: ## This help.
|
||||
install:
|
||||
go install mizu.go
|
||||
|
||||
build-debug:
|
||||
export GCLFAGS='-gcflags="all=-N -l"'
|
||||
${MAKE} build
|
||||
|
||||
build: ## Build mizu CLI binary (select platform via GOOS / GOARCH env variables).
|
||||
go build -ldflags="-X 'github.com/up9inc/mizu/cli/mizu.GitCommitHash=$(COMMIT_HASH)' \
|
||||
go build ${GCLFAGS} -ldflags="-X 'github.com/up9inc/mizu/cli/mizu.GitCommitHash=$(COMMIT_HASH)' \
|
||||
-X 'github.com/up9inc/mizu/cli/mizu.Branch=$(GIT_BRANCH)' \
|
||||
-X 'github.com/up9inc/mizu/cli/mizu.BuildTimestamp=$(BUILD_TIMESTAMP)' \
|
||||
-X 'github.com/up9inc/mizu/cli/mizu.Platform=$(SUFFIX)' \
|
||||
|
||||
@@ -87,9 +87,8 @@ func (provider *Provider) ReportTappedPods(pods []core.Pod) error {
|
||||
tappedPodsUrl := fmt.Sprintf("%s/status/tappedPods", provider.url)
|
||||
|
||||
podInfos := kubernetes.GetPodInfosForPods(pods)
|
||||
tapStatus := shared.TapStatus{Pods: podInfos}
|
||||
|
||||
if jsonValue, err := json.Marshal(tapStatus); err != nil {
|
||||
if jsonValue, err := json.Marshal(podInfos); err != nil {
|
||||
return fmt.Errorf("failed Marshal the tapped pods %w", err)
|
||||
} else {
|
||||
if response, err := provider.client.Post(tappedPodsUrl, "application/json", bytes.NewBuffer(jsonValue)); err != nil {
|
||||
|
||||
@@ -89,6 +89,8 @@ func getInstallMizuAgentConfig(maxDBSizeBytes int64, tapperResources shared.Reso
|
||||
MizuResourcesNamespace: config.Config.MizuResourcesNamespace,
|
||||
AgentDatabasePath: shared.DataDirPath,
|
||||
StandaloneMode: true,
|
||||
ServiceMap: config.Config.ServiceMap,
|
||||
OAS: config.Config.OAS,
|
||||
}
|
||||
|
||||
return &mizuAgentConfig
|
||||
@@ -99,7 +101,7 @@ func watchApiServerPodReady(ctx context.Context, kubernetesProvider *kubernetes.
|
||||
podWatchHelper := kubernetes.NewPodWatchHelper(kubernetesProvider, podExactRegex)
|
||||
eventChan, errorChan := kubernetes.FilteredWatch(ctx, podWatchHelper, []string{config.Config.MizuResourcesNamespace}, podWatchHelper)
|
||||
|
||||
timeAfter := time.After(30 * time.Second)
|
||||
timeAfter := time.After(1 * time.Minute)
|
||||
for {
|
||||
select {
|
||||
case wEvent, ok := <-eventChan:
|
||||
|
||||
@@ -156,6 +156,8 @@ func getTapMizuAgentConfig() *shared.MizuAgentConfig {
|
||||
TapperResources: config.Config.Tap.TapperResources,
|
||||
MizuResourcesNamespace: config.Config.MizuResourcesNamespace,
|
||||
AgentDatabasePath: shared.DataDirPath,
|
||||
ServiceMap: config.Config.ServiceMap,
|
||||
OAS: config.Config.OAS,
|
||||
}
|
||||
|
||||
return &mizuAgentConfig
|
||||
|
||||
@@ -34,9 +34,11 @@ type ConfigStruct struct {
|
||||
ConfigFilePath string `yaml:"config-path,omitempty" readonly:""`
|
||||
HeadlessMode bool `yaml:"headless" default:"false"`
|
||||
LogLevelStr string `yaml:"log-level,omitempty" default:"INFO" readonly:""`
|
||||
ServiceMap bool `yaml:"service-map" default:"false" readonly:""`
|
||||
OAS bool `yaml:"oas" default:"false" readonly:""`
|
||||
}
|
||||
|
||||
func(config *ConfigStruct) validate() error {
|
||||
func (config *ConfigStruct) validate() error {
|
||||
if _, err := logging.LogLevel(config.LogLevelStr); err != nil {
|
||||
return fmt.Errorf("%s is not a valid log level, err: %v", config.LogLevelStr, err)
|
||||
}
|
||||
|
||||
23
deploy/kubernetes/helm-chart/.helmignore
Normal file
23
deploy/kubernetes/helm-chart/.helmignore
Normal file
@@ -0,0 +1,23 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
7
deploy/kubernetes/helm-chart/Chart.yaml
Normal file
7
deploy/kubernetes/helm-chart/Chart.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
apiVersion: v2
|
||||
name: mizuhelm
|
||||
description: Mizu helm chart for Kubernetes
|
||||
type: application
|
||||
version: 0.1.1
|
||||
kubeVersion: ">= 1.16.0-0"
|
||||
appVersion: "0.21.29"
|
||||
@@ -0,0 +1,13 @@
|
||||
kind: PersistentVolumeClaim
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: {{ .Values.volumeClaim.name }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
limits:
|
||||
storage: 700M
|
||||
requests:
|
||||
storage: 700M
|
||||
30
deploy/kubernetes/helm-chart/templates/clusterRole.yaml
Normal file
30
deploy/kubernetes/helm-chart/templates/clusterRole.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
{{- if .Values.rbac.create -}}
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: {{ .Values.rbac.name }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
mizu-cli-version: {{ .Chart.AppVersion }}
|
||||
heritage: {{ .Release.Service }}
|
||||
release: {{ .Release.Name }}
|
||||
rules:
|
||||
- apiGroups: [ "", "extensions", "apps" ]
|
||||
resources: [ "endpoints", "pods", "services", "namespaces" ]
|
||||
verbs: [ "get", "list", "watch" ]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: {{ .Values.rbac.roleBindingName }}
|
||||
labels:
|
||||
mizu-cli-version: {{ .Chart.AppVersion }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: {{ .Values.rbac.name }}
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ .Values.serviceAccountName }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
{{- end -}}
|
||||
8
deploy/kubernetes/helm-chart/templates/configmap.yaml
Normal file
8
deploy/kubernetes/helm-chart/templates/configmap.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ .Values.configMap.name }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
mizu-config.json: >-
|
||||
{"maxDBSizeBytes":200000000,"agentImage":"{{ .Values.container.tapper.image.repository }}:{{ .Values.container.tapper.image.tag }}","pullPolicy":"Always","logLevel":4,"tapperResources":{"CpuLimit":"750m","MemoryLimit":"1Gi","CpuRequests":"50m","MemoryRequests":"50Mi"},"mizuResourceNamespace":"{{ .Release.Namespace }}","agentDatabasePath":"/app/data/","standaloneMode":true}
|
||||
128
deploy/kubernetes/helm-chart/templates/deployment.yaml
Normal file
128
deploy/kubernetes/helm-chart/templates/deployment.yaml
Normal file
@@ -0,0 +1,128 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ .Values.pod.name }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
app: {{ .Values.pod.name }}
|
||||
spec:
|
||||
replicas: {{ .Values.deployment.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ .Values.pod.name }}
|
||||
template:
|
||||
metadata:
|
||||
name: {{ .Values.pod.name }}
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app: {{ .Values.pod.name }}
|
||||
spec:
|
||||
volumes:
|
||||
- name: {{ .Values.configMap.name }}
|
||||
configMap:
|
||||
name: {{ .Values.configMap.name }}
|
||||
defaultMode: 420
|
||||
- name: {{ .Values.volumeClaim.name }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ .Values.volumeClaim.name }}
|
||||
containers:
|
||||
- name: {{ .Values.pod.name }}
|
||||
image: "{{ .Values.container.mizuAgent.image.repository }}:{{ .Values.container.mizuAgent.image.tag | default .Chart.AppVersion }}"
|
||||
command:
|
||||
- ./mizuagent
|
||||
- '--api-server'
|
||||
env:
|
||||
- name: SYNC_ENTRIES_CONFIG
|
||||
- name: LOG_LEVEL
|
||||
value: INFO
|
||||
resources:
|
||||
limits:
|
||||
cpu: 750m
|
||||
memory: 1Gi
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 50Mi
|
||||
volumeMounts:
|
||||
- name: {{ .Values.configMap.name }}
|
||||
mountPath: /app/config/
|
||||
- name: {{ .Values.volumeClaim.name }}
|
||||
mountPath: /app/data/
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /echo
|
||||
port: {{ .Values.pod.port }}
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 1
|
||||
timeoutSeconds: 1
|
||||
periodSeconds: 10
|
||||
successThreshold: 1
|
||||
failureThreshold: 3
|
||||
terminationMessagePath: /dev/termination-log
|
||||
terminationMessagePolicy: File
|
||||
imagePullPolicy: Always
|
||||
- name: {{ .Values.container.basenine.name }}
|
||||
image: "{{ .Values.container.basenine.image.repository }}:{{ .Values.container.basenine.image.tag | default .Chart.AppVersion }}"
|
||||
command:
|
||||
- /basenine
|
||||
args:
|
||||
- '-addr'
|
||||
- 0.0.0.0
|
||||
- '-port'
|
||||
- '9099'
|
||||
- '-persistent'
|
||||
workingDir: /app/data/
|
||||
resources:
|
||||
limits:
|
||||
cpu: 750m
|
||||
memory: 1Gi
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 50Mi
|
||||
volumeMounts:
|
||||
- name: {{ .Values.configMap.name }}
|
||||
mountPath: /app/config/
|
||||
- name: {{ .Values.volumeClaim.name }}
|
||||
mountPath: /app/data/
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: 9099
|
||||
timeoutSeconds: 1
|
||||
periodSeconds: 1
|
||||
successThreshold: 1
|
||||
failureThreshold: 3
|
||||
terminationMessagePath: /dev/termination-log
|
||||
terminationMessagePolicy: File
|
||||
imagePullPolicy: Always
|
||||
- name: kratos
|
||||
image: "{{ .Values.container.kratos.image.repository }}:{{ .Values.container.kratos.image.tag | default .Chart.AppVersion }}"
|
||||
resources:
|
||||
limits:
|
||||
cpu: 750m
|
||||
memory: 1Gi
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 50Mi
|
||||
volumeMounts:
|
||||
- name: {{ .Values.configMap.name }}
|
||||
mountPath: /app/config/
|
||||
- name: {{ .Values.volumeClaim.name }}
|
||||
mountPath: /app/data/
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health/ready
|
||||
port: 4433
|
||||
scheme: HTTP
|
||||
timeoutSeconds: 1
|
||||
periodSeconds: 1
|
||||
successThreshold: 1
|
||||
failureThreshold: 3
|
||||
terminationMessagePath: /dev/termination-log
|
||||
terminationMessagePolicy: File
|
||||
imagePullPolicy: Always
|
||||
restartPolicy: Always
|
||||
terminationGracePeriodSeconds: 0
|
||||
dnsPolicy: ClusterFirstWithHostNet
|
||||
serviceAccountName: {{ .Values.serviceAccountName }}
|
||||
serviceAccount: {{ .Values.serviceAccountName }}
|
||||
securityContext: { }
|
||||
schedulerName: default-scheduler
|
||||
29
deploy/kubernetes/helm-chart/templates/role.yaml
Normal file
29
deploy/kubernetes/helm-chart/templates/role.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: {{ .Values.roleName }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
mizu-cli-version: {{ .Chart.AppVersion }}
|
||||
rules:
|
||||
- apiGroups: [ "apps" ]
|
||||
resources: [ "daemonsets" ]
|
||||
verbs: [ "patch", "get", "list", "create", "delete" ]
|
||||
- apiGroups: [ "events.k8s.i" ]
|
||||
resources: [ "events" ]
|
||||
verbs: [ "list", "watch" ]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: {{ .Values.roleBindingName }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: {{ .Values.roleName }}
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ .Values.serviceAccountName }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
---
|
||||
14
deploy/kubernetes/helm-chart/templates/service.yaml
Normal file
14
deploy/kubernetes/helm-chart/templates/service.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ .Values.service.name }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- name: api
|
||||
port: {{ .Values.service.port }}
|
||||
targetPort: {{ .Values.pod.port }}
|
||||
protocol: TCP
|
||||
selector:
|
||||
app: {{ .Values.pod.name }}
|
||||
@@ -0,0 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ .Values.serviceAccountName }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
mizu-cli-version: {{ .Chart.AppVersion }}
|
||||
51
deploy/kubernetes/helm-chart/values.yaml
Normal file
51
deploy/kubernetes/helm-chart/values.yaml
Normal file
@@ -0,0 +1,51 @@
|
||||
# Default values for mizu.
|
||||
rbac:
|
||||
create: true
|
||||
name: "mizu-cluster-role"
|
||||
roleBindingName: "mizu-role-binding"
|
||||
|
||||
serviceAccountName: "mizu-service-account"
|
||||
|
||||
roleName: "mizu-role-daemon"
|
||||
roleBindingName: "mizu-role-binding-daemon"
|
||||
|
||||
service:
|
||||
name: "mizu-api-server"
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
|
||||
pod:
|
||||
name: "mizu-api-server"
|
||||
port: 8899
|
||||
|
||||
container:
|
||||
mizuAgent:
|
||||
image:
|
||||
repository: "709825985650.dkr.ecr.us-east-1.amazonaws.com/up9/mizufree"
|
||||
tag: "0.21.29"
|
||||
tapper:
|
||||
image:
|
||||
repository: "709825985650.dkr.ecr.us-east-1.amazonaws.com/up9/mizufree"
|
||||
tag: "0.21.29"
|
||||
basenine:
|
||||
name: "basenine"
|
||||
port: 9099
|
||||
image:
|
||||
repository: "709825985650.dkr.ecr.us-east-1.amazonaws.com/up9/basenine"
|
||||
tag: "v0.3.0"
|
||||
kratos:
|
||||
name: "kratos"
|
||||
port: 4433
|
||||
image:
|
||||
repository: "709825985650.dkr.ecr.us-east-1.amazonaws.com/up9/kratos"
|
||||
tag: "0.0.0"
|
||||
|
||||
deployment:
|
||||
replicaCount: 1
|
||||
|
||||
configMap:
|
||||
name: "mizu-config"
|
||||
|
||||
volumeClaim:
|
||||
create: true
|
||||
name: "mizu-volume-claim"
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
for f in tap/extensions/*; do
|
||||
if [ -d "$f" ]; then
|
||||
echo Building extension: $f
|
||||
extension=$(basename $f) && \
|
||||
cd tap/extensions/${extension} && \
|
||||
go build -buildmode=plugin -o ../${extension}.so . && \
|
||||
|
||||
@@ -1,16 +1,92 @@
|
||||

|
||||
|
||||
# Kubernetes permissions for MIZU
|
||||
|
||||
This document describes in details all permissions required for full and correct operation of Mizu
|
||||
This document describes in details all permissions required for full and correct operation of Mizu.
|
||||
|
||||
## Editting permissions
|
||||
|
||||
During installation, Mizu creates a `ServiceAccount` and the roles it requires. No further action is required.
|
||||
However, if there is a need, it is possible to make changes to Mizu permissions.
|
||||
|
||||
### Adding permissions on top of Mizu's defaults
|
||||
|
||||
Mizu pods use the `ServiceAccount` `mizu-service-account`. Permissions can be added to Mizu by creating `ClusterRoleBindings` and `RoleBindings` that target that `ServiceAccount`.
|
||||
|
||||
For example, in order to add a `PodSecurityPolicy` which allows Mizu to run `hostNetwork` and `privileged` pods, create the following resources:
|
||||
|
||||
```yaml
|
||||
apiVersion: policy/v1beta1
|
||||
kind: PodSecurityPolicy
|
||||
metadata:
|
||||
name: my-mizu-psp
|
||||
spec:
|
||||
hostNetwork: true
|
||||
privileged: true
|
||||
allowedCapabilities:
|
||||
- "*"
|
||||
fsGroup:
|
||||
rule: RunAsAny
|
||||
runAsUser:
|
||||
rule: RunAsAny
|
||||
seLinux:
|
||||
rule: RunAsAny
|
||||
supplementalGroups:
|
||||
rule: RunAsAny
|
||||
volumes:
|
||||
- "*"
|
||||
---
|
||||
kind: ClusterRole
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: my-mizu-clusterrole
|
||||
rules:
|
||||
- apiGroups:
|
||||
- policy
|
||||
resources:
|
||||
- podsecuritypolicies
|
||||
verbs:
|
||||
- use
|
||||
resourceNames:
|
||||
- my-mizu-psp
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: my-mizu-clusterrolebinding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: my-mizu-clusterrole
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: mizu-service-account # The service account used by Mizu
|
||||
namespace: mizu
|
||||
```
|
||||
|
||||
With this setup, when Mizu starts and creates `mizu-service-account`, this account will be subject to `my-mizu-psp` via `my-mizu-clusterrolebinding`.
|
||||
When Mizu cleans up resources, the above resources will remain available for future executions.
|
||||
|
||||
### Replacing Mizu's default permissions with custom permissions
|
||||
|
||||
Mizu does not create its `ServiceAccounts`, `ClusterRoles`, `ClusterRoleBindings`, `Roles` or `RoleBindings` if resources by the same name already exist. In order to replace Mizu's defaults, simply create your resources before running Mizu.
|
||||
|
||||
For example, creating a `ClusterRole` by the name of `mizu-cluster-role` before running Mizu will cause Mizu to use that `ClusterRole` instead of the default one created by Mizu.
|
||||
|
||||
Notes:
|
||||
|
||||
1. The resource names must match Mizu's default names.
|
||||
2. User-managed resources must not have the label `app.kubernetes.io/managed-by=mizu`. Remove the label or set it to another value.
|
||||
|
||||
## List of permissions
|
||||
|
||||
We broke down this list into few categories:
|
||||
|
||||
- Required - what is needed for `mizu` to run properly on your k8s cluster
|
||||
- Optional - permissions needed for proper name resolving for service & pod IPs
|
||||
- addition required for policy validation
|
||||
|
||||
- Optional - permissions needed for proper name resolving for service & pod IPs
|
||||
- addition required for policy validation
|
||||
|
||||
|
||||
# Required permissions
|
||||
### Required permissions
|
||||
|
||||
Mizu needs following permissions on your Kubernetes cluster to run properly
|
||||
|
||||
@@ -57,7 +133,7 @@ Mizu needs following permissions on your Kubernetes cluster to run properly
|
||||
- get
|
||||
```
|
||||
|
||||
## Permissions required running with install command or (optional) for service / pod name resolving
|
||||
#### Permissions required running with install command or (optional) for service / pod name resolving
|
||||
|
||||
Mandatory permissions for running with install command.
|
||||
|
||||
@@ -178,7 +254,7 @@ Optional for service/pod name resolving in non install standalone
|
||||
- watch
|
||||
```
|
||||
|
||||
## Permissions for Policy rules validation feature (opt)
|
||||
#### Permissions for Policy rules validation feature (opt)
|
||||
|
||||
Optionally, in order to use the policy rules validation feature, Mizu requires the following additional permissions:
|
||||
|
||||
@@ -195,7 +271,7 @@ Optionally, in order to use the policy rules validation feature, Mizu requires t
|
||||
|
||||
- - -
|
||||
|
||||
## Namespace-Restricted mode
|
||||
#### Namespace-Restricted mode
|
||||
|
||||
Alternatively, in order to restrict Mizu to one namespace only (by setting `agent.namespace` in the config file), Mizu needs the following permissions in that namespace:
|
||||
|
||||
@@ -235,7 +311,7 @@ Alternatively, in order to restrict Mizu to one namespace only (by setting `agen
|
||||
- get
|
||||
```
|
||||
|
||||
### Name resolving in Namespace-Restricted mode (opt)
|
||||
##### Name resolving in Namespace-Restricted mode (opt)
|
||||
|
||||
To restrict Mizu to one namespace while also resolving IPs, Mizu needs the following permissions in that namespace:
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||

|
||||
|
||||
# Service mesh mutual tls (mtls) with Mizu
|
||||
|
||||
This document describe how Mizu tapper handles workloads configured with mtls, making the internal traffic between services in a cluster to be encrypted.
|
||||
|
||||
The list of service meshes supported by Mizu include:
|
||||
@@ -7,38 +9,67 @@ The list of service meshes supported by Mizu include:
|
||||
- Istio
|
||||
- Linkerd
|
||||
|
||||
In order to create a service mesh setup for development, follow those steps:
|
||||
1. Deploy a sample application to a Kubernetes cluster, the sample application needs to make internal service to service calls
|
||||
2. SSH to one of the nodes, and run `tcpdump`
|
||||
3. Make sure you see the internal service to service calls in a plain text
|
||||
4. Deploy a service mesh (Istio, Linkerd) to the cluster - make sure it is attached to all pods of the sample application, and that it is configured with mtls (default)
|
||||
5. Run `tcpdump` again, make sure you don't see the internal service to service calls in a plain text
|
||||
## Installation
|
||||
|
||||
### Optional: Allow source IP resolving in Istio
|
||||
|
||||
When using Istio, in order to enable Mizu to reslove source IPs to names, turn on the [use_remote_address](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-for) option in Istio sidecar Envoys.
|
||||
This setting causes the Envoys to append to `X-Forwarded-For` request header. Mizu in turn uses the `X-Forwarded-For` header to determine the true source IPs.
|
||||
One way to turn on the `use_remote_address` HTTP connection manager option is by applying an `EnvoyFilter`:
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.istio.io/v1alpha3
|
||||
kind: EnvoyFilter
|
||||
metadata:
|
||||
name: mizu-xff
|
||||
namespace: istio-system # as defined in meshConfig resource.
|
||||
spec:
|
||||
configPatches:
|
||||
- applyTo: NETWORK_FILTER
|
||||
match:
|
||||
context: SIDECAR_OUTBOUND # will match outbound listeners in all sidecars
|
||||
patch:
|
||||
operation: MERGE
|
||||
value:
|
||||
typed_config:
|
||||
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"
|
||||
use_remote_address: true
|
||||
```
|
||||
|
||||
Save the above text to `mizu-xff-envoyfilter.yaml` and run `kubectl apply -f mizu-xff-envoyfilter.yaml`.
|
||||
|
||||
With Istio, mizu does not resolve source IPs for non-HTTP traffic.
|
||||
|
||||
## Implementation
|
||||
|
||||
### Istio support
|
||||
|
||||
#### The connection between Istio and Envoy
|
||||
|
||||
In order to implement its service mesh capabilities, [Istio](https://istio.io) uses an [Envoy](https://www.envoyproxy.io) sidecar in front of every pod in the cluster. The Envoy is responsible for the mtls communication, and that's why we are focusing on Envoy proxy.
|
||||
|
||||
In the future we might see more players in that field, then we'll have to either add support for each of them or go with a unified eBPF solution.
|
||||
|
||||
#### Network namespaces
|
||||
|
||||
A [linux network namespace](https://man7.org/linux/man-pages/man7/network_namespaces.7.html) is an isolation that limit the process view of the network. In the container world it used to isolate one container from another. In the Kubernetes world it used to isolate a pod from another. That means that two containers running on the same pod share the same network namespace. A container can reach a container in the same pod by accessing `localhost`.
|
||||
|
||||
An Envoy proxy configured with mtls receives the inbound traffic directed to the pod, decrypts it and sends it via `localhost` to the target container.
|
||||
|
||||
#### Tapping mtls traffic
|
||||
In order for Mizu to be able to see the decrypted traffic it needs to listen on the same network namespace of the target pod. Multiple threads of the same process can have different network namespaces.
|
||||
|
||||
In order for Mizu to be able to see the decrypted traffic it needs to listen on the same network namespace of the target pod. Multiple threads of the same process can have different network namespaces.
|
||||
|
||||
[gopacket](https://github.com/google/gopacket) uses [libpacp](https://github.com/the-tcpdump-group/libpcap) by default for capturing the traffic. Libpacap doesn't support network namespaces and we can't ask it to listen to traffic on a different namespace. However, we can change the network namespace of the calling thread and then start libpcap to see the traffic on a different namespace.
|
||||
|
||||
#### Finding the network namespace of a running process
|
||||
|
||||
The network namespace of a running process can be found in `/proc/PID/ns/net` link. Once we have this link, we can ask Linux to change the network namespace of a thread to this one.
|
||||
|
||||
This mean that Mizu needs to have access to the `/proc` (procfs) of the running node.
|
||||
|
||||
#### Finding the network namespace of a running pod
|
||||
|
||||
In order for Mizu to be able to listen to mtls traffic, it needs to get the PIDs of the the running pods, filter them according to the user filters and then start listen to their internal network namespace traffic.
|
||||
|
||||
There is no official way in Kubernetes to get from pod to PID. The CRI implementation purposefully doesn't force a pod to be a processes on the host. It can be a Virtual Machine as well like [Kata containers](https://katacontainers.io)
|
||||
@@ -50,4 +81,15 @@ Once Mizu detects an Envoy process, it need to check whether this specific Envoy
|
||||
Istio sends an `INSTANCE_IP` environment variable to every Envoy proxy process. By examining the Envoy process's environment variables we can see whether it's relevant or not. Examining a process environment variables is done by reading the `/proc/PID/envion` file.
|
||||
|
||||
#### Edge cases
|
||||
|
||||
The method we use to find Envoy processes and correlate them to the cluster IPs may be inaccurate in certain situations. If, for example, a user runs an Envoy process manually, and set its `INSTANCE_IP` environment variable to one of the `CLUSTER_IPS` the tapper gets, then Mizu will capture traffic for it.
|
||||
|
||||
## Development
|
||||
|
||||
In order to create a service mesh setup for development, follow those steps:
|
||||
|
||||
1. Deploy a sample application to a Kubernetes cluster, the sample application needs to make internal service to service calls
|
||||
2. SSH to one of the nodes, and run `tcpdump`
|
||||
3. Make sure you see the internal service to service calls in a plain text
|
||||
4. Deploy a service mesh (Istio, Linkerd) to the cluster - make sure it is attached to all pods of the sample application, and that it is configured with mtls (default)
|
||||
5. Run `tcpdump` again, make sure you don't see the internal service to service calls in a plain text
|
||||
|
||||
@@ -17,5 +17,5 @@ const (
|
||||
BasenineHost = "127.0.0.1"
|
||||
BaseninePort = "9099"
|
||||
BasenineImageRepo = "ghcr.io/up9inc/basenine"
|
||||
BasenineImageTag = "v0.2.26"
|
||||
BasenineImageTag = "v0.3.0"
|
||||
)
|
||||
|
||||
@@ -73,10 +73,10 @@ func getMissingPods(pods1 []core.Pod, pods2 []core.Pod) []core.Pod {
|
||||
return missingPods
|
||||
}
|
||||
|
||||
func GetPodInfosForPods(pods []core.Pod) []shared.PodInfo {
|
||||
podInfos := make([]shared.PodInfo, 0)
|
||||
func GetPodInfosForPods(pods []core.Pod) []*shared.PodInfo {
|
||||
podInfos := make([]*shared.PodInfo, 0)
|
||||
for _, pod := range pods {
|
||||
podInfos = append(podInfos, shared.PodInfo{Name: pod.Name, Namespace: pod.Namespace, NodeName: pod.Spec.NodeName})
|
||||
podInfos = append(podInfos, &shared.PodInfo{Name: pod.Name, Namespace: pod.Namespace, NodeName: pod.Spec.NodeName})
|
||||
}
|
||||
return podInfos
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ type MizuAgentConfig struct {
|
||||
MizuResourcesNamespace string `json:"mizuResourceNamespace"`
|
||||
AgentDatabasePath string `json:"agentDatabasePath"`
|
||||
StandaloneMode bool `json:"standaloneMode"`
|
||||
ServiceMap bool `json:"serviceMap"`
|
||||
OAS bool `json:"oas"`
|
||||
}
|
||||
|
||||
type WebSocketMessageMetadata struct {
|
||||
@@ -81,10 +83,6 @@ type TappedPodStatus struct {
|
||||
IsTapped bool `json:"isTapped"`
|
||||
}
|
||||
|
||||
type TapStatus struct {
|
||||
Pods []PodInfo `json:"pods"`
|
||||
}
|
||||
|
||||
type PodInfo struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Name string `json:"name"`
|
||||
@@ -124,9 +122,9 @@ func CreateWebSocketMessageTypeAnalyzeStatus(analyzeStatus AnalyzeStatus) WebSoc
|
||||
}
|
||||
|
||||
type HealthResponse struct {
|
||||
TapStatus TapStatus `json:"tapStatus"`
|
||||
TappersCount int `json:"tappersCount"`
|
||||
TappersStatus []TapperStatus `json:"tappersStatus"`
|
||||
TappedPods []*PodInfo `json:"tappedPods"`
|
||||
TappersCount int `json:"tappersCount"`
|
||||
TappersStatus []*TapperStatus `json:"tappersStatus"`
|
||||
}
|
||||
|
||||
type VersionResponse struct {
|
||||
|
||||
@@ -81,7 +81,7 @@ type OutputChannelItem struct {
|
||||
Timestamp int64
|
||||
ConnectionInfo *ConnectionInfo
|
||||
Pair *RequestResponsePair
|
||||
Summary *BaseEntryDetails
|
||||
Summary *BaseEntry
|
||||
}
|
||||
|
||||
type SuperTimer struct {
|
||||
@@ -97,8 +97,7 @@ type Dissector interface {
|
||||
Register(*Extension)
|
||||
Ping()
|
||||
Dissect(b *bufio.Reader, isClient bool, tcpID *TcpID, counterPair *CounterPair, superTimer *SuperTimer, superIdentifier *SuperIdentifier, emitter Emitter, options *TrafficFilteringOptions) error
|
||||
Analyze(item *OutputChannelItem, resolvedSource string, resolvedDestination string) *MizuEntry
|
||||
Summarize(entry *MizuEntry) *BaseEntryDetails
|
||||
Analyze(item *OutputChannelItem, resolvedSource string, resolvedDestination string) *Entry
|
||||
Represent(request map[string]interface{}, response map[string]interface{}) (object []byte, bodySize int64, err error)
|
||||
Macros() map[string]string
|
||||
}
|
||||
@@ -117,7 +116,7 @@ func (e *Emitting) Emit(item *OutputChannelItem) {
|
||||
e.AppStats.IncMatchedPairs()
|
||||
}
|
||||
|
||||
type MizuEntry struct {
|
||||
type Entry struct {
|
||||
Id uint `json:"id"`
|
||||
Protocol Protocol `json:"proto"`
|
||||
Source *TCP `json:"src"`
|
||||
@@ -127,13 +126,13 @@ type MizuEntry struct {
|
||||
StartTime time.Time `json:"startTime"`
|
||||
Request map[string]interface{} `json:"request"`
|
||||
Response map[string]interface{} `json:"response"`
|
||||
Base *BaseEntryDetails `json:"base"`
|
||||
Summary string `json:"summary"`
|
||||
Method string `json:"method"`
|
||||
Status int `json:"status"`
|
||||
ElapsedTime int64 `json:"elapsedTime"`
|
||||
Path string `json:"path"`
|
||||
IsOutgoing bool `json:"isOutgoing,omitempty"`
|
||||
Rules ApplicableRules `json:"rules,omitempty"`
|
||||
ContractStatus ContractStatus `json:"contractStatus,omitempty"`
|
||||
ContractRequestReason string `json:"contractRequestReason,omitempty"`
|
||||
ContractResponseReason string `json:"contractResponseReason,omitempty"`
|
||||
@@ -141,22 +140,22 @@ type MizuEntry struct {
|
||||
HTTPPair string `json:"httpPair,omitempty"`
|
||||
}
|
||||
|
||||
type MizuEntryWrapper struct {
|
||||
type EntryWrapper struct {
|
||||
Protocol Protocol `json:"protocol"`
|
||||
Representation string `json:"representation"`
|
||||
BodySize int64 `json:"bodySize"`
|
||||
Data MizuEntry `json:"data"`
|
||||
Data *Entry `json:"data"`
|
||||
Rules []map[string]interface{} `json:"rulesMatched,omitempty"`
|
||||
IsRulesEnabled bool `json:"isRulesEnabled"`
|
||||
}
|
||||
|
||||
type BaseEntryDetails struct {
|
||||
type BaseEntry struct {
|
||||
Id uint `json:"id"`
|
||||
Protocol Protocol `json:"protocol,omitempty"`
|
||||
Protocol Protocol `json:"proto,omitempty"`
|
||||
Url string `json:"url,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
StatusCode int `json:"statusCode"`
|
||||
StatusCode int `json:"status"`
|
||||
Method string `json:"method,omitempty"`
|
||||
Timestamp int64 `json:"timestamp,omitempty"`
|
||||
Source *TCP `json:"src"`
|
||||
@@ -182,11 +181,29 @@ type Contract struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type DataUnmarshaler interface {
|
||||
UnmarshalData(*MizuEntry) error
|
||||
func Summarize(entry *Entry) *BaseEntry {
|
||||
return &BaseEntry{
|
||||
Id: entry.Id,
|
||||
Protocol: entry.Protocol,
|
||||
Path: entry.Path,
|
||||
Summary: entry.Summary,
|
||||
StatusCode: entry.Status,
|
||||
Method: entry.Method,
|
||||
Timestamp: entry.Timestamp,
|
||||
Source: entry.Source,
|
||||
Destination: entry.Destination,
|
||||
IsOutgoing: entry.IsOutgoing,
|
||||
Latency: entry.ElapsedTime,
|
||||
Rules: entry.Rules,
|
||||
ContractStatus: entry.ContractStatus,
|
||||
}
|
||||
}
|
||||
|
||||
func (bed *BaseEntryDetails) UnmarshalData(entry *MizuEntry) error {
|
||||
type DataUnmarshaler interface {
|
||||
UnmarshalData(*Entry) error
|
||||
}
|
||||
|
||||
func (bed *BaseEntry) UnmarshalData(entry *Entry) error {
|
||||
bed.Protocol = entry.Protocol
|
||||
bed.Id = entry.Id
|
||||
bed.Path = entry.Path
|
||||
|
||||
@@ -223,7 +223,7 @@ func (d dissecting) Dissect(b *bufio.Reader, isClient bool, tcpID *api.TcpID, co
|
||||
}
|
||||
}
|
||||
|
||||
func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string, resolvedDestination string) *api.MizuEntry {
|
||||
func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string, resolvedDestination string) *api.Entry {
|
||||
request := item.Pair.Request.Payload.(map[string]interface{})
|
||||
reqDetails := request["details"].(map[string]interface{})
|
||||
|
||||
@@ -261,7 +261,7 @@ func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string,
|
||||
|
||||
request["url"] = summary
|
||||
reqDetails["method"] = request["method"]
|
||||
return &api.MizuEntry{
|
||||
return &api.Entry{
|
||||
Protocol: protocol,
|
||||
Source: &api.TCP{
|
||||
Name: resolvedSource,
|
||||
@@ -286,25 +286,6 @@ func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string,
|
||||
|
||||
}
|
||||
|
||||
func (d dissecting) Summarize(entry *api.MizuEntry) *api.BaseEntryDetails {
|
||||
return &api.BaseEntryDetails{
|
||||
Id: entry.Id,
|
||||
Protocol: protocol,
|
||||
Summary: entry.Summary,
|
||||
StatusCode: entry.Status,
|
||||
Method: entry.Method,
|
||||
Timestamp: entry.Timestamp,
|
||||
Source: entry.Source,
|
||||
Destination: entry.Destination,
|
||||
IsOutgoing: entry.IsOutgoing,
|
||||
Latency: entry.ElapsedTime,
|
||||
Rules: api.ApplicableRules{
|
||||
Latency: 0,
|
||||
Status: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (d dissecting) Represent(request map[string]interface{}, response map[string]interface{}) (object []byte, bodySize int64, err error) {
|
||||
bodySize = 0
|
||||
representation := make(map[string]interface{}, 0)
|
||||
|
||||
@@ -21,9 +21,32 @@ func filterAndEmit(item *api.OutputChannelItem, emitter api.Emitter, options *ap
|
||||
FilterSensitiveData(item, options)
|
||||
}
|
||||
|
||||
replaceForwardedFor(item)
|
||||
|
||||
emitter.Emit(item)
|
||||
}
|
||||
|
||||
func replaceForwardedFor(item *api.OutputChannelItem) {
|
||||
if item.Protocol.Name != "http" {
|
||||
return
|
||||
}
|
||||
|
||||
request := item.Pair.Request.Payload.(api.HTTPPayload).Data.(*http.Request)
|
||||
|
||||
forwardedFor := request.Header.Get("X-Forwarded-For")
|
||||
if forwardedFor == "" {
|
||||
return
|
||||
}
|
||||
|
||||
ips := strings.Split(forwardedFor, ",")
|
||||
lastIP := strings.TrimSpace(ips[0])
|
||||
|
||||
item.ConnectionInfo.ClientIP = lastIP
|
||||
// Erase the port field. Because the proxy terminates the connection from the client, the port that we see here
|
||||
// is not the source port on the client side.
|
||||
item.ConnectionInfo.ClientPort = ""
|
||||
}
|
||||
|
||||
func handleHTTP2Stream(http2Assembler *Http2Assembler, tcpID *api.TcpID, superTimer *api.SuperTimer, emitter api.Emitter, options *api.TrafficFilteringOptions) error {
|
||||
streamID, messageHTTP1, isGrpc, err := http2Assembler.readMessage()
|
||||
if err != nil {
|
||||
|
||||
@@ -157,7 +157,7 @@ func (d dissecting) Dissect(b *bufio.Reader, isClient bool, tcpID *api.TcpID, co
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string, resolvedDestination string) *api.MizuEntry {
|
||||
func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string, resolvedDestination string) *api.Entry {
|
||||
var host, authority, path string
|
||||
|
||||
request := item.Pair.Request.Payload.(map[string]interface{})
|
||||
@@ -241,7 +241,7 @@ func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string,
|
||||
elapsedTime = 0
|
||||
}
|
||||
httpPair, _ := json.Marshal(item.Pair)
|
||||
return &api.MizuEntry{
|
||||
return &api.Entry{
|
||||
Protocol: item.Protocol,
|
||||
Source: &api.TCP{
|
||||
Name: resolvedSource,
|
||||
@@ -267,26 +267,6 @@ func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string,
|
||||
}
|
||||
}
|
||||
|
||||
func (d dissecting) Summarize(entry *api.MizuEntry) *api.BaseEntryDetails {
|
||||
return &api.BaseEntryDetails{
|
||||
Id: entry.Id,
|
||||
Protocol: entry.Protocol,
|
||||
Path: entry.Path,
|
||||
Summary: entry.Summary,
|
||||
StatusCode: entry.Status,
|
||||
Method: entry.Method,
|
||||
Timestamp: entry.Timestamp,
|
||||
Source: entry.Source,
|
||||
Destination: entry.Destination,
|
||||
IsOutgoing: entry.IsOutgoing,
|
||||
Latency: entry.ElapsedTime,
|
||||
Rules: api.ApplicableRules{
|
||||
Latency: 0,
|
||||
Status: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func representRequest(request map[string]interface{}) (repRequest []interface{}) {
|
||||
details, _ := json.Marshal([]api.TableData{
|
||||
{
|
||||
|
||||
@@ -62,7 +62,7 @@ func (d dissecting) Dissect(b *bufio.Reader, isClient bool, tcpID *api.TcpID, co
|
||||
}
|
||||
}
|
||||
|
||||
func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string, resolvedDestination string) *api.MizuEntry {
|
||||
func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string, resolvedDestination string) *api.Entry {
|
||||
request := item.Pair.Request.Payload.(map[string]interface{})
|
||||
reqDetails := request["details"].(map[string]interface{})
|
||||
apiKey := ApiKey(reqDetails["apiKey"].(float64))
|
||||
@@ -146,7 +146,7 @@ func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string,
|
||||
if elapsedTime < 0 {
|
||||
elapsedTime = 0
|
||||
}
|
||||
return &api.MizuEntry{
|
||||
return &api.Entry{
|
||||
Protocol: _protocol,
|
||||
Source: &api.TCP{
|
||||
Name: resolvedSource,
|
||||
@@ -171,25 +171,6 @@ func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string,
|
||||
}
|
||||
}
|
||||
|
||||
func (d dissecting) Summarize(entry *api.MizuEntry) *api.BaseEntryDetails {
|
||||
return &api.BaseEntryDetails{
|
||||
Id: entry.Id,
|
||||
Protocol: _protocol,
|
||||
Summary: entry.Summary,
|
||||
StatusCode: entry.Status,
|
||||
Method: entry.Method,
|
||||
Timestamp: entry.Timestamp,
|
||||
Source: entry.Source,
|
||||
Destination: entry.Destination,
|
||||
IsOutgoing: entry.IsOutgoing,
|
||||
Latency: entry.ElapsedTime,
|
||||
Rules: api.ApplicableRules{
|
||||
Latency: 0,
|
||||
Status: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (d dissecting) Represent(request map[string]interface{}, response map[string]interface{}) (object []byte, bodySize int64, err error) {
|
||||
bodySize = 0
|
||||
representation := make(map[string]interface{}, 0)
|
||||
|
||||
@@ -59,7 +59,7 @@ func (d dissecting) Dissect(b *bufio.Reader, isClient bool, tcpID *api.TcpID, co
|
||||
}
|
||||
}
|
||||
|
||||
func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string, resolvedDestination string) *api.MizuEntry {
|
||||
func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string, resolvedDestination string) *api.Entry {
|
||||
request := item.Pair.Request.Payload.(map[string]interface{})
|
||||
response := item.Pair.Response.Payload.(map[string]interface{})
|
||||
reqDetails := request["details"].(map[string]interface{})
|
||||
@@ -80,7 +80,7 @@ func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string,
|
||||
if elapsedTime < 0 {
|
||||
elapsedTime = 0
|
||||
}
|
||||
return &api.MizuEntry{
|
||||
return &api.Entry{
|
||||
Protocol: protocol,
|
||||
Source: &api.TCP{
|
||||
Name: resolvedSource,
|
||||
@@ -106,25 +106,6 @@ func (d dissecting) Analyze(item *api.OutputChannelItem, resolvedSource string,
|
||||
|
||||
}
|
||||
|
||||
func (d dissecting) Summarize(entry *api.MizuEntry) *api.BaseEntryDetails {
|
||||
return &api.BaseEntryDetails{
|
||||
Id: entry.Id,
|
||||
Protocol: protocol,
|
||||
Summary: entry.Summary,
|
||||
StatusCode: entry.Status,
|
||||
Method: entry.Method,
|
||||
Timestamp: entry.Timestamp,
|
||||
Source: entry.Source,
|
||||
Destination: entry.Destination,
|
||||
IsOutgoing: entry.IsOutgoing,
|
||||
Latency: entry.ElapsedTime,
|
||||
Rules: api.ApplicableRules{
|
||||
Latency: 0,
|
||||
Status: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (d dissecting) Represent(request map[string]interface{}, response map[string]interface{}) (object []byte, bodySize int64, err error) {
|
||||
bodySize = 0
|
||||
representation := make(map[string]interface{}, 0)
|
||||
|
||||
2
ui/package-lock.json
generated
2
ui/package-lock.json
generated
@@ -18684,4 +18684,4 @@
|
||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,3 @@ body
|
||||
.mizuApp
|
||||
color: $font-color
|
||||
width: 100%
|
||||
|
||||
.centeredForm
|
||||
max-width: 500px
|
||||
text-align: center
|
||||
display: flex
|
||||
flex-direction: column
|
||||
margin: 0 auto
|
||||
|
||||
.form-input, .form-button
|
||||
margin-top: 20px
|
||||
|
||||
@@ -8,6 +8,7 @@ import {toast} from "react-toastify";
|
||||
import InstallPage from "./components/InstallPage";
|
||||
import LoginPage from "./components/LoginPage";
|
||||
import LoadingOverlay from "./components/LoadingOverlay";
|
||||
import AuthPageBase from './components/AuthPageBase';
|
||||
|
||||
const api = Api.getInstance();
|
||||
|
||||
@@ -75,10 +76,10 @@ const EntApp = () => {
|
||||
pageComponent = <TrafficPage onTLSDetected={onTLSDetected}/>;
|
||||
break;
|
||||
case Page.Setup:
|
||||
pageComponent = <InstallPage onFirstLogin={() => setIsFirstLogin(true)}/>;
|
||||
pageComponent = <AuthPageBase><InstallPage onFirstLogin={() => setIsFirstLogin(true)}/></AuthPageBase>;
|
||||
break;
|
||||
case Page.Login:
|
||||
pageComponent = <LoginPage/>;
|
||||
pageComponent = <AuthPageBase><LoginPage/></AuthPageBase>;
|
||||
break;
|
||||
default:
|
||||
pageComponent = <div>Unknown Error</div>;
|
||||
|
||||
16
ui/src/components/AuthPageBase.tsx
Normal file
16
ui/src/components/AuthPageBase.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import background from "./assets/authBackground.png";
|
||||
import logo from './assets/MizuEntLogoFull.svg';
|
||||
import "./style/AuthBasePage.sass";
|
||||
|
||||
|
||||
export const AuthPageBase: React.FC = ({children}) => {
|
||||
return <div className="authContainer" style={{background: `url(${background})`, backgroundSize: "cover"}}>
|
||||
<div className="authHeader">
|
||||
<img alt="logo" src={logo}/>
|
||||
</div>
|
||||
{children}
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default AuthPageBase;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useCallback, useEffect, useMemo, useRef, useState} from "react";
|
||||
import React, {useCallback, useEffect, useMemo, useState} from "react";
|
||||
import styles from './style/EntriesList.module.sass';
|
||||
import ScrollableFeedVirtualized from "react-scrollable-feed-virtualized";
|
||||
import Moment from 'moment';
|
||||
@@ -33,14 +33,14 @@ interface EntriesListProps {
|
||||
leftOffBottom: number;
|
||||
truncatedTimestamp: number;
|
||||
setTruncatedTimestamp: any;
|
||||
scrollableRef: any;
|
||||
}
|
||||
|
||||
const api = Api.getInstance();
|
||||
|
||||
export const EntriesList: React.FC<EntriesListProps> = ({entries, setEntries, query, listEntryREF, onSnapBrokenEvent, isSnappedToBottom, setIsSnappedToBottom, queriedCurrent, setQueriedCurrent, queriedTotal, setQueriedTotal, startTime, noMoreDataTop, setNoMoreDataTop, focusedEntryId, setFocusedEntryId, updateQuery, leftOffTop, setLeftOffTop, isWebSocketConnectionClosed, ws, openWebSocket, leftOffBottom, truncatedTimestamp, setTruncatedTimestamp}) => {
|
||||
export const EntriesList: React.FC<EntriesListProps> = ({entries, setEntries, query, listEntryREF, onSnapBrokenEvent, isSnappedToBottom, setIsSnappedToBottom, queriedCurrent, setQueriedCurrent, queriedTotal, setQueriedTotal, startTime, noMoreDataTop, setNoMoreDataTop, focusedEntryId, setFocusedEntryId, updateQuery, leftOffTop, setLeftOffTop, isWebSocketConnectionClosed, ws, openWebSocket, leftOffBottom, truncatedTimestamp, setTruncatedTimestamp, scrollableRef}) => {
|
||||
const [loadMoreTop, setLoadMoreTop] = useState(false);
|
||||
const [isLoadingTop, setIsLoadingTop] = useState(false);
|
||||
const scrollableRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const list = document.getElementById('list').firstElementChild;
|
||||
@@ -92,7 +92,7 @@ export const EntriesList: React.FC<EntriesListProps> = ({entries, setEntries, qu
|
||||
if (scrollTo) {
|
||||
scrollableRef.current.scrollToIndex(data.data.length - 1);
|
||||
}
|
||||
},[setLoadMoreTop, setIsLoadingTop, entries, setEntries, query, setNoMoreDataTop, leftOffTop, setLeftOffTop, queriedCurrent, setQueriedCurrent, setQueriedTotal, setTruncatedTimestamp]);
|
||||
},[setLoadMoreTop, setIsLoadingTop, entries, setEntries, query, setNoMoreDataTop, leftOffTop, setLeftOffTop, queriedCurrent, setQueriedCurrent, setQueriedTotal, setTruncatedTimestamp, scrollableRef]);
|
||||
|
||||
useEffect(() => {
|
||||
if(!isWebSocketConnectionClosed || !loadMoreTop || noMoreDataTop) return;
|
||||
|
||||
@@ -69,9 +69,7 @@ const EntryTitle: React.FC<any> = ({protocol, data, bodySize, elapsedTime, updat
|
||||
</div>;
|
||||
};
|
||||
|
||||
const EntrySummary: React.FC<any> = ({data, updateQuery}) => {
|
||||
const entry = data.base;
|
||||
|
||||
const EntrySummary: React.FC<any> = ({entry, updateQuery}) => {
|
||||
return <EntryItem
|
||||
key={`entry-${entry.id}`}
|
||||
entry={entry}
|
||||
@@ -92,7 +90,7 @@ export const EntryDetailed: React.FC<EntryDetailedProps> = ({entryData, updateQu
|
||||
elapsedTime={entryData.data.elapsedTime}
|
||||
updateQuery={updateQuery}
|
||||
/>
|
||||
{entryData.data && <EntrySummary data={entryData.data} updateQuery={updateQuery}/>}
|
||||
{entryData.data && <EntrySummary entry={entryData.data} updateQuery={updateQuery}/>}
|
||||
<>
|
||||
{entryData.data && <EntryViewer
|
||||
representation={entryData.representation}
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
color: $blue-gray
|
||||
border-radius: 4px
|
||||
padding: 10px
|
||||
position: relative
|
||||
.bodyHeader
|
||||
padding: 0 1rem
|
||||
.endpointURL
|
||||
|
||||
@@ -20,11 +20,11 @@ interface TCPInterface {
|
||||
}
|
||||
|
||||
interface Entry {
|
||||
protocol: ProtocolInterface,
|
||||
proto: ProtocolInterface,
|
||||
method?: string,
|
||||
summary: string,
|
||||
id: number,
|
||||
statusCode?: number;
|
||||
status?: number;
|
||||
timestamp: Date;
|
||||
src: TCPInterface,
|
||||
dst: TCPInterface,
|
||||
@@ -53,7 +53,7 @@ export const EntryItem: React.FC<EntryProps> = ({entry, focusedEntryId, setFocus
|
||||
|
||||
const isSelected = focusedEntryId === entry.id.toString();
|
||||
|
||||
const classification = getClassification(entry.statusCode)
|
||||
const classification = getClassification(entry.status)
|
||||
const numberOfRules = entry.rules.numberOfRules
|
||||
let ingoingIcon;
|
||||
let outgoingIcon;
|
||||
@@ -123,7 +123,7 @@ export const EntryItem: React.FC<EntryProps> = ({entry, focusedEntryId, setFocus
|
||||
break;
|
||||
}
|
||||
|
||||
const isStatusCodeEnabled = ((entry.protocol.name === "http" && "statusCode" in entry) || entry.statusCode !== 0);
|
||||
const isStatusCodeEnabled = ((entry.proto.name === "http" && "status" in entry) || entry.status !== 0);
|
||||
var endpointServiceContainer = "10px";
|
||||
if (!isStatusCodeEnabled) endpointServiceContainer = "20px";
|
||||
|
||||
@@ -137,7 +137,7 @@ export const EntryItem: React.FC<EntryProps> = ({entry, focusedEntryId, setFocus
|
||||
setFocusedEntryId(entry.id.toString());
|
||||
}}
|
||||
style={{
|
||||
border: isSelected ? `1px ${entry.protocol.backgroundColor} solid` : "1px transparent solid",
|
||||
border: isSelected ? `1px ${entry.proto.backgroundColor} solid` : "1px transparent solid",
|
||||
position: !headingMode ? "absolute" : "unset",
|
||||
top: style['top'],
|
||||
marginTop: !headingMode ? style['marginTop'] : "10px",
|
||||
@@ -145,12 +145,12 @@ export const EntryItem: React.FC<EntryProps> = ({entry, focusedEntryId, setFocus
|
||||
}}
|
||||
>
|
||||
{!headingMode ? <Protocol
|
||||
protocol={entry.protocol}
|
||||
protocol={entry.proto}
|
||||
horizontal={false}
|
||||
updateQuery={updateQuery}
|
||||
/> : null}
|
||||
{isStatusCodeEnabled && <div>
|
||||
<StatusCode statusCode={entry.statusCode} updateQuery={updateQuery}/>
|
||||
<StatusCode statusCode={entry.status} updateQuery={updateQuery}/>
|
||||
</div>}
|
||||
<div className={styles.endpointServiceContainer} style={{paddingLeft: endpointServiceContainer}}>
|
||||
<Summary method={entry.method} summary={entry.summary} updateQuery={updateQuery}/>
|
||||
@@ -161,7 +161,9 @@ export const EntryItem: React.FC<EntryProps> = ({entry, focusedEntryId, setFocus
|
||||
displayIconOnMouseOver={true}
|
||||
flipped={true}
|
||||
style={{marginTop: "-4px", overflow: "visible"}}
|
||||
iconStyle={!headingMode ? {marginTop: "4px", left: "68px", position: "absolute"} : {marginTop: "4px", left: "calc(50vw + 41px)", position: "absolute"}}
|
||||
iconStyle={!headingMode ? {marginTop: "4px", left: "68px", position: "absolute"} :
|
||||
entry.proto.name === "http" ? {marginTop: "4px", left: "calc(50vw + 41px)", position: "absolute"} :
|
||||
{marginTop: "4px", left: "calc(50vw - 9px)", position: "absolute"}}
|
||||
>
|
||||
<span
|
||||
title="Source Name"
|
||||
@@ -169,7 +171,7 @@ export const EntryItem: React.FC<EntryProps> = ({entry, focusedEntryId, setFocus
|
||||
{entry.src.name ? entry.src.name : "[Unresolved]"}
|
||||
</span>
|
||||
</Queryable>
|
||||
<SwapHorizIcon style={{color: entry.protocol.backgroundColor, marginTop: "-2px"}}></SwapHorizIcon>
|
||||
<SwapHorizIcon style={{color: entry.proto.backgroundColor, marginTop: "-2px"}}></SwapHorizIcon>
|
||||
<Queryable
|
||||
query={`dst.name == "${entry.dst.name}"`}
|
||||
updateQuery={updateQuery}
|
||||
@@ -214,7 +216,7 @@ export const EntryItem: React.FC<EntryProps> = ({entry, focusedEntryId, setFocus
|
||||
{entry.src.ip}
|
||||
</span>
|
||||
</Queryable>
|
||||
<span className={`${styles.tcpInfo}`} style={{marginTop: "18px"}}>:</span>
|
||||
<span className={`${styles.tcpInfo}`} style={{marginTop: "18px"}}>{entry.src.port ? ":" : ""}</span>
|
||||
<Queryable
|
||||
query={`src.port == "${entry.src.port}"`}
|
||||
updateQuery={updateQuery}
|
||||
|
||||
@@ -265,7 +265,7 @@ export const QueryForm: React.FC<QueryFormProps> = ({query, setQuery, background
|
||||
</Typography>
|
||||
<br></br>
|
||||
<Typography id="modal-modal-description">
|
||||
true if the given selector's value starts with the string:
|
||||
true if the given selector's value starts with (similarly <code style={{fontSize: "14px"}}>endsWith</code>, <code style={{fontSize: "14px"}}>contains</code>) the string:
|
||||
</Typography>
|
||||
<SyntaxHighlighter
|
||||
showLineNumbers={false}
|
||||
@@ -273,19 +273,19 @@ export const QueryForm: React.FC<QueryFormProps> = ({query, setQuery, background
|
||||
language="python"
|
||||
/>
|
||||
<Typography id="modal-modal-description">
|
||||
true if the given selector's value ends with the string:
|
||||
a field that contains a JSON encoded string can be filtered based a JSONPath:
|
||||
</Typography>
|
||||
<SyntaxHighlighter
|
||||
showLineNumbers={false}
|
||||
code={`request.path.endsWith("something")`}
|
||||
code={`response.content.text.json().some.path == "somevalue"`}
|
||||
language="python"
|
||||
/>
|
||||
<Typography id="modal-modal-description">
|
||||
true if the given selector's value contains the string:
|
||||
fields that contain sensitive information can be redacted:
|
||||
</Typography>
|
||||
<SyntaxHighlighter
|
||||
showLineNumbers={false}
|
||||
code={`request.path.contains("something")`}
|
||||
code={`and redact("request.path", "src.name")`}
|
||||
language="python"
|
||||
/>
|
||||
<Typography id="modal-modal-description">
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Button, TextField } from "@material-ui/core";
|
||||
import { Button } from "@material-ui/core";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { MizuContext, Page } from "../EntApp";
|
||||
import { adminUsername } from "../consts";
|
||||
import Api, { FormValidationErrorType } from "../helpers/api";
|
||||
import { toast } from 'react-toastify';
|
||||
import LoadingOverlay from "./LoadingOverlay";
|
||||
import { useCommonStyles } from "../helpers/commonStyle";
|
||||
|
||||
const api = Api.getInstance();
|
||||
|
||||
@@ -14,6 +15,7 @@ interface InstallPageProps {
|
||||
|
||||
export const InstallPage: React.FC<InstallPageProps> = ({onFirstLogin}) => {
|
||||
|
||||
const classes = useCommonStyles();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [password, setPassword] = useState("");
|
||||
const [passwordConfirm, setPasswordConfirm] = useState("");
|
||||
@@ -33,7 +35,6 @@ export const InstallPage: React.FC<InstallPageProps> = ({onFirstLogin}) => {
|
||||
setIsLoading(true);
|
||||
await api.register(adminUsername, password);
|
||||
if (!await api.isAuthenticationNeeded()) {
|
||||
toast.success("admin user created successfully");
|
||||
setPage(Page.Traffic);
|
||||
onFirstLogin();
|
||||
}
|
||||
@@ -54,11 +55,21 @@ export const InstallPage: React.FC<InstallPageProps> = ({onFirstLogin}) => {
|
||||
|
||||
return <div className="centeredForm">
|
||||
{isLoading && <LoadingOverlay/>}
|
||||
<p>Welcome to Mizu, please set up the admin user to continue</p>
|
||||
<TextField className="form-input" variant="standard" fullWidth value={adminUsername} disabled={true}/>
|
||||
<TextField className="form-input" label="Password" variant="standard" type="password" fullWidth value={password} onChange={e => setPassword(e.target.value)}/>
|
||||
<TextField className="form-input" label="Confirm Password" variant="standard" type="password" fullWidth value={passwordConfirm} onChange={e => setPasswordConfirm(e.target.value)}/>
|
||||
<Button className="form-button" variant="contained" fullWidth onClick={onFormSubmit}>Finish</Button>
|
||||
<div className="form-title left-text">Setup</div>
|
||||
<span className="form-subtitle">Welcome to Mizu, please set up the admin user to continue</span>
|
||||
<div className="form-input">
|
||||
<label htmlFor="inputUsername">Username</label>
|
||||
<input id="inputUsername" className={classes.textField} value={adminUsername} disabled={true} />
|
||||
</div>
|
||||
<div className="form-input">
|
||||
<label htmlFor="inputUsername">Password</label>
|
||||
<input id="inputUsername" className={classes.textField} value={password} type="password" onChange={(event) => setPassword(event.target.value)}/>
|
||||
</div>
|
||||
<div className="form-input">
|
||||
<label htmlFor="inputUsername">Confirm Password</label>
|
||||
<input id="inputUsername" className={classes.textField} value={passwordConfirm} type="password" onChange={(event) => setPasswordConfirm(event.target.value)}/>
|
||||
</div>
|
||||
<Button className={classes.button + " form-button"} variant="contained" fullWidth onClick={onFormSubmit}>Finish</Button>
|
||||
</div>;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { Button, TextField } from "@material-ui/core";
|
||||
import { Button } from "@material-ui/core";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
import { MizuContext, Page } from "../EntApp";
|
||||
import Api from "../helpers/api";
|
||||
import { useCommonStyles } from "../helpers/commonStyle";
|
||||
import LoadingOverlay from "./LoadingOverlay";
|
||||
|
||||
const api = Api.getInstance();
|
||||
|
||||
const LoginPage: React.FC = () => {
|
||||
|
||||
const classes = useCommonStyles();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
@@ -22,7 +23,6 @@ const LoginPage: React.FC = () => {
|
||||
try {
|
||||
await api.login(username, password);
|
||||
if (!await api.isAuthenticationNeeded()) {
|
||||
toast.success("Logged in successfully");
|
||||
setPage(Page.Traffic);
|
||||
} else {
|
||||
toast.error("Invalid credentials");
|
||||
@@ -33,16 +33,28 @@ const LoginPage: React.FC = () => {
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const handleFormOnKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter") {
|
||||
onFormSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
return <div className="centeredForm">
|
||||
|
||||
return <div className="centeredForm" onKeyPress={handleFormOnKeyPress}>
|
||||
{isLoading && <LoadingOverlay/>}
|
||||
<p>Welcome to Mizu, please login to continue</p>
|
||||
<TextField className="form-input" label="Username" variant="standard" fullWidth value={username} onChange={e => setUsername(e.target.value)} />
|
||||
<TextField className="form-input" label="Password" variant="standard" type="password" fullWidth value={password} onChange={e => setPassword(e.target.value)} />
|
||||
<Button className="form-button" variant="contained" fullWidth onClick={onFormSubmit}>Login</Button>
|
||||
<div className="form-title left-text">Login</div>
|
||||
<div className="form-input">
|
||||
<label htmlFor="inputUsername">Username</label>
|
||||
<input id="inputUsername" autoFocus className={classes.textField} value={username} onChange={(event) => setUsername(event.target.value)}/>
|
||||
</div>
|
||||
<div className="form-input">
|
||||
<label htmlFor="inputPassword">Password</label>
|
||||
<input id="inputPassword" className={classes.textField} value={password} type="password" onChange={(event) => setPassword(event.target.value)}/>
|
||||
</div>
|
||||
<Button className={classes.button + " form-button"} variant="contained" fullWidth onClick={onFormSubmit}>Log in</Button>
|
||||
|
||||
</div>;
|
||||
};
|
||||
|
||||
|
||||
@@ -24,8 +24,10 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({isOpen, onClose, is
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if(!isOpen) return;
|
||||
(async () => {
|
||||
try {
|
||||
setSearchValue("");
|
||||
setIsLoading(true);
|
||||
const tapConfig = await api.getTapConfig()
|
||||
if(isFirstLogin) {
|
||||
@@ -43,7 +45,7 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({isOpen, onClose, is
|
||||
setIsLoading(false);
|
||||
}
|
||||
})()
|
||||
}, [isFirstLogin])
|
||||
}, [isFirstLogin, isOpen])
|
||||
|
||||
const setAllNamespacesTappedValue = (isTap: boolean) => {
|
||||
const newNamespaces = {};
|
||||
@@ -129,7 +131,7 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({isOpen, onClose, is
|
||||
</div> : <>
|
||||
<div className="namespacesSettingsContainer">
|
||||
<div style={{margin: "10px 0"}}>
|
||||
<input className="searchNamespace" placeholder="Search" value={searchValue}
|
||||
<input className={classes.textField + " searchNamespace"} placeholder="Search" value={searchValue}
|
||||
onChange={(event) => setSearchValue(event.target.value)}/></div>
|
||||
<div className="namespacesTable">
|
||||
{buildNamespacesTable()}
|
||||
|
||||
@@ -16,15 +16,8 @@
|
||||
padding: 20px 0
|
||||
|
||||
.searchNamespace
|
||||
outline: 0
|
||||
background: white
|
||||
border-radius: 4px
|
||||
width: 300px
|
||||
max-width: calc(40vw - 85px)
|
||||
padding: 8px 10px
|
||||
border: 1px #9D9D9D solid
|
||||
font-size: 14px
|
||||
color: #494677
|
||||
|
||||
.settingsActionsContainer
|
||||
display: flex
|
||||
|
||||
@@ -72,6 +72,8 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({onTLSDetected, setAnaly
|
||||
|
||||
const [startTime, setStartTime] = useState(0);
|
||||
|
||||
const scrollableRef = useRef(null);
|
||||
|
||||
const handleQueryChange = useMemo(() => debounce(async (query: string) => {
|
||||
if (!query) {
|
||||
setQueryBackgroundColor("#f5f5f5")
|
||||
@@ -210,7 +212,7 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({onTLSDetected, setAnaly
|
||||
setSelectedEntryData(null);
|
||||
(async () => {
|
||||
try {
|
||||
const entryData = await api.getEntry(focusedEntryId);
|
||||
const entryData = await api.getEntry(focusedEntryId, query);
|
||||
setSelectedEntryData(entryData);
|
||||
} catch (error) {
|
||||
if (error.response?.data?.type) {
|
||||
@@ -239,6 +241,8 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({onTLSDetected, setAnaly
|
||||
} else {
|
||||
openWebSocket(`leftOff(-1)`, true);
|
||||
}
|
||||
scrollableRef.current.jumpToBottom();
|
||||
setIsSnappedToBottom(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,6 +322,7 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({onTLSDetected, setAnaly
|
||||
leftOffBottom={leftOffBottom}
|
||||
truncatedTimestamp={truncatedTimestamp}
|
||||
setTruncatedTimestamp={setTruncatedTimestamp}
|
||||
scrollableRef={scrollableRef}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -39,16 +39,16 @@ export const StatusBar: React.FC<Props> = ({tappingStatus}) => {
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Pod name</th>
|
||||
<th>Namespace</th>
|
||||
<th style={{marginLeft: 10}}>Tapping</th>
|
||||
<th style={{width: "40%"}}>Pod name</th>
|
||||
<th style={{width: "40%"}}>Namespace</th>
|
||||
<th style={{width: "20%", textAlign: "center"}}>Tapping</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tappingStatus.map(pod => <tr key={pod.name}>
|
||||
<td>{pod.name}</td>
|
||||
<td>{pod.namespace}</td>
|
||||
<td style={{textAlign: "center"}}><img style={{height: 20}} alt="status" src={pod.isTapped ? successIcon : failIcon}/></td>
|
||||
<td style={{width: "40%"}}>{pod.name}</td>
|
||||
<td style={{width: "40%"}}>{pod.namespace}</td>
|
||||
<td style={{width: "20%", textAlign: "center"}}><img style={{height: 20}} alt="status" src={pod.isTapped ? successIcon : failIcon}/></td>
|
||||
</tr>)}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
border-bottom-left-radius: 8px
|
||||
border-bottom-right-radius: 8px
|
||||
top: 0
|
||||
padding: 2px 10px
|
||||
padding: 10px
|
||||
font-size: 14px
|
||||
transition: max-height 2s ease-out
|
||||
width: auto
|
||||
@@ -21,19 +21,30 @@
|
||||
.podsCount
|
||||
display: flex
|
||||
justify-content: center
|
||||
padding: 8px
|
||||
font-weight: 600
|
||||
|
||||
img
|
||||
margin-right: 10px
|
||||
height: 22px
|
||||
|
||||
th
|
||||
text-align: left
|
||||
padding-right: 15px
|
||||
td
|
||||
padding-right: 15px
|
||||
padding-top: 5px
|
||||
table
|
||||
width: 100%
|
||||
margin-top: 20px
|
||||
|
||||
tbody
|
||||
max-height: 70vh
|
||||
overflow-y: auto
|
||||
display: block
|
||||
tr
|
||||
display: table
|
||||
table-layout: fixed
|
||||
width: 100%
|
||||
th
|
||||
text-align: left
|
||||
padding-right: 5%
|
||||
td
|
||||
text-align: left
|
||||
padding-right: 5%
|
||||
|
||||
.expandedStatusBar
|
||||
max-height: 100vh
|
||||
|
||||
136
ui/src/components/assets/MizuEntLogoFull.svg
Normal file
136
ui/src/components/assets/MizuEntLogoFull.svg
Normal file
@@ -0,0 +1,136 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="260"
|
||||
height="146"
|
||||
viewBox="0 0 260 146"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg60"
|
||||
sodipodi:docname="MizuEntLogoFull.svg"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
|
||||
<metadata
|
||||
id="metadata66">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs64" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="2488"
|
||||
inkscape:window-height="1376"
|
||||
id="namedview62"
|
||||
showgrid="false"
|
||||
inkscape:zoom="3.7748624"
|
||||
inkscape:cx="67.688372"
|
||||
inkscape:cy="33.709918"
|
||||
inkscape:window-x="72"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg60" />
|
||||
<path
|
||||
d="M50.5684 89.1243H36.3639L26.9321 66.0935C26.7048 65.4538 26.2503 64.2943 25.5685 62.6149C24.9624 60.8556 24.2427 58.8964 23.4094 56.7373C22.6518 54.4982 21.8563 52.2191 21.023 49.9C20.1897 47.581 19.4321 45.4618 18.7503 43.5426C18.7503 51.3373 18.6475 46.7868 18.4094 54.5782C18.4094 59.0173 14.8865 62.6149 10.6811 62.6149H10.2362C5.56284 62.6149 1.86923 58.4328 2.19637 53.5119L4.59996 17.3574C4.95347 12.0399 9.14316 7.91683 14.1931 7.91683H14.5754C18.6754 7.91683 22.3624 10.5518 23.8851 14.5701L39.0912 54.6981C39.6215 56.1375 40.3033 58.1767 41.1366 60.8157C42.0457 63.3746 42.9169 66.1735 43.7503 69.2123C44.5836 66.2535 45.4169 63.4946 46.2503 60.9356C47.1594 58.2967 47.9169 56.2175 48.523 54.6981L63.7981 14.3878C65.2791 10.4796 68.8651 7.91683 72.8528 7.91683C78.0153 7.91683 82.2834 12.1638 82.5879 17.6038L86.5911 89.1243H68.9775L68.2957 72.8109C68.2199 71.2915 68.1442 69.2923 68.0684 66.8133C68.0684 64.3343 68.0684 61.7353 68.0684 59.0164C68.0684 56.2175 68.0684 53.4586 68.0684 50.7397C68.1442 47.9408 68.1821 45.5418 68.1821 43.5426C67.576 45.2219 66.8942 47.1411 66.1366 49.3003C65.4548 51.3794 64.7351 53.4186 63.9775 55.4178C63.2199 57.417 62.5002 59.2963 61.8184 61.0556C61.1366 62.7349 60.6063 64.0544 60.2275 65.014L50.5684 89.1243Z"
|
||||
fill="#205CF5"
|
||||
id="path2" />
|
||||
<path
|
||||
d="M101.591 86.6053C98.6363 83.4863 97.7834 80.3678 97.7834 76.0496V38.3846C97.7834 33.1511 101.803 28.9084 106.761 28.9084C111.719 28.9084 115.738 33.1511 115.738 38.3846V70.6517C115.738 72.491 116.003 73.7305 116.533 74.3702C117.064 75.01 117.859 75.3299 118.92 75.3299C119.526 75.3299 130.19 75.2899 130.796 75.2099C131.402 75.05 130.53 75.2899 130.682 75.2099L123.465 86.7253C123.389 86.8053 123.049 87.0052 122.442 87.3251C121.836 87.565 121.041 87.8848 120.056 88.2847C115.341 87.445 111.705 88.5246 107.666 89.143C104.091 89.1243 102.955 88.0448 101.591 86.6053ZM106.988 0C110.17 0 112.594 0.999599 114.261 2.9988C115.927 4.998 116.761 7.51699 116.761 10.5558C116.761 13.6745 115.889 16.2735 114.147 18.3527C112.48 20.4318 109.942 21.4714 106.533 21.4714C103.352 21.4714 100.889 20.4718 99.147 18.4726C97.4804 16.3934 96.647 13.9544 96.647 11.1555C96.647 7.95681 97.5183 5.31787 99.2607 3.23871C101.003 1.07957 103.579 0 106.988 0Z"
|
||||
fill="#205CF5"
|
||||
id="path4" />
|
||||
<path
|
||||
d="M126.778 89.1243V78.5686L148.369 43.0628C146.93 43.1427 145.566 43.2227 144.278 43.3027C142.991 43.3027 141.703 43.3027 140.415 43.3027H135.512C132.304 43.3027 129.562 40.8646 129.017 37.5275C128.322 33.2754 131.424 29.3882 135.512 29.3882H160.826C165.557 29.3882 169.392 33.4365 169.392 38.4303C169.392 40.125 168.941 41.7856 168.09 43.2226L148.938 75.5698C149.922 75.4898 150.869 75.4498 151.778 75.4498C152.763 75.3698 153.786 75.3299 154.847 75.3299H194.088V89.1243H126.778Z"
|
||||
fill="#205CF5"
|
||||
id="path6" />
|
||||
<path
|
||||
d="M207.269 38.3846C207.269 33.1511 211.289 28.9084 216.247 28.9084C221.205 28.9084 225.224 33.1511 225.224 38.3846V60.2159C225.224 65.014 223.75 86.1255 237.273 86.3654C237.879 89.5641 226.099 85.8856 226.932 87.8049C227.841 90.1239 223.065 86.3654 226.133 89.1243L237.273 98.3606C221.591 98.3606 215.678 88.4446 214.997 87.3251C214.391 86.4454 213.784 85.3259 213.178 83.9664C212.572 82.527 212.424 82.6869 212.046 80.8476C210.076 84.6061 208.977 85.8731 205.227 87.8049C202.666 89.1243 198.103 89.1243 194.088 89.1243C191.133 89.1243 185.909 89.1243 184.091 89.1243C182.046 88.0847 181.019 85.9656 179.656 84.2063C178.368 82.367 177.421 80.1679 176.815 77.6089C176.284 75.05 176.019 72.1711 176.019 68.9724V38.4446C176.019 33.1779 180.064 28.9084 185.053 28.9084C190.043 28.9084 194.088 33.1779 194.088 38.4446V66.4534C194.088 69.2523 194.391 71.5314 194.997 73.2907C195.678 74.97 196.891 75.8097 198.633 75.8097C200.754 75.8097 203.409 74.8101 205.227 71.8512C207.046 68.8125 207.269 64.1743 207.269 58.8964V38.3846Z"
|
||||
fill="#205CF5"
|
||||
id="path8" />
|
||||
<path
|
||||
d="M107.5 78.5686H129.091V89.1243H107.5V78.5686Z"
|
||||
fill="#205CF5"
|
||||
id="path10" />
|
||||
<path
|
||||
d="M15.9222 72.5261C14.4156 70.7167 12.2242 69.8121 9.34795 69.8121C6.26629 69.8121 3.93792 70.7891 2.36285 72.7432C0.78778 74.6249 0.000244141 77.0132 0.000244141 79.9081C0.000244141 82.4412 0.753539 84.6486 2.26013 86.5303C3.8352 88.3396 6.06085 89.2443 8.93706 89.2443C12.0187 89.2443 14.3129 88.3034 15.8194 86.4217C17.3945 84.54 18.182 82.1879 18.182 79.3653C18.182 76.6152 17.4288 74.3354 15.9222 72.5261Z"
|
||||
fill="#205CF5"
|
||||
id="path12" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M218.864 65.014C222.002 65.014 224.546 67.6992 224.546 71.0116C224.546 80.259 230.619 86.3654 236.591 86.3654C242.562 86.3654 248.636 80.259 248.636 71.0116C248.636 67.6992 251.18 65.014 254.318 65.014C257.456 65.014 260 67.6992 260 71.0116C260 85.3483 250.2 98.3606 236.591 98.3606C222.982 98.3606 213.182 85.3483 213.182 71.0116C213.182 67.6992 215.726 65.014 218.864 65.014Z"
|
||||
fill="#205CF5"
|
||||
id="path14" />
|
||||
<path
|
||||
d="m 45.292,121.295 c 0.065,0.037 0.119,0.09 0.157,0.154 0.037,0.064 0.057,0.137 0.056,0.211 0,0.074 -0.02,0.147 -0.058,0.211 -0.039,0.064 -0.093,0.116 -0.159,0.152 l -1.898,1.063 -0.014,-0.009 -2.95,1.689 c -0.129,0.074 -0.276,0.112 -0.425,0.112 -0.149,0 -0.295,-0.038 -0.424,-0.112 l -3.815,-2.179 c -0.515,-0.294 -0.942,-0.717 -1.24,-1.225 -0.297,-0.509 -0.453,-1.086 -0.453,-1.674 V 107.13 c 10e-4,-0.593 0.16,-1.174 0.462,-1.686 0.303,-0.511 0.737,-0.934 1.259,-1.225 l 0.191,-0.107 c 0.098,-0.055 0.214,-0.069 0.322,-0.039 0.109,0.029 0.201,0.1 0.257,0.197 0.036,0.063 0.055,0.134 0.054,0.206 v 15.485 c 0,0.073 0.019,0.146 0.056,0.21 0.037,0.064 0.091,0.117 0.155,0.154 l 2.12,1.221 c 0.064,0.037 0.137,0.056 0.211,0.056 0.075,0 0.148,-0.019 0.212,-0.056 l 0.027,-0.015 c 0.065,-0.037 0.118,-0.09 0.156,-0.153 0.037,-0.064 0.057,-0.136 0.057,-0.21 0,-0.073 -0.02,-0.146 -0.057,-0.209 -0.038,-0.064 -0.091,-0.117 -0.156,-0.154 l -1.725,-0.984 c -0.065,-0.037 -0.118,-0.09 -0.155,-0.154 -0.037,-0.064 -0.057,-0.137 -0.056,-0.21 v -15.714 c 0,-0.148 0.039,-0.294 0.115,-0.422 0.076,-0.128 0.184,-0.234 0.315,-0.306 l 1.48,-0.829 c 0.098,-0.054 0.214,-0.068 0.322,-0.039 0.109,0.03 0.201,0.101 0.257,0.197 0.036,0.063 0.055,0.133 0.054,0.205 v 15.485 c 0,0.074 0.019,0.146 0.056,0.21 0.038,0.064 0.091,0.117 0.156,0.154 z"
|
||||
id="path38"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#b1c0e1" />
|
||||
<path
|
||||
d="m 44.037,116.722 c -0.097,-0.056 -0.169,-0.147 -0.198,-0.254 -0.03,-0.107 -0.015,-0.222 0.04,-0.318 0.038,-0.066 0.093,-0.12 0.16,-0.158 l 4.227,-2.452 c 0.064,-0.037 0.117,-0.091 0.154,-0.155 0.036,-0.064 0.055,-0.136 0.055,-0.209 V 98.425 c 0,-0.11 -0.022,-0.219 -0.064,-0.321 -0.043,-0.102 -0.105,-0.194 -0.184,-0.272 -0.079,-0.078 -0.172,-0.14 -0.275,-0.182 -0.103,-0.042 -0.214,-0.064 -0.325,-0.064 -0.15,0 -0.298,0.039 -0.427,0.115 l -3.811,2.202 -2.112,1.184 c -0.13,0.073 -0.239,0.179 -0.314,0.307 -0.076,0.128 -0.116,0.273 -0.116,0.421 v 15.767 c 0,0.074 0.019,0.146 0.056,0.21 0.037,0.064 0.091,0.118 0.155,0.155 l 5.289,3.032 c 0.131,0.074 0.279,0.113 0.431,0.112 0.151,-10e-4 0.299,-0.042 0.429,-0.119 l 1.494,-0.888 c 0.063,-0.038 0.116,-0.091 0.152,-0.155 0.036,-0.064 0.054,-0.136 0.053,-0.209 0,-0.073 -0.02,-0.145 -0.058,-0.208 -0.037,-0.063 -0.09,-0.116 -0.154,-0.152 z m -0.442,-14.005 1.696,-1.008 c 0.065,-0.038 0.138,-0.059 0.213,-0.059 0.075,-0.001 0.149,0.018 0.214,0.054 0.066,0.037 0.12,0.09 0.157,0.154 0.038,0.064 0.058,0.137 0.057,0.211 v 9.676 c 0,0.074 -0.02,0.147 -0.057,0.211 -0.038,0.064 -0.092,0.117 -0.157,0.153 l -1.696,0.955 c -0.065,0.037 -0.138,0.056 -0.212,0.055 -0.074,0 -0.147,-0.02 -0.211,-0.057 -0.064,-0.037 -0.117,-0.089 -0.154,-0.153 -0.037,-0.064 -0.057,-0.136 -0.057,-0.209 v -9.622 c 0.001,-0.073 0.02,-0.144 0.056,-0.207 0.036,-0.063 0.088,-0.116 0.151,-0.154 z"
|
||||
id="path40"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#b1c0e1" />
|
||||
<path
|
||||
d="m 49.323,98.919 v 14.721 c 0,0.074 -0.019,0.147 -0.056,0.211 -0.038,0.064 -0.091,0.117 -0.156,0.154 l -3.393,1.943 c -0.064,0.037 -0.118,0.09 -0.155,0.153 -0.037,0.064 -0.056,0.136 -0.056,0.209 0,0.073 0.019,0.145 0.056,0.209 0.037,0.064 0.091,0.116 0.155,0.153 l 4.664,2.667 c 0.064,0.037 0.117,0.09 0.154,0.153 0.038,0.064 0.057,0.136 0.057,0.21 0,0.073 -0.019,0.145 -0.057,0.209 -0.037,0.063 -0.09,0.116 -0.154,0.153 l -8.899,5.093 c -0.065,0.036 -0.12,0.089 -0.157,0.153 -0.038,0.064 -0.058,0.137 -0.058,0.211 0,0.074 0.02,0.147 0.058,0.211 0.037,0.064 0.092,0.117 0.157,0.153 l 5.303,2.999 c 0.513,0.291 1.094,0.443 1.685,0.443 0.591,0 1.172,-0.152 1.685,-0.443 l 11.448,-6.49 c 0.388,-0.22 0.71,-0.537 0.934,-0.92 0.225,-0.383 0.343,-0.817 0.343,-1.259 v -13.068 c 0,-0.588 -0.157,-1.165 -0.454,-1.674 -0.297,-0.509 -0.725,-0.932 -1.24,-1.226 L 50.594,98.194 c -0.129,-0.074 -0.275,-0.113 -0.424,-0.113 -0.149,0 -0.295,0.039 -0.424,0.112 -0.129,0.074 -0.236,0.179 -0.31,0.307 -0.074,0.127 -0.113,0.272 -0.113,0.419 z m 10.38,18.031 -7.416,-4.251 c -0.128,-0.073 -0.235,-0.179 -0.309,-0.306 -0.074,-0.127 -0.113,-0.272 -0.113,-0.418 v -5.573 c 0,-0.074 0.019,-0.147 0.056,-0.21 0.038,-0.064 0.091,-0.117 0.156,-0.154 0.064,-0.037 0.137,-0.057 0.212,-0.057 0.074,0 0.148,0.019 0.212,0.056 l 6.15,3.522 c 0.513,0.295 0.94,0.717 1.236,1.226 0.296,0.508 0.452,1.085 0.452,1.671 v 4.129 c 0.001,0.055 -0.01,0.11 -0.031,0.161 -0.022,0.051 -0.053,0.097 -0.092,0.136 -0.039,0.039 -0.086,0.07 -0.137,0.091 -0.052,0.021 -0.107,0.032 -0.162,0.032 -0.075,0 -0.149,-0.019 -0.214,-0.057 z"
|
||||
id="path42"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#b1c0e1" />
|
||||
<path
|
||||
d="m 45.289,122.031 -1.899,1.065 -0.014,-0.008 -2.95,1.695 c -0.129,0.074 -0.275,0.113 -0.425,0.113 -0.149,0 -0.296,-0.039 -0.425,-0.113 l -3.815,-2.186 c -0.51,-0.294 -0.934,-0.714 -1.229,-1.22 l 2.133,-1.22 c 0.037,0.068 0.092,0.125 0.159,0.164 l 2.12,1.226 c 0.064,0.036 0.137,0.056 0.212,0.056 0.074,0 0.147,-0.02 0.211,-0.056 l 0.027,-0.016 c 0.065,-0.036 0.119,-0.089 0.157,-0.153 0.037,-0.064 0.057,-0.137 0.057,-0.211 0,-0.074 -0.02,-0.146 -0.057,-0.21 -0.038,-0.064 -0.092,-0.117 -0.157,-0.154 l -1.724,-0.988 c -0.063,-0.037 -0.116,-0.088 -0.152,-0.151 l 2.53,-1.446 c 0.037,0.071 0.094,0.13 0.164,0.17 l 5.08,2.912 c 0.065,0.036 0.119,0.089 0.157,0.153 0.037,0.064 0.057,0.137 0.057,0.211 0,0.074 -0.02,0.146 -0.057,0.21 -0.038,0.064 -0.092,0.117 -0.157,0.154 z"
|
||||
id="path44"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#b1c0e1" />
|
||||
<path
|
||||
d="m 62.551,121.433 c -0.223,0.393 -0.551,0.72 -0.947,0.944 l -11.445,6.503 c -0.512,0.29 -1.093,0.443 -1.685,0.443 -0.591,0 -1.172,-0.153 -1.684,-0.443 l -5.303,-3.006 c -0.065,-0.037 -0.118,-0.09 -0.156,-0.154 -0.037,-0.064 -0.057,-0.137 -0.057,-0.211 0,-0.073 0.02,-0.146 0.057,-0.21 0.038,-0.064 0.091,-0.117 0.156,-0.154 l 6.991,-4.011 1.908,-1.093 c 0.065,-0.036 0.119,-0.089 0.157,-0.153 0.037,-0.064 0.057,-0.137 0.057,-0.211 0,-0.074 -0.02,-0.147 -0.057,-0.211 -0.038,-0.064 -0.092,-0.117 -0.157,-0.153 l -1.908,-1.093 -2.757,-1.579 c -0.065,-0.037 -0.119,-0.09 -0.157,-0.154 -0.037,-0.064 -0.057,-0.137 -0.057,-0.211 0,-0.074 0.02,-0.146 0.057,-0.21 0.038,-0.064 0.092,-0.117 0.157,-0.154 l 2.757,-1.584 0.637,-0.365 c 0.058,-0.033 0.107,-0.08 0.143,-0.137 z"
|
||||
id="path46"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#b1c0e1" />
|
||||
<path
|
||||
d="m 48.701,120.034 -0.227,0.134 -1.267,0.754 c -0.129,0.077 -0.277,0.118 -0.429,0.119 -0.151,0.001 -0.3,-0.037 -0.431,-0.112 l -5.285,-3.031 C 41.03,117.879 41,117.856 40.973,117.83 c -0.025,-0.025 -0.047,-0.054 -0.065,-0.085 l 1.163,-0.668 1.919,-1.098 c -0.014,0.011 -0.028,0.023 -0.04,0.036 -0.013,0.012 -0.025,0.025 -0.035,0.039 -0.002,0.002 -0.004,0.005 -0.005,0.008 -0.009,0.011 -0.017,0.023 -0.024,0.035 -0.003,0.005 -0.006,0.01 -0.008,0.016 -0.005,0.008 -0.009,0.017 -0.012,0.024 -0.004,0.008 -0.008,0.017 -0.01,0.025 -0.004,0.01 -0.007,0.019 -0.01,0.029 -0.002,0.005 -0.004,0.01 -0.004,0.015 -0.003,0.013 -0.006,0.025 -0.007,0.038 -0.013,0.084 0,0.169 0.037,0.245 0.037,0.076 0.096,0.139 0.17,0.18 l 4.438,2.513 0.218,0.121 c 0.098,0.055 0.169,0.146 0.199,0.253 0.03,0.108 0.015,0.222 -0.041,0.318 -0.036,0.066 -0.09,0.121 -0.155,0.16 z"
|
||||
id="path48"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#b1c0e1" />
|
||||
<path
|
||||
d="m 73.728,105.767 v 15.648 c 0,0.055 -0.011,0.11 -0.032,0.161 -0.022,0.05 -0.053,0.096 -0.092,0.135 -0.039,0.039 -0.086,0.07 -0.137,0.091 -0.052,0.021 -0.107,0.032 -0.162,0.032 h -5.14 c -0.735,10e-4 -1.441,-0.286 -1.963,-0.798 -0.251,-0.249 -0.451,-0.543 -0.59,-0.866 -0.147,-0.341 -0.22,-0.709 -0.216,-1.08 v -13.323 c 0,-0.055 0.011,-0.109 0.032,-0.16 0.021,-0.051 0.052,-0.097 0.091,-0.136 0.04,-0.039 0.086,-0.07 0.138,-0.091 0.051,-0.021 0.106,-0.032 0.162,-0.032 h 1.923 c 0.056,0 0.111,0.011 0.162,0.032 0.052,0.021 0.098,0.052 0.138,0.091 0.039,0.039 0.07,0.085 0.091,0.136 0.021,0.051 0.032,0.105 0.032,0.16 v 12.904 c 0,0.055 0.011,0.109 0.032,0.16 0.021,0.051 0.052,0.097 0.092,0.136 0.039,0.039 0.086,0.069 0.137,0.091 0.051,0.021 0.106,0.032 0.162,0.032 h 1.925 c 0.055,0 0.11,-0.011 0.162,-0.032 0.051,-0.022 0.098,-0.052 0.137,-0.091 0.039,-0.039 0.07,-0.085 0.092,-0.136 0.021,-0.051 0.032,-0.105 0.032,-0.16 v -12.904 c 0,-0.055 0.01,-0.109 0.032,-0.16 0.021,-0.051 0.052,-0.097 0.091,-0.136 0.039,-0.039 0.086,-0.07 0.137,-0.091 0.052,-0.021 0.107,-0.032 0.162,-0.032 h 1.947 c 0.055,0 0.11,0.011 0.162,0.032 0.051,0.021 0.098,0.052 0.137,0.091 0.039,0.039 0.07,0.085 0.092,0.136 0.021,0.051 0.032,0.105 0.032,0.16 z"
|
||||
id="path50"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#b1c0e1" />
|
||||
<path
|
||||
d="m 83.421,108.087 v 4.116 c 0.004,0.371 -0.07,0.738 -0.216,1.08 -0.142,0.327 -0.346,0.625 -0.601,0.877 -0.257,0.254 -0.561,0.457 -0.897,0.596 -0.341,0.142 -0.708,0.215 -1.078,0.214 h -2.348 c -0.055,0 -0.11,0.011 -0.162,0.032 -0.051,0.021 -0.098,0.052 -0.137,0.091 -0.039,0.039 -0.07,0.085 -0.091,0.136 -0.022,0.05 -0.032,0.105 -0.032,0.16 v 6.022 c 0,0.055 -0.011,0.11 -0.032,0.16 -0.022,0.051 -0.053,0.097 -0.092,0.136 -0.039,0.039 -0.086,0.07 -0.137,0.091 -0.052,0.021 -0.107,0.032 -0.162,0.032 h -1.924 c -0.055,0 -0.11,-0.011 -0.162,-0.032 -0.051,-0.021 -0.098,-0.052 -0.137,-0.091 -0.039,-0.039 -0.07,-0.085 -0.092,-0.136 -0.021,-0.05 -0.032,-0.105 -0.032,-0.16 v -15.648 c 0,-0.055 0.011,-0.11 0.032,-0.16 0.022,-0.051 0.053,-0.097 0.092,-0.136 0.039,-0.039 0.086,-0.07 0.137,-0.091 0.052,-0.021 0.107,-0.032 0.162,-0.032 h 5.117 c 0.37,-0.002 0.737,0.071 1.078,0.214 0.333,0.137 0.638,0.336 0.897,0.584 0.259,0.249 0.463,0.548 0.601,0.877 0.144,0.338 0.218,0.702 0.216,1.068 z m -2.792,3.698 v -3.279 c 0,-0.055 -0.011,-0.11 -0.032,-0.161 -0.021,-0.05 -0.052,-0.096 -0.092,-0.135 -0.039,-0.039 -0.085,-0.07 -0.137,-0.091 -0.051,-0.021 -0.106,-0.032 -0.162,-0.032 h -1.925 c -0.055,0 -0.11,0.011 -0.162,0.032 -0.051,0.021 -0.098,0.052 -0.137,0.091 -0.039,0.039 -0.07,0.085 -0.091,0.135 -0.022,0.051 -0.032,0.106 -0.032,0.161 v 3.279 c 0,0.055 0.01,0.109 0.032,0.16 0.021,0.051 0.052,0.097 0.091,0.136 0.039,0.039 0.086,0.07 0.137,0.091 0.052,0.021 0.107,0.032 0.162,0.032 h 1.925 c 0.112,0 0.22,-0.044 0.299,-0.123 0.079,-0.078 0.124,-0.184 0.124,-0.294 z"
|
||||
id="path52"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#b1c0e1" />
|
||||
<path
|
||||
d="m 92.91,108.088 v 10.998 c 0.002,0.367 -0.071,0.73 -0.215,1.068 -0.139,0.33 -0.343,0.628 -0.601,0.877 -0.26,0.249 -0.564,0.447 -0.898,0.585 -0.341,0.142 -0.707,0.215 -1.078,0.214 h -2.77 c -0.364,0 -0.725,-0.071 -1.062,-0.208 -0.337,-0.138 -0.643,-0.34 -0.9,-0.595 -0.257,-0.255 -0.461,-0.558 -0.6,-0.891 -0.138,-0.333 -0.209,-0.69 -0.207,-1.05 v -1.652 c 0,-0.055 0.011,-0.109 0.032,-0.16 0.021,-0.051 0.052,-0.097 0.091,-0.136 0.04,-0.039 0.086,-0.069 0.138,-0.091 0.051,-0.021 0.106,-0.032 0.162,-0.032 h 1.923 c 0.056,0 0.111,0.011 0.162,0.032 0.052,0.022 0.098,0.052 0.137,0.091 0.04,0.039 0.071,0.085 0.092,0.136 0.021,0.051 0.032,0.105 0.032,0.16 v 1.232 c 0,0.055 0.011,0.109 0.032,0.16 0.021,0.051 0.052,0.097 0.092,0.136 0.039,0.039 0.085,0.07 0.137,0.091 0.051,0.021 0.106,0.032 0.162,0.032 h 1.925 c 0.055,0 0.11,-0.011 0.162,-0.032 0.051,-0.021 0.098,-0.052 0.137,-0.091 0.039,-0.039 0.07,-0.085 0.091,-0.136 0.022,-0.051 0.033,-0.105 0.032,-0.16 v -3.279 c 0.001,-0.055 -0.01,-0.11 -0.032,-0.16 -0.021,-0.051 -0.052,-0.097 -0.091,-0.136 -0.039,-0.039 -0.086,-0.07 -0.137,-0.091 -0.052,-0.021 -0.107,-0.032 -0.162,-0.032 h -2.348 c -0.37,10e-4 -0.737,-0.071 -1.078,-0.214 -0.333,-0.137 -0.635,-0.34 -0.886,-0.596 -0.249,-0.255 -0.449,-0.552 -0.59,-0.877 -0.146,-0.342 -0.219,-0.709 -0.215,-1.08 v -4.116 c -0.002,-0.36 0.069,-0.717 0.207,-1.05 0.139,-0.333 0.343,-0.635 0.6,-0.89 0.257,-0.255 0.563,-0.457 0.9,-0.595 0.337,-0.138 0.698,-0.209 1.062,-0.208 h 2.77 c 0.371,-0.002 0.737,0.071 1.078,0.214 0.334,0.137 0.638,0.336 0.898,0.584 0.258,0.249 0.462,0.548 0.601,0.877 0.144,0.339 0.217,0.703 0.215,1.071 z m -2.792,3.697 v -3.279 c 0.001,-0.055 -0.01,-0.109 -0.032,-0.16 -0.021,-0.051 -0.052,-0.097 -0.091,-0.136 -0.039,-0.039 -0.086,-0.07 -0.137,-0.091 -0.052,-0.021 -0.107,-0.032 -0.162,-0.032 h -1.925 c -0.056,0 -0.111,0.011 -0.162,0.032 -0.052,0.021 -0.098,0.052 -0.137,0.091 -0.04,0.039 -0.071,0.085 -0.092,0.136 -0.021,0.051 -0.032,0.105 -0.032,0.16 v 3.279 c 0,0.055 0.011,0.11 0.032,0.16 0.021,0.051 0.052,0.097 0.092,0.136 0.039,0.039 0.085,0.07 0.137,0.091 0.051,0.021 0.106,0.032 0.162,0.032 h 1.925 c 0.112,0 0.219,-0.044 0.298,-0.122 0.079,-0.078 0.124,-0.184 0.124,-0.295 z"
|
||||
id="path54"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#b1c0e1" />
|
||||
<path
|
||||
d="m 2.375,119.861 v -15.332 h 2.88 v 5.522 c 0.888,-1.032 1.94,-1.548 3.156,-1.548 1.326,0 2.422,0.492 3.29,1.475 0.868,0.976 1.302,2.381 1.302,4.215 0,1.896 -0.445,3.357 -1.333,4.381 -0.881,1.025 -1.954,1.538 -3.218,1.538 -0.622,0 -1.237,-0.157 -1.845,-0.471 -0.601,-0.321 -1.12,-0.791 -1.557,-1.412 v 1.632 z m 2.859,-5.794 c 0,1.15 0.178,2.001 0.533,2.552 0.499,0.781 1.162,1.171 1.988,1.171 0.636,0 1.175,-0.275 1.62,-0.826 0.45,-0.558 0.676,-1.433 0.676,-2.625 0,-1.269 -0.226,-2.182 -0.676,-2.74 -0.451,-0.565 -1.029,-0.847 -1.732,-0.847 -0.691,0 -1.264,0.275 -1.722,0.826 -0.458,0.544 -0.687,1.373 -0.687,2.489 z"
|
||||
id="path56"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#b1c0e0" />
|
||||
<path
|
||||
d="m 13.966,108.754 h 3.064 l 2.603,7.886 2.542,-7.886 h 2.982 l -3.843,10.688 -0.687,1.935 c -0.252,0.649 -0.495,1.144 -0.727,1.485 -0.226,0.342 -0.489,0.617 -0.789,0.826 -0.294,0.217 -0.66,0.384 -1.097,0.502 -0.43,0.119 -0.919,0.178 -1.465,0.178 -0.554,0 -1.097,-0.059 -1.63,-0.178 l -0.256,-2.3 c 0.451,0.09 0.857,0.136 1.219,0.136 0.67,0 1.165,-0.203 1.486,-0.607 0.322,-0.397 0.567,-0.906 0.738,-1.527 z"
|
||||
id="path58"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#b1c0e0" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 20 KiB |
BIN
ui/src/components/assets/authBackground.png
Normal file
BIN
ui/src/components/assets/authBackground.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 137 KiB |
53
ui/src/components/style/AuthBasePage.sass
Normal file
53
ui/src/components/style/AuthBasePage.sass
Normal file
@@ -0,0 +1,53 @@
|
||||
@import "../../variables.module"
|
||||
|
||||
.authContainer
|
||||
height: 100vh
|
||||
width: 100vw
|
||||
background-size: cover
|
||||
float: left
|
||||
|
||||
.authHeader
|
||||
margin: 80px 0 120px 0
|
||||
width: 100%
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
|
||||
.centeredForm
|
||||
background-color: $main-background-color
|
||||
border-radius: 5px
|
||||
box-shadow: 0 0 20px -4px $light-blue-color
|
||||
|
||||
padding: 35px
|
||||
max-width: 350px
|
||||
text-align: center
|
||||
display: flex
|
||||
flex-direction: column
|
||||
margin: 0 auto
|
||||
|
||||
.form-button
|
||||
margin-top: 25px !important
|
||||
padding: 14px 0 !important
|
||||
.MuiButton-label
|
||||
text-transform: none !important
|
||||
|
||||
.form-input
|
||||
margin-top: 20px !important
|
||||
input
|
||||
width: 100%
|
||||
box-sizing: border-box
|
||||
label
|
||||
float: left
|
||||
font-size: 12px
|
||||
opacity: 0.7
|
||||
margin-bottom: 4px
|
||||
|
||||
.form-title
|
||||
font-size: 28px
|
||||
color: $blue-gray
|
||||
font-weight: 600
|
||||
text-align: left
|
||||
|
||||
.form-subtitle
|
||||
text-align: left
|
||||
margin: 4% 0
|
||||
@@ -32,9 +32,8 @@
|
||||
fieldset
|
||||
border: none
|
||||
|
||||
$divider-breakpoint-1: 1474px
|
||||
$divider-breakpoint-2: 1366px
|
||||
$divider-breakpoint-3: 1980px
|
||||
$divider-breakpoint-1: 1055px
|
||||
$divider-breakpoint-2: 1453px
|
||||
|
||||
@media (max-width: $divider-breakpoint-1)
|
||||
.divider1
|
||||
@@ -43,7 +42,3 @@ $divider-breakpoint-3: 1980px
|
||||
@media (max-width: $divider-breakpoint-2)
|
||||
.divider2
|
||||
display: none
|
||||
|
||||
@media (min-width: $divider-breakpoint-1) and (max-width: $divider-breakpoint-3)
|
||||
.divider2
|
||||
display: none
|
||||
|
||||
@@ -38,8 +38,8 @@ export default class Api {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
getEntry = async (id) => {
|
||||
const response = await this.client.get(`/entries/${id}`);
|
||||
getEntry = async (id, query) => {
|
||||
const response = await this.client.get(`/entries/${id}?query=${query}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,5 +26,14 @@ export const useCommonStyles = makeStyles(() => ({
|
||||
"&:hover": {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
}
|
||||
},
|
||||
textField: {
|
||||
outline: 0,
|
||||
background: "white",
|
||||
borderRadius: "4px",
|
||||
padding: "8px 10px",
|
||||
border: "1px #9D9D9D solid",
|
||||
fontSize: "14px",
|
||||
color: "#494677"
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user