Compare commits

..

9 Commits

Author SHA1 Message Date
M. Mert Yıldıran
9771d689ca Fix the acceptance tests and a typo in CONFIGURATION.md (#610)
* Enable acceptance tests

* Fix the acceptance tests and a typo in `CONFIGURATION.md`

* Include the container name into the log fetching function

* Duplicate the fix for the logs test

* Revert "Enable acceptance tests"

This reverts commit c10a67c293.
2022-01-09 17:38:41 +03:00
Nimrod Gilboa Markevich
5a044875d3 Rename Istio to service mesh (#605)
- Rename --istio flag to the more general --service-mesh
- Rename internal variables, consts and structures to reflect this conceptual change
- Update the docs accordingly
2022-01-09 13:21:14 +02:00
RoyUP9
c49c344c2a Added kubernetes provider singleton (#599) 2022-01-09 10:50:58 +02:00
M. Mert Yıldıran
e3e9681110 Move Basenine binary into a separate container (#603)
* Move Basenine binary into a separate container

* Set `WorkingDir` to `shared.DataDirPath` in the `basenine` container

* Use `consts.go` to set the Basenine image and port

* Bring back the `net-wait-go` usage to prevent startup failures
2022-01-09 11:18:34 +03:00
RoyUP9
adf2274213 Added api server running check in install command (#596) 2022-01-06 14:58:28 +02:00
lirazyehezkel
cb5344090a TRA-4089 Mizu enterprise frame (#594)
* ent app

* mizu ent frame

* apis

* design settings modal by Javier

* fix warnings

* fix warnings

* text change

* redirect after logout

* cr fixes
2022-01-06 14:50:50 +02:00
RamiBerm
2110afc514 TRA-4075 fix logout (#595) 2022-01-06 14:01:14 +02:00
RoyUP9
2c4a5d06ab Init tapped namespaces map (#593) 2022-01-06 13:21:58 +02:00
Igor Gov
14650aa3f4 Updating mizu base docker image alpine 3.14 -> 3.15 (#591)
Co-authored-by: Igor Gov <igor.govorov1@gmail.com>
2022-01-06 12:47:50 +02:00
49 changed files with 1163 additions and 231 deletions

View File

@@ -41,16 +41,10 @@ RUN go build -ldflags="-s -w \
-X 'mizuserver/pkg/version.BuildTimestamp=${BUILD_TIMESTAMP}' \
-X 'mizuserver/pkg/version.SemVer=${SEM_VER}'" -o mizuagent .
# Download Basenine executable, verify the sha1sum and move it to a directory in $PATH
ADD https://github.com/up9inc/basenine/releases/download/v0.2.19/basenine_linux_amd64 ./basenine_linux_amd64
ADD https://github.com/up9inc/basenine/releases/download/v0.2.19/basenine_linux_amd64.sha256 ./basenine_linux_amd64.sha256
RUN shasum -a 256 -c basenine_linux_amd64.sha256
RUN chmod +x ./basenine_linux_amd64
COPY devops/build_extensions.sh ..
RUN cd .. && /bin/bash build_extensions.sh
FROM alpine:3.14
FROM alpine:3.15
RUN apk add bash libpcap-dev
@@ -58,7 +52,6 @@ WORKDIR /app
# Copy binary and config files from /build to root folder of scratch container.
COPY --from=builder ["/app/agent-build/mizuagent", "."]
COPY --from=builder ["/app/agent-build/basenine_linux_amd64", "/usr/local/bin/basenine"]
COPY --from=builder ["/app/agent/build/extensions", "extensions"]
COPY --from=site-build ["/app/ui-build/build", "site"]
RUN mkdir /app/data/

View File

@@ -81,11 +81,16 @@ func TestLogs(t *testing.T) {
logsFileNames = append(logsFileNames, file.Name)
}
if !Contains(logsFileNames, "mizu.mizu-api-server.log") {
if !Contains(logsFileNames, "mizu.mizu-api-server.mizu-api-server.log") {
t.Errorf("api server logs not found")
return
}
if !Contains(logsFileNames, "mizu.mizu-api-server.basenine.log") {
t.Errorf("basenine logs not found")
return
}
if !Contains(logsFileNames, "mizu_cli.log") {
t.Errorf("cli logs not found")
return
@@ -174,11 +179,16 @@ func TestLogsPath(t *testing.T) {
logsFileNames = append(logsFileNames, file.Name)
}
if !Contains(logsFileNames, "mizu.mizu-api-server.log") {
if !Contains(logsFileNames, "mizu.mizu-api-server.mizu-api-server.log") {
t.Errorf("api server logs not found")
return
}
if !Contains(logsFileNames, "mizu.mizu-api-server.basenine.log") {
t.Errorf("basenine logs not found")
return
}
if !Contains(logsFileNames, "mizu_cli.log") {
t.Errorf("cli logs not found")
return

View File

@@ -862,11 +862,16 @@ func TestTapDumpLogs(t *testing.T) {
logsFileNames = append(logsFileNames, file.Name)
}
if !Contains(logsFileNames, "mizu.mizu-api-server.log") {
if !Contains(logsFileNames, "mizu.mizu-api-server.mizu-api-server.log") {
t.Errorf("api server logs not found")
return
}
if !Contains(logsFileNames, "mizu.mizu-api-server.basenine.log") {
t.Errorf("basenine logs not found")
return
}
if !Contains(logsFileNames, "mizu_cli.log") {
t.Errorf("cli logs not found")
return

View File

@@ -92,7 +92,7 @@ func getDefaultCommandArgs() []string {
setFlag := "--set"
telemetry := "telemetry=false"
agentImage := "agent-image=gcr.io/up9-docker-hub/mizu/ci:0.0.0"
imagePullPolicy := "image-pull-policy=Never"
imagePullPolicy := "image-pull-policy=IfNotPresent"
headless := "headless=true"
return []string{setFlag, telemetry, setFlag, agentImage, setFlag, imagePullPolicy, setFlag, headless}

View File

@@ -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-20211215185650-10083bb9a1b3
github.com/up9inc/basenine/client/go v0.0.0-20220107003657-7c0578359920
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

View File

@@ -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-20211215185650-10083bb9a1b3 h1:FeDCVOBFVpZA5/O5hfPdGTn0rdR2jTEYo3iB2htELI4=
github.com/up9inc/basenine/client/go v0.0.0-20211215185650-10083bb9a1b3/go.mod h1:SvJGPoa/6erhUQV7kvHBwM/0x5LyO6XaG2lUaCaKiUI=
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/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=

View File

@@ -16,7 +16,6 @@ import (
"mizuserver/pkg/utils"
"net/http"
"os"
"os/exec"
"os/signal"
"path"
"path/filepath"
@@ -115,7 +114,7 @@ func main() {
go pipeTapChannelToSocket(socketConnection, filteredOutputItemsChannel)
} else if *apiServerMode {
startBasenineServer(shared.BasenineHost, shared.BaseninePort)
configureBasenineServer(shared.BasenineHost, shared.BaseninePort)
startTime = time.Now().UnixNano() / int64(time.Millisecond)
api.StartResolving(*namespace)
@@ -149,16 +148,7 @@ func main() {
logger.Log.Info("Exiting")
}
func startBasenineServer(host string, port string) {
cmd := exec.Command("basenine", "-addr", host, "-port", port, "-persistent")
cmd.Dir = config.Config.AgentDatabasePath
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
if err != nil {
logger.Log.Panicf("Failed starting Basenine: %v", err)
}
func configureBasenineServer(host string, port string) {
if !wait.New(
wait.WithProto("tcp"),
wait.WithWait(200*time.Millisecond),
@@ -166,25 +156,16 @@ func startBasenineServer(host string, port string) {
wait.WithDeadline(5*time.Second),
wait.WithDebug(true),
).Do([]string{fmt.Sprintf("%s:%s", host, port)}) {
logger.Log.Panicf("Basenine is not available: %v", err)
logger.Log.Panicf("Basenine is not available!")
}
// Make a channel to gracefully exit Basenine.
channel := make(chan os.Signal)
signal.Notify(channel, os.Interrupt, syscall.SIGTERM)
// Handle the channel.
go func() {
<-channel
cmd.Process.Signal(syscall.SIGTERM)
}()
// Limit the database size to default 200MB
err = basenine.Limit(host, port, config.Config.MaxDBSizeBytes)
err := basenine.Limit(host, port, config.Config.MaxDBSizeBytes)
if err != nil {
logger.Log.Panicf("Error while limiting database size: %v", err)
}
// Define the macros
for _, extension := range extensions {
macros := extension.Dissector.Macros()
for macro, expanded := range macros {

View File

@@ -16,7 +16,7 @@ import (
"time"
)
var globalTapConfig = &models.TapConfig{}
var globalTapConfig = &models.TapConfig{TappedNamespaces: make(map[string]bool)}
var cancelTapperSyncer context.CancelFunc
func PostTapConfig(c *gin.Context) {
@@ -45,7 +45,7 @@ func PostTapConfig(c *gin.Context) {
podRegex, _ := regexp.Compile(".*")
kubernetesProvider, err := kubernetes.NewProviderInCluster()
kubernetesProvider, err := providers.GetKubernetesProvider()
if err != nil {
c.JSON(http.StatusInternalServerError, err)
return
@@ -66,7 +66,7 @@ func PostTapConfig(c *gin.Context) {
}
func GetTapConfig(c *gin.Context) {
kubernetesProvider, err := kubernetes.NewProviderInCluster()
kubernetesProvider, err := providers.GetKubernetesProvider()
if err != nil {
c.JSON(http.StatusInternalServerError, err)
return
@@ -94,7 +94,7 @@ func GetTapConfig(c *gin.Context) {
c.JSON(http.StatusOK, tapConfig)
}
func startMizuTapperSyncer(ctx context.Context, provider *kubernetes.Provider, targetNamespaces []string, podFilterRegex regexp.Regexp, ignoredUserAgents []string, mizuApiFilteringOptions tapApi.TrafficFilteringOptions, istio bool) (*kubernetes.MizuTapperSyncer, error) {
func startMizuTapperSyncer(ctx context.Context, provider *kubernetes.Provider, targetNamespaces []string, podFilterRegex regexp.Regexp, ignoredUserAgents []string, mizuApiFilteringOptions tapApi.TrafficFilteringOptions, serviceMesh bool) (*kubernetes.MizuTapperSyncer, error) {
tapperSyncer, err := kubernetes.CreateAndStartMizuTapperSyncer(ctx, provider, kubernetes.TapperSyncerConfig{
TargetNamespaces: targetNamespaces,
PodFilterRegex: podFilterRegex,
@@ -106,7 +106,7 @@ func startMizuTapperSyncer(ctx context.Context, provider *kubernetes.Provider, t
IgnoredUserAgents: ignoredUserAgents,
MizuApiFilteringOptions: mizuApiFilteringOptions,
MizuServiceAccountExists: true, //assume service account exists since install mode will not function without it anyway
Istio: istio,
ServiceMesh: serviceMesh,
}, time.Now())
if err != nil {

View File

@@ -18,7 +18,7 @@ func Login(c *gin.Context) {
func Logout(c *gin.Context) {
token := c.GetHeader("x-session-token")
if err := providers.Logout(token, c.Request.Context()); err != nil {
c.AbortWithStatusJSON(401, gin.H{"error": "error occured while logging out, the session might still be valid"})
c.AbortWithStatusJSON(500, gin.H{"error": "error occured while logging out, the session might still be valid"})
} else {
c.JSON(200, "")
}

View File

@@ -0,0 +1,27 @@
package providers
import (
"github.com/up9inc/mizu/shared/kubernetes"
"sync"
)
var lock = &sync.Mutex{}
var kubernetesProvider *kubernetes.Provider
func GetKubernetesProvider() (*kubernetes.Provider, error) {
if kubernetesProvider == nil {
lock.Lock()
defer lock.Unlock()
if kubernetesProvider == nil {
var err error
kubernetesProvider, err = kubernetes.NewProviderInCluster()
if err != nil {
return nil, err
}
}
}
return kubernetesProvider, nil
}

View File

@@ -108,7 +108,7 @@ func AnyUserExists(ctx context.Context) (bool, error) {
func Logout(token string, ctx context.Context) error {
logoutRequest := client.V0alpha2Api.SubmitSelfServiceLogoutFlowWithoutBrowser(ctx)
logoutRequest.SubmitSelfServiceLogoutFlowWithoutBrowserBody(ory.SubmitSelfServiceLogoutFlowWithoutBrowserBody{
logoutRequest = logoutRequest.SubmitSelfServiceLogoutFlowWithoutBrowserBody(ory.SubmitSelfServiceLogoutFlowWithoutBrowserBody{
SessionToken: token,
})
if response, err := logoutRequest.Execute(); err != nil {

View File

@@ -4,6 +4,10 @@ import (
"context"
"errors"
"fmt"
"github.com/up9inc/mizu/shared/kubernetes"
core "k8s.io/api/core/v1"
"regexp"
"time"
"github.com/creasty/defaults"
"github.com/up9inc/mizu/cli/config"
@@ -58,7 +62,21 @@ func runMizuInstall() {
return
}
logger.Log.Infof(uiUtils.Magenta, "Created Mizu Agent components, run `mizu view` to connect to the mizu daemon instance")
logger.Log.Infof("Waiting for Mizu server to start...")
readyChan := make(chan string)
readyErrorChan := make(chan error)
go watchApiServerPodReady(ctx, kubernetesProvider, readyChan, readyErrorChan)
select {
case readyMessage := <-readyChan:
logger.Log.Infof(readyMessage)
case err := <-readyErrorChan:
defer resources.CleanUpMizuResources(ctx, cancel, kubernetesProvider, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace)
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("%v", errormessage.FormatError(err)))
return
}
logger.Log.Infof(uiUtils.Magenta, "Installation completed, run `mizu view` to connect to the mizu daemon instance")
}
func getInstallMizuAgentConfig(maxDBSizeBytes int64, tapperResources shared.Resources) *shared.MizuAgentConfig {
@@ -75,3 +93,58 @@ func getInstallMizuAgentConfig(maxDBSizeBytes int64, tapperResources shared.Reso
return &mizuAgentConfig
}
func watchApiServerPodReady(ctx context.Context, kubernetesProvider *kubernetes.Provider, readyChan chan string, readyErrorChan chan error) {
podExactRegex := regexp.MustCompile(fmt.Sprintf("^%s.*", kubernetes.ApiServerPodName))
podWatchHelper := kubernetes.NewPodWatchHelper(kubernetesProvider, podExactRegex)
eventChan, errorChan := kubernetes.FilteredWatch(ctx, podWatchHelper, []string{config.Config.MizuResourcesNamespace}, podWatchHelper)
timeAfter := time.After(30 * time.Second)
for {
select {
case wEvent, ok := <-eventChan:
if !ok {
eventChan = nil
continue
}
switch wEvent.Type {
case kubernetes.EventAdded:
logger.Log.Debugf("Watching API Server pod ready loop, added")
case kubernetes.EventDeleted:
logger.Log.Debugf("Watching API Server pod ready loop, %s removed", kubernetes.ApiServerPodName)
case kubernetes.EventModified:
modifiedPod, err := wEvent.ToPod()
if err != nil {
readyErrorChan <- err
return
}
logger.Log.Debugf("Watching API Server pod ready loop, modified: %v", modifiedPod.Status.Phase)
if modifiedPod.Status.Phase == core.PodRunning {
readyChan <- fmt.Sprintf("%v pod is running", modifiedPod.Name)
return
}
case kubernetes.EventBookmark:
break
case kubernetes.EventError:
break
}
case err, ok := <-errorChan:
if !ok {
errorChan = nil
continue
}
readyErrorChan <- fmt.Errorf("[ERROR] Agent creation, watching %v namespace, error: %v", config.Config.MizuResourcesNamespace, err)
return
case <-timeAfter:
readyErrorChan <- fmt.Errorf("mizu API server was not ready in time")
return
case <-ctx.Done():
logger.Log.Debugf("Watching API Server pod ready loop, ctx done")
return
}
}
}

View File

@@ -119,5 +119,5 @@ func init() {
tapCmd.Flags().StringP(configStructs.WorkspaceTapName, "w", defaultTapConfig.Workspace, "Uploads traffic to your UP9 workspace for further analysis (requires auth)")
tapCmd.Flags().String(configStructs.EnforcePolicyFile, defaultTapConfig.EnforcePolicyFile, "Yaml file path with policy rules")
tapCmd.Flags().String(configStructs.ContractFile, defaultTapConfig.ContractFile, "OAS/Swagger file to validate to monitor the contracts")
tapCmd.Flags().Bool(configStructs.IstioName, defaultTapConfig.Istio, "Record decrypted traffic if the cluster configured with istio and mtls")
tapCmd.Flags().Bool(configStructs.ServiceMeshName, defaultTapConfig.ServiceMesh, "Record decrypted traffic if the cluster is configured with a service mesh and with mtls")
}

View File

@@ -192,7 +192,7 @@ func startTapperSyncer(ctx context.Context, cancel context.CancelFunc, provider
IgnoredUserAgents: config.Config.Tap.IgnoredUserAgents,
MizuApiFilteringOptions: mizuApiFilteringOptions,
MizuServiceAccountExists: state.mizuServiceAccountExists,
Istio: config.Config.Tap.Istio,
ServiceMesh: config.Config.Tap.ServiceMesh,
}, startTime)
if err != nil {

View File

@@ -22,7 +22,7 @@ const (
WorkspaceTapName = "workspace"
EnforcePolicyFile = "traffic-validation-file"
ContractFile = "contract"
IstioName = "istio"
ServiceMeshName = "service-mesh"
)
type TapConfig struct {
@@ -44,7 +44,7 @@ type TapConfig struct {
AskUploadConfirmation bool `yaml:"ask-upload-confirmation" default:"true"`
ApiServerResources shared.Resources `yaml:"api-server-resources"`
TapperResources shared.Resources `yaml:"tapper-resources"`
Istio bool `yaml:"istio" default:"false"`
ServiceMesh bool `yaml:"service-mesh" default:"false"`
}
func (config *TapConfig) PodRegex() *regexp.Regexp {

View File

@@ -38,18 +38,20 @@ func DumpLogs(ctx context.Context, provider *kubernetes.Provider, filePath strin
defer zipWriter.Close()
for _, pod := range pods {
logs, err := provider.GetPodLogs(ctx, pod.Namespace, pod.Name)
if err != nil {
logger.Log.Errorf("Failed to get logs, %v", err)
continue
} else {
logger.Log.Debugf("Successfully read log length %d for pod: %s.%s", len(logs), pod.Namespace, pod.Name)
}
for _, container := range pod.Spec.Containers {
logs, err := provider.GetPodLogs(ctx, pod.Namespace, pod.Name, container.Name)
if err != nil {
logger.Log.Errorf("Failed to get logs, %v", err)
continue
} else {
logger.Log.Debugf("Successfully read log length %d for pod: %s.%s.%s", len(logs), pod.Namespace, pod.Name, container.Name)
}
if err := AddStrToZip(zipWriter, logs, fmt.Sprintf("%s.%s.log", pod.Namespace, pod.Name)); err != nil {
logger.Log.Errorf("Failed write logs, %v", err)
} else {
logger.Log.Debugf("Successfully added log length %d from pod: %s.%s", len(logs), pod.Namespace, pod.Name)
if err := AddStrToZip(zipWriter, logs, fmt.Sprintf("%s.%s.%s.log", pod.Namespace, pod.Name, container.Name)); err != nil {
logger.Log.Errorf("Failed write logs, %v", err)
} else {
logger.Log.Debugf("Successfully added log length %d from pod: %s.%s.%s", len(logs), pod.Namespace, pod.Name, container.Name)
}
}
}

View File

@@ -36,12 +36,6 @@ COPY tap ../tap
COPY agent .
RUN go build -gcflags="all=-N -l" -o mizuagent .
# Download Basenine executable, verify the sha1sum and move it to a directory in $PATH
ADD https://github.com/up9inc/basenine/releases/download/v0.2.19/basenine_linux_amd64 ./basenine_linux_amd64
ADD https://github.com/up9inc/basenine/releases/download/v0.2.19/basenine_linux_amd64.sha256 ./basenine_linux_amd64.sha256
RUN shasum -a 256 -c basenine_linux_amd64.sha256
RUN chmod +x ./basenine_linux_amd64
COPY devops/build_extensions_debug.sh ..
RUN cd .. && /bin/bash build_extensions_debug.sh
@@ -54,7 +48,6 @@ WORKDIR /app
# Copy binary and config files from /build to root folder of scratch container.
COPY --from=builder ["/app/agent-build/mizuagent", "."]
COPY --from=builder ["/app/agent-build/basenine_linux_amd64", "/usr/local/bin/basenine"]
COPY --from=builder ["/app/agent/build/extensions", "extensions"]
COPY --from=site-build ["/app/ui-build/build", "site"]

View File

@@ -25,7 +25,7 @@ Please make sure to use full option name (`tap.dry-run` as opposed to `dry-run`
* `dump-logs` - if set to `true`, saves log files for all Mizu components (tapper, api-server, CLI) in a zip file under `$HOME/.mizu`. Default value is `false`
* `image-pull-policy` - container image pull policy for Kubernetes, default value `Always`. Other accepted values are `Never` or `IfNotExist`. Please mind the implications when changing this.
* `image-pull-policy` - container image pull policy for Kubernetes, default value `Always`. Other accepted values are `Never` or `IfNotPresent`. Please mind the implications when changing this.
* `kube-config-path` - path to alternative kubeconfig file to use for all interactions with Kubernetes cluster. By default - `$HOME/.kubeconfig`

View File

@@ -1,37 +1,44 @@
![Mizu: The API Traffic Viewer for Kubernetes](../assets/mizu-logo.svg)
# Istio mutual tls (mtls) with Mizu
# 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.
Besides Istio there are other service meshes that implement mtls. However, as of now Istio is the most used one, and this is why we are focusing on it.
The list of service meshes supported by Mizu include:
In order to create an Istio setup for development, follow those steps:
- 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 Istio to the cluster - make sure it is attached to all pods of the sample application, and that it is configured with mtls (default)
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
## The connection between Istio and Envoy
In order to implement its service mesh capabilities, [Istio](https://istio.io) use 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.
## 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
#### 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
#### 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.
[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
#### 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
#### 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)
@@ -42,5 +49,5 @@ 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
#### 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.

View File

@@ -14,6 +14,8 @@ const (
GoGCEnvVar = "GOGC"
DefaultApiServerPort = 8899
LogLevelEnvVar = "LOG_LEVEL"
BasenineHost = "localhost"
BasenineHost = "127.0.0.1"
BaseninePort = "9099"
BasenineImageRepo = "ghcr.io/up9inc/basenine"
BasenineImageTag = "v0.2.26"
)

View File

@@ -44,7 +44,7 @@ type TapperSyncerConfig struct {
IgnoredUserAgents []string
MizuApiFilteringOptions api.TrafficFilteringOptions
MizuServiceAccountExists bool
Istio bool
ServiceMesh bool
}
func CreateAndStartMizuTapperSyncer(ctx context.Context, kubernetesProvider *Provider, config TapperSyncerConfig, startTime time.Time) (*MizuTapperSyncer, error) {
@@ -316,7 +316,7 @@ func (tapperSyncer *MizuTapperSyncer) updateMizuTappers() error {
tapperSyncer.config.ImagePullPolicy,
tapperSyncer.config.MizuApiFilteringOptions,
tapperSyncer.config.LogLevel,
tapperSyncer.config.Istio,
tapperSyncer.config.ServiceMesh,
); err != nil {
return err
}

View File

@@ -278,6 +278,36 @@ func (provider *Provider) GetMizuApiServerPodObject(opts *ApiServerOptions, moun
},
},
},
{
Name: "basenine",
Image: fmt.Sprintf("%s:%s", shared.BasenineImageRepo, shared.BasenineImageTag),
ImagePullPolicy: opts.ImagePullPolicy,
VolumeMounts: volumeMounts,
ReadinessProbe: &core.Probe{
FailureThreshold: 3,
Handler: core.Handler{
TCPSocket: &core.TCPSocketAction{
Port: intstr.Parse(shared.BaseninePort),
},
},
PeriodSeconds: 1,
SuccessThreshold: 1,
TimeoutSeconds: 1,
},
Resources: core.ResourceRequirements{
Limits: core.ResourceList{
"cpu": cpuLimit,
"memory": memLimit,
},
Requests: core.ResourceList{
"cpu": cpuRequests,
"memory": memRequests,
},
},
Command: []string{"/basenine"},
Args: []string{"-addr", "0.0.0.0", "-port", shared.BaseninePort, "-persistent"},
WorkingDir: shared.DataDirPath,
},
}
if createAuthContainer {
@@ -690,7 +720,7 @@ func (provider *Provider) CreateConfigMap(ctx context.Context, namespace string,
return nil
}
func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespace string, daemonSetName string, podImage string, tapperPodName string, apiServerPodIp string, nodeToTappedPodMap map[string][]core.Pod, serviceAccountName string, resources shared.Resources, imagePullPolicy core.PullPolicy, mizuApiFilteringOptions api.TrafficFilteringOptions, logLevel logging.Level, istio bool) error {
func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespace string, daemonSetName string, podImage string, tapperPodName string, apiServerPodIp string, nodeToTappedPodMap map[string][]core.Pod, serviceAccountName string, resources shared.Resources, imagePullPolicy core.PullPolicy, mizuApiFilteringOptions api.TrafficFilteringOptions, logLevel logging.Level, serviceMesh bool) error {
logger.Log.Debugf("Applying %d tapper daemon sets, ns: %s, daemonSetName: %s, podImage: %s, tapperPodName: %s", len(nodeToTappedPodMap), namespace, daemonSetName, podImage, tapperPodName)
if len(nodeToTappedPodMap) == 0 {
@@ -715,8 +745,8 @@ func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespac
"--nodefrag",
}
if istio {
mizuCmd = append(mizuCmd, "--procfs", procfsMountPath, "--istio")
if serviceMesh {
mizuCmd = append(mizuCmd, "--procfs", procfsMountPath, "--servicemesh")
}
agentContainer := applyconfcore.Container()
@@ -726,7 +756,7 @@ func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespac
caps := applyconfcore.Capabilities().WithDrop("ALL").WithAdd("NET_RAW").WithAdd("NET_ADMIN")
if istio {
if serviceMesh {
caps = caps.WithAdd("SYS_ADMIN") // for reading /proc/PID/net/ns
caps = caps.WithAdd("SYS_PTRACE") // for setting netns to other process
caps = caps.WithAdd("DAC_OVERRIDE") // for reading /proc/PID/environ
@@ -913,8 +943,8 @@ func (provider *Provider) ListAllNamespaces(ctx context.Context) ([]core.Namespa
return namespaces.Items, err
}
func (provider *Provider) GetPodLogs(ctx context.Context, namespace string, podName string) (string, error) {
podLogOpts := core.PodLogOptions{}
func (provider *Provider) GetPodLogs(ctx context.Context, namespace string, podName string, containerName string) (string, error) {
podLogOpts := core.PodLogOptions{Container: containerName}
req := provider.clientSet.CoreV1().Pods(namespace).GetLogs(podName, &podLogOpts)
podLogs, err := req.Stream(ctx)
if err != nil {

View File

@@ -52,7 +52,7 @@ var tstype = flag.String("timestamp_type", "", "Type of timestamps to use")
var promisc = flag.Bool("promisc", true, "Set promiscuous mode")
var staleTimeoutSeconds = flag.Int("staletimout", 120, "Max time in seconds to keep connections which don't transmit data")
var pids = flag.String("pids", "", "A comma separated list of PIDs to capture their network namespaces")
var istio = flag.Bool("istio", false, "Record decrypted traffic if the cluster configured with istio and mtls")
var servicemesh = flag.Bool("servicemesh", false, "Record decrypted traffic if the cluster is configured with a service mesh and with mtls")
var memprofile = flag.String("memprofile", "", "Write memory profile")
@@ -179,7 +179,7 @@ func initializePacketSources() error {
}
var err error
if packetSourceManager, err = source.NewPacketSourceManager(*procfs, *pids, *fname, *iface, *istio, tapTargets, behaviour); err != nil {
if packetSourceManager, err = source.NewPacketSourceManager(*procfs, *pids, *fname, *iface, *servicemesh, tapTargets, behaviour); err != nil {
return err
} else {
packetSourceManager.ReadPackets(!*nodefrag, mainPacketInputChan)

405
ui/package-lock.json generated
View File

@@ -1161,11 +1161,51 @@
"resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz",
"integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg=="
},
"@emotion/cache": {
"version": "11.7.1",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz",
"integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==",
"requires": {
"@emotion/memoize": "^0.7.4",
"@emotion/sheet": "^1.1.0",
"@emotion/utils": "^1.0.0",
"@emotion/weak-memoize": "^0.2.5",
"stylis": "4.0.13"
}
},
"@emotion/hash": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
},
"@emotion/is-prop-valid": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.1.tgz",
"integrity": "sha512-bW1Tos67CZkOURLc0OalnfxtSXQJMrAMV0jZTVGJUPSOd4qgjF3+tTD5CwJM13PHA8cltGW1WGbbvV9NpvUZPw==",
"requires": {
"@emotion/memoize": "^0.7.4"
}
},
"@emotion/memoize": {
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz",
"integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ=="
},
"@emotion/sheet": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz",
"integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g=="
},
"@emotion/utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.0.0.tgz",
"integrity": "sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA=="
},
"@emotion/weak-memoize": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
},
"@eslint/eslintrc": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.0.tgz",
@@ -1898,6 +1938,335 @@
"react-is": "^16.8.0 || ^17.0.0"
}
},
"@mui/base": {
"version": "5.0.0-alpha.62",
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.62.tgz",
"integrity": "sha512-ItmdSZwHKQbLbAsS3sWguR7OHqYqh2cYWahoVmHb13Kc6bMdmVUTY4x57IlDSU712B0yuA0Q/gPTq7xADKnFow==",
"requires": {
"@babel/runtime": "^7.16.3",
"@emotion/is-prop-valid": "^1.1.1",
"@mui/utils": "^5.2.3",
"@popperjs/core": "^2.4.4",
"clsx": "^1.1.1",
"prop-types": "^15.7.2",
"react-is": "^17.0.2"
},
"dependencies": {
"@babel/runtime": {
"version": "7.16.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz",
"integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
}
}
},
"@mui/icons-material": {
"version": "5.2.5",
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.2.5.tgz",
"integrity": "sha512-uQiUz+l0xy+2jExyKyU19MkMAR2F7bQFcsQ5hdqAtsB14Jw2zlmIAD55mV6f0NxKCut7Rx6cA3ZpfzlzAfoK8Q==",
"requires": {
"@babel/runtime": "^7.16.3"
},
"dependencies": {
"@babel/runtime": {
"version": "7.16.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz",
"integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
}
}
},
"@mui/material": {
"version": "5.2.6",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.2.6.tgz",
"integrity": "sha512-yF2bRqyJMo6bYXT7TPA9IU/XLaXHi47Xvmj8duQa5ha3bCpFMXLfGoZcAUl6ZDjjGEz1nCFS+c1qx219xD/aeQ==",
"requires": {
"@babel/runtime": "^7.16.3",
"@mui/base": "5.0.0-alpha.62",
"@mui/system": "^5.2.6",
"@mui/types": "^7.1.0",
"@mui/utils": "^5.2.3",
"@types/react-transition-group": "^4.4.4",
"clsx": "^1.1.1",
"csstype": "^3.0.10",
"hoist-non-react-statics": "^3.3.2",
"prop-types": "^15.7.2",
"react-is": "^17.0.2",
"react-transition-group": "^4.4.2"
},
"dependencies": {
"@babel/runtime": {
"version": "7.16.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz",
"integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"@types/react-transition-group": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz",
"integrity": "sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==",
"requires": {
"@types/react": "*"
}
},
"csstype": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz",
"integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA=="
},
"react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
},
"react-transition-group": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
"integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==",
"requires": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
}
}
}
},
"@mui/private-theming": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.2.3.tgz",
"integrity": "sha512-Lc1Cmu8lSsYZiXADi9PBb17Ho82ZbseHQujUFAcp6bCJ5x/d+87JYCIpCBMagPu/isRlFCwbziuXPmz7WOzJPQ==",
"requires": {
"@babel/runtime": "^7.16.3",
"@mui/utils": "^5.2.3",
"prop-types": "^15.7.2"
},
"dependencies": {
"@babel/runtime": {
"version": "7.16.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz",
"integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
}
}
},
"@mui/styled-engine": {
"version": "5.2.6",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.2.6.tgz",
"integrity": "sha512-bqAhli8eGS6v2qxivy2/4K0Ag8o//jsu1G2G6QcieFiT6y7oIF/nd/6Tvw6OSm3roOTifVQWNKwkt1yFWhGS+w==",
"requires": {
"@babel/runtime": "^7.16.3",
"@emotion/cache": "^11.7.1",
"prop-types": "^15.7.2"
},
"dependencies": {
"@babel/runtime": {
"version": "7.16.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz",
"integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
}
}
},
"@mui/styles": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@mui/styles/-/styles-5.2.3.tgz",
"integrity": "sha512-Art4qjlEI9H2h34mLL8s+CE9nWZWZbuJLbNpievaIM6DGuayz3DYkJHcH5mXJYFPhTNoe9IQYbpyKofjE0YVag==",
"requires": {
"@babel/runtime": "^7.16.3",
"@emotion/hash": "^0.8.0",
"@mui/private-theming": "^5.2.3",
"@mui/types": "^7.1.0",
"@mui/utils": "^5.2.3",
"clsx": "^1.1.1",
"csstype": "^3.0.10",
"hoist-non-react-statics": "^3.3.2",
"jss": "^10.8.2",
"jss-plugin-camel-case": "^10.8.2",
"jss-plugin-default-unit": "^10.8.2",
"jss-plugin-global": "^10.8.2",
"jss-plugin-nested": "^10.8.2",
"jss-plugin-props-sort": "^10.8.2",
"jss-plugin-rule-value-function": "^10.8.2",
"jss-plugin-vendor-prefixer": "^10.8.2",
"prop-types": "^15.7.2"
},
"dependencies": {
"@babel/runtime": {
"version": "7.16.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz",
"integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"csstype": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz",
"integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA=="
},
"jss": {
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.9.0.tgz",
"integrity": "sha512-YpzpreB6kUunQBbrlArlsMpXYyndt9JATbt95tajx0t4MTJJcCJdd4hdNpHmOIDiUJrF/oX5wtVFrS3uofWfGw==",
"requires": {
"@babel/runtime": "^7.3.1",
"csstype": "^3.0.2",
"is-in-browser": "^1.1.3",
"tiny-warning": "^1.0.2"
}
},
"jss-plugin-camel-case": {
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.9.0.tgz",
"integrity": "sha512-UH6uPpnDk413/r/2Olmw4+y54yEF2lRIV8XIZyuYpgPYTITLlPOsq6XB9qeqv+75SQSg3KLocq5jUBXW8qWWww==",
"requires": {
"@babel/runtime": "^7.3.1",
"hyphenate-style-name": "^1.0.3",
"jss": "10.9.0"
}
},
"jss-plugin-default-unit": {
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.9.0.tgz",
"integrity": "sha512-7Ju4Q9wJ/MZPsxfu4T84mzdn7pLHWeqoGd/D8O3eDNNJ93Xc8PxnLmV8s8ZPNRYkLdxZqKtm1nPQ0BM4JRlq2w==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.9.0"
}
},
"jss-plugin-global": {
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.9.0.tgz",
"integrity": "sha512-4G8PHNJ0x6nwAFsEzcuVDiBlyMsj2y3VjmFAx/uHk/R/gzJV+yRHICjT4MKGGu1cJq2hfowFWCyrr/Gg37FbgQ==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.9.0"
}
},
"jss-plugin-nested": {
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.9.0.tgz",
"integrity": "sha512-2UJnDrfCZpMYcpPYR16oZB7VAC6b/1QLsRiAutOt7wJaaqwCBvNsosLEu/fUyKNQNGdvg2PPJFDO5AX7dwxtoA==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.9.0",
"tiny-warning": "^1.0.2"
}
},
"jss-plugin-props-sort": {
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.9.0.tgz",
"integrity": "sha512-7A76HI8bzwqrsMOJTWKx/uD5v+U8piLnp5bvru7g/3ZEQOu1+PjHvv7bFdNO3DwNPC9oM0a//KwIJsIcDCjDzw==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.9.0"
}
},
"jss-plugin-rule-value-function": {
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.9.0.tgz",
"integrity": "sha512-IHJv6YrEf8pRzkY207cPmdbBstBaE+z8pazhPShfz0tZSDtRdQua5jjg6NMz3IbTasVx9FdnmptxPqSWL5tyJg==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "10.9.0",
"tiny-warning": "^1.0.2"
}
},
"jss-plugin-vendor-prefixer": {
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.9.0.tgz",
"integrity": "sha512-MbvsaXP7iiVdYVSEoi+blrW+AYnTDvHTW6I6zqi7JcwXdc6I9Kbm234nEblayhF38EftoenbM+5218pidmC5gA==",
"requires": {
"@babel/runtime": "^7.3.1",
"css-vendor": "^2.0.8",
"jss": "10.9.0"
}
}
}
},
"@mui/system": {
"version": "5.2.6",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.2.6.tgz",
"integrity": "sha512-PZ7bmpWOLikWgqn2zWv9/Xa7lxnRBOmfjoMH7c/IVYJs78W3971brXJ3xV9MEWWQcoqiYQeXzUJaNf4rFbKCBA==",
"requires": {
"@babel/runtime": "^7.16.3",
"@mui/private-theming": "^5.2.3",
"@mui/styled-engine": "^5.2.6",
"@mui/types": "^7.1.0",
"@mui/utils": "^5.2.3",
"clsx": "^1.1.1",
"csstype": "^3.0.10",
"prop-types": "^15.7.2"
},
"dependencies": {
"@babel/runtime": {
"version": "7.16.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz",
"integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"csstype": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz",
"integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA=="
}
}
},
"@mui/types": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.1.0.tgz",
"integrity": "sha512-Hh7ALdq/GjfIwLvqH3XftuY3bcKhupktTm+S6qRIDGOtPtRuq2L21VWzOK4p7kblirK0XgGVH5BLwa6u8z/6QQ=="
},
"@mui/utils": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.2.3.tgz",
"integrity": "sha512-sQujlajIS0zQKcGIS6tZR0L1R+ib26B6UtuEn+cZqwKHsPo3feuS+SkdscYBdcCdMbrZs4gj8WIJHl2z6tbSzQ==",
"requires": {
"@babel/runtime": "^7.16.3",
"@types/prop-types": "^15.7.4",
"@types/react-is": "^16.7.1 || ^17.0.0",
"prop-types": "^15.7.2",
"react-is": "^17.0.2"
},
"dependencies": {
"@babel/runtime": {
"version": "7.16.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz",
"integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"@types/prop-types": {
"version": "15.7.4",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz",
"integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ=="
},
"react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
}
}
},
"@nodelib/fs.scandir": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz",
@@ -1957,6 +2326,11 @@
}
}
},
"@popperjs/core": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.0.tgz",
"integrity": "sha512-zrsUxjLOKAzdewIDRWy9nsV1GQsKBCWaGwsZQlCgr6/q+vjyZhFgqedLfFBuI9anTPEUT4APq9Mu0SZBTzIcGQ=="
},
"@rollup/plugin-node-resolve": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz",
@@ -2479,6 +2853,14 @@
"@types/react": "*"
}
},
"@types/react-is": {
"version": "17.0.3",
"resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz",
"integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==",
"requires": {
"@types/react": "*"
}
},
"@types/react-transition-group": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.1.tgz",
@@ -4355,6 +4737,11 @@
}
}
},
"classnames": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
},
"clean-css": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz",
@@ -10689,6 +11076,19 @@
"object-visit": "^1.0.0"
}
},
"material-ui-popup-state": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/material-ui-popup-state/-/material-ui-popup-state-2.0.0.tgz",
"integrity": "sha512-1sbb9xpMs7OxG0SOGfGO0ZnwiLtqZSoXda0/AqqJkpouT4e0nADXutQtDJFMa9GUMNAODVDlYnNmfqM+MhFjsg==",
"requires": {
"@babel/runtime": "^7.12.5",
"@mui/icons-material": "^5.0.0",
"@mui/material": "^5.0.0",
"@mui/styles": "^5.0.0",
"classnames": "^2.2.6",
"prop-types": "^15.7.2"
}
},
"md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
@@ -15733,6 +16133,11 @@
}
}
},
"stylis": {
"version": "4.0.13",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz",
"integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag=="
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",

View File

@@ -18,6 +18,7 @@
"highlight.js": "^11.3.1",
"json-beautify": "^1.1.1",
"jsonpath": "^1.1.1",
"material-ui-popup-state": "^2.0.0",
"moment": "^2.29.1",
"node-sass": "^5.0.0",
"numeral": "^2.0.6",

View File

@@ -7,25 +7,6 @@ body
color: $font-color
width: 100%
.header
height: 60px
display: flex
align-items: center
padding: 5px 24px
justify-content: space-between
.title
letter-spacing: 2px
img
height: 45px
.description
margin-left: 10px
font-size: 11px
font-weight: bold
color: $light-blue-color
.centeredForm
max-width: 500px
text-align: center

View File

@@ -1,71 +1,16 @@
import React, {useEffect, useState} from 'react';
import React, {useState} from 'react';
import './App.sass';
import {TrafficPage} from "./components/TrafficPage";
import {TLSWarning} from "./components/TLSWarning/TLSWarning";
import {Header} from "./components/Header/Header";
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import Api from "./helpers/api";
import LoadingOverlay from './components/LoadingOverlay';
import LoginPage from './components/LoginPage';
import InstallPage from './components/InstallPage';
const api = Api.getInstance();
// TODO: move to state management
export enum Page {
Traffic,
Setup,
Login
}
// TODO: move to state management
export interface MizuContextModel {
page: Page;
setPage: (page: Page) => void;
}
// TODO: move to state management
export const MizuContext = React.createContext<MizuContextModel>(null);
import {TrafficPage} from "./components/TrafficPage";
const App = () => {
const [isLoading, setIsLoading] = useState(true);
const [analyzeStatus, setAnalyzeStatus] = useState(null);
const [showTLSWarning, setShowTLSWarning] = useState(false);
const [userDismissedTLSWarning, setUserDismissedTLSWarning] = useState(false);
const [addressesWithTLS, setAddressesWithTLS] = useState(new Set<string>());
const [page, setPage] = useState(Page.Traffic); // TODO: move to state management
const determinePage = async () => { // TODO: move to state management
if (window['isEnt'] !== true) {
setPage(Page.Traffic);
setIsLoading(false);
return;
}
try {
const isInstallNeeded = await api.isInstallNeeded();
if (isInstallNeeded) {
setPage(Page.Setup);
} else {
const isAuthNeeded = await api.isAuthenticationNeeded();
if(isAuthNeeded) {
setPage(Page.Login);
}
}
} catch (e) {
toast.error("Error occured while checking Mizu API status, see console for mode details");
console.error(e);
} finally {
setIsLoading(false);
}
}
useEffect(() => {
determinePage();
}, []);
const onTLSDetected = (destAddress: string) => {
addressesWithTLS.add(destAddress);
setAddressesWithTLS(new Set(addressesWithTLS));
@@ -75,49 +20,16 @@ const App = () => {
}
};
let pageComponent: any;
switch (page) { // TODO: move to state management / proper routing
case Page.Traffic:
pageComponent = <TrafficPage setAnalyzeStatus={setAnalyzeStatus} onTLSDetected={onTLSDetected}/>;
break;
case Page.Setup:
pageComponent = <InstallPage/>;
break;
case Page.Login:
pageComponent = <LoginPage/>;
break;
default:
pageComponent = <div>Unknown Error</div>;
}
if (isLoading) {
return <LoadingOverlay/>;
}
return (
<div className="mizuApp">
<MizuContext.Provider value={{page, setPage}}>
<Header analyzeStatus={analyzeStatus}/>
{pageComponent}
<TLSWarning showTLSWarning={showTLSWarning}
setShowTLSWarning={setShowTLSWarning}
addressesWithTLS={addressesWithTLS}
setAddressesWithTLS={setAddressesWithTLS}
userDismissedTLSWarning={userDismissedTLSWarning}
setUserDismissedTLSWarning={setUserDismissedTLSWarning}/>
</MizuContext.Provider>
<ToastContainer
position="bottom-right"
autoClose={5000}
hideProgressBar={false}
newestOnTop={false}
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
/>
<Header analyzeStatus={analyzeStatus}/>
<TrafficPage setAnalyzeStatus={setAnalyzeStatus} onTLSDetected={onTLSDetected}/>
<TLSWarning showTLSWarning={showTLSWarning}
setShowTLSWarning={setShowTLSWarning}
addressesWithTLS={addressesWithTLS}
setAddressesWithTLS={setAddressesWithTLS}
userDismissedTLSWarning={userDismissedTLSWarning}
setUserDismissedTLSWarning={setUserDismissedTLSWarning}/>
</div>
);
}

107
ui/src/EntApp.tsx Normal file
View File

@@ -0,0 +1,107 @@
import React, {useEffect, useState} from 'react';
import './App.sass';
import {TrafficPage} from "./components/TrafficPage";
import {TLSWarning} from "./components/TLSWarning/TLSWarning";
import {EntHeader} from "./components/Header/EntHeader";
import Api from "./helpers/api";
import {toast} from "react-toastify";
import InstallPage from "./components/InstallPage";
import LoginPage from "./components/LoginPage";
import LoadingOverlay from "./components/LoadingOverlay";
const api = Api.getInstance();
// TODO: move to state management
export enum Page {
Traffic,
Setup,
Login
}
// TODO: move to state management
export interface MizuContextModel {
page: Page;
setPage: (page: Page) => void;
}
// TODO: move to state management
export const MizuContext = React.createContext<MizuContextModel>(null);
const EntApp = () => {
const [isLoading, setIsLoading] = useState(true);
const [showTLSWarning, setShowTLSWarning] = useState(false);
const [userDismissedTLSWarning, setUserDismissedTLSWarning] = useState(false);
const [addressesWithTLS, setAddressesWithTLS] = useState(new Set<string>());
const [page, setPage] = useState(Page.Traffic); // TODO: move to state management
const [isFirstLogin, setIsFirstLogin] = useState(false);
const determinePage = async () => { // TODO: move to state management
try {
const isInstallNeeded = await api.isInstallNeeded();
if (isInstallNeeded) {
setPage(Page.Setup);
} else {
const isAuthNeeded = await api.isAuthenticationNeeded();
if(isAuthNeeded) {
setPage(Page.Login);
}
}
} catch (e) {
toast.error("Error occured while checking Mizu API status, see console for mode details");
console.error(e);
} finally {
setIsLoading(false);
}
}
useEffect(() => {
determinePage();
}, []);
const onTLSDetected = (destAddress: string) => {
addressesWithTLS.add(destAddress);
setAddressesWithTLS(new Set(addressesWithTLS));
if (!userDismissedTLSWarning) {
setShowTLSWarning(true);
}
};
let pageComponent: any;
switch (page) { // TODO: move to state management / proper routing
case Page.Traffic:
pageComponent = <TrafficPage onTLSDetected={onTLSDetected}/>;
break;
case Page.Setup:
pageComponent = <InstallPage onFirstLogin={() => setIsFirstLogin(true)}/>;
break;
case Page.Login:
pageComponent = <LoginPage/>;
break;
default:
pageComponent = <div>Unknown Error</div>;
}
if (isLoading) {
return <LoadingOverlay/>;
}
return (
<div className="mizuApp">
<MizuContext.Provider value={{page, setPage}}>
{page === Page.Traffic && <EntHeader isFirstLogin={isFirstLogin} setIsFirstLogin={setIsFirstLogin}/>}
{pageComponent}
{page === Page.Traffic && <TLSWarning showTLSWarning={showTLSWarning}
setShowTLSWarning={setShowTLSWarning}
addressesWithTLS={addressesWithTLS}
setAddressesWithTLS={setAddressesWithTLS}
userDismissedTLSWarning={userDismissedTLSWarning}
setUserDismissedTLSWarning={setUserDismissedTLSWarning}/>}
</MizuContext.Provider>
</div>
);
}
export default EntApp;

View File

@@ -36,7 +36,7 @@ interface QueryFormProps {
openWebSocket: (query: string, resetEntries: boolean) => void;
}
const style = {
export const modalStyle = {
position: 'absolute',
top: '10%',
left: '50%',
@@ -45,6 +45,7 @@ const style = {
bgcolor: 'background.paper',
borderRadius: '5px',
boxShadow: 24,
outline: "none",
p: 4,
color: '#000',
};
@@ -153,11 +154,11 @@ export const QueryForm: React.FC<QueryFormProps> = ({query, setQuery, background
style={{overflow: 'auto'}}
>
<Fade in={openModal}>
<Box sx={style}>
<Box sx={modalStyle}>
<Typography id="modal-modal-title" variant="h5" component="h2" style={{textAlign: 'center'}}>
Filtering Guide (Cheatsheet)
</Typography>
<Typography id="modal-modal-description">
<Typography component={'span'} id="modal-modal-description">
<p>Mizu has a rich filtering syntax that let's you query the results both flexibly and efficiently.</p>
<p>Here are some examples that you can try;</p>
</Typography>

View File

@@ -0,0 +1,78 @@
import React, {useContext, useEffect, useState} from "react";
import logo from '../assets/MizuEntLogo.svg';
import './Header.sass';
import userImg from '../assets/user-circle.svg';
import settingImg from '../assets/settings.svg';
import {Menu, MenuItem} from "@material-ui/core";
import PopupState, {bindMenu, bindTrigger} from "material-ui-popup-state";
import logoutIcon from '../assets/logout.png';
import {SettingsModal} from "../SettingsModal/SettingModal";
import Api from "../../helpers/api";
import {toast} from "react-toastify";
import {MizuContext, Page} from "../../EntApp";
const api = Api.getInstance();
interface EntHeaderProps {
isFirstLogin: boolean;
setIsFirstLogin: (flag: boolean) => void
}
export const EntHeader: React.FC<EntHeaderProps> = ({isFirstLogin, setIsFirstLogin}) => {
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false);
useEffect(() => {
if(isFirstLogin) {
setIsSettingsModalOpen(true)
}
}, [isFirstLogin])
const onSettingsModalClose = () => {
setIsSettingsModalOpen(false);
setIsFirstLogin(false);
}
return <div className="header">
<div>
<div className="title">
<img style={{height: 55}} src={logo} alt="logo"/>
</div>
</div>
<div style={{display: "flex", alignItems: "center"}}>
<img className="headerIcon" alt="settings" src={settingImg} style={{marginRight: 25}} onClick={() => setIsSettingsModalOpen(true)}/>
<ProfileButton/>
</div>
<SettingsModal isOpen={isSettingsModalOpen} onClose={onSettingsModalClose} isFirstLogin={isFirstLogin}/>
</div>;
}
const ProfileButton = () => {
const {setPage} = useContext(MizuContext);
const logout = async (popupState) => {
try {
await api.logout();
setPage(Page.Login);
} catch (e) {
toast.error("Something went wrong, please check the console");
console.error(e);
}
popupState.close();
}
return (<PopupState variant="popover" popupId="demo-popup-menu">
{(popupState) => (
<React.Fragment>
<img className="headerIcon" alt="user" src={userImg} {...bindTrigger(popupState)}/>
<Menu {...bindMenu(popupState)}>
<MenuItem style={{fontSize: 12, fontWeight: 600}} onClick={() => logout(popupState)}>
<img alt="logout" src={logoutIcon} style={{marginRight: 5, height: 16}}/>
Log Out
</MenuItem>
</Menu>
</React.Fragment>
)}
</PopupState>);
}

View File

@@ -0,0 +1,23 @@
@import '../../variables.module'
.header
height: 60px
display: flex
align-items: center
padding: 5px 24px
justify-content: space-between
.title
letter-spacing: 2px
img
height: 45px
.description
margin-left: 10px
font-size: 11px
font-weight: bold
color: $light-blue-color
.headerIcon
cursor: pointer

View File

@@ -2,6 +2,7 @@ import React from "react";
import {AuthPresentation} from "../AuthPresentation/AuthPresentation";
import {AnalyzeButton} from "../AnalyzeButton/AnalyzeButton";
import logo from '../assets/Mizu-logo.svg';
import './Header.sass';
interface HeaderProps {
analyzeStatus: any

View File

@@ -1,6 +1,6 @@
import { Button, TextField } from "@material-ui/core";
import React, { useContext, useState } from "react";
import { MizuContext, Page } from "../App";
import { MizuContext, Page } from "../EntApp";
import { adminUsername } from "../consts";
import Api, { FormValidationErrorType } from "../helpers/api";
import { toast } from 'react-toastify';
@@ -8,7 +8,11 @@ import LoadingOverlay from "./LoadingOverlay";
const api = Api.getInstance();
export const InstallPage: React.FC = () => {
interface InstallPageProps {
onFirstLogin: () => void;
}
export const InstallPage: React.FC<InstallPageProps> = ({onFirstLogin}) => {
const [isLoading, setIsLoading] = useState(false);
const [password, setPassword] = useState("");
@@ -31,6 +35,7 @@ export const InstallPage: React.FC = () => {
if (!await api.isAuthenticationNeeded()) {
toast.success("admin user created successfully");
setPage(Page.Traffic);
onFirstLogin();
}
} catch (e) {
if (e.type === FormValidationErrorType) {

View File

@@ -11,11 +11,17 @@ const LoadingOverlay: React.FC<LoadingOverlayProps> = ({delay}) => {
const [isVisible, setIsVisible] = useState(false);
// @ts-ignore
useEffect(() => {
let isRelevant = true;
setTimeout(() => {
setIsVisible(true);
if(isRelevant)
setIsVisible(true);
}, delay ?? SpinnerShowDelayMs);
}, []);
return () => isRelevant = false;
}, [delay]);
return <div className="loading-overlay-container" hidden={!isVisible}>
<div className="loading-overlay-spinner"/>

View File

@@ -1,7 +1,7 @@
import { Button, TextField } from "@material-ui/core";
import React, { useContext, useState } from "react";
import { toast } from "react-toastify";
import { MizuContext, Page } from "../App";
import { MizuContext, Page } from "../EntApp";
import Api from "../helpers/api";
import LoadingOverlay from "./LoadingOverlay";

View File

@@ -0,0 +1,150 @@
import React, {useEffect, useMemo, useState} from "react";
import {Modal, Backdrop, Fade, Box, Button} from "@material-ui/core";
import {modalStyle} from "../Filters";
import Checkbox from "../UI/Checkbox";
import './SettingsModal.sass';
import Api from "../../helpers/api";
import spinner from "../assets/spinner.svg";
import {useCommonStyles} from "../../helpers/commonStyle";
import {toast} from "react-toastify";
interface SettingsModalProps {
isOpen: boolean
onClose: () => void
isFirstLogin: boolean
}
const api = Api.getInstance();
export const SettingsModal: React.FC<SettingsModalProps> = ({isOpen, onClose, isFirstLogin}) => {
const classes = useCommonStyles();
const [namespaces, setNamespaces] = useState({});
const [isLoading, setIsLoading] = useState(false);
const [searchValue, setSearchValue] = useState("");
useEffect(() => {
(async () => {
try {
setIsLoading(true);
const tapConfig = await api.getTapConfig()
if(isFirstLogin) {
const namespacesObj = {...tapConfig?.tappedNamespaces}
Object.keys(tapConfig?.tappedNamespaces ?? {}).forEach(namespace => {
namespacesObj[namespace] = true;
})
setNamespaces(namespacesObj);
} else {
setNamespaces(tapConfig?.tappedNamespaces);
}
} catch (e) {
console.error(e);
} finally {
setIsLoading(false);
}
})()
}, [isFirstLogin])
const setAllNamespacesTappedValue = (isTap: boolean) => {
const newNamespaces = {};
Object.keys(namespaces).forEach(key => {
newNamespaces[key] = isTap;
})
setNamespaces(newNamespaces);
}
const updateTappingSettings = async () => {
try {
await api.setTapConfig(namespaces);
onClose();
toast.success("Saved successfully");
} catch (e) {
console.error(e);
toast.error("Something went wrong, changes may not have been saved.")
}
}
const toggleTapNamespace = (namespace) => {
const newNamespaces = {...namespaces};
newNamespaces[namespace] = !namespaces[namespace]
setNamespaces(newNamespaces);
}
const toggleAll = () => {
const isChecked = Object.values(namespaces).every(tap => tap === true);
setAllNamespacesTappedValue(!isChecked);
}
const filteredNamespaces = useMemo(() => {
return Object.keys(namespaces).filter((namespace) => namespace.includes(searchValue));
},[namespaces, searchValue])
const buildNamespacesTable = () => {
return <table cellPadding={5} style={{borderCollapse: "collapse"}}>
<thead>
<tr style={{borderBottomWidth: "2px"}}>
<th style={{width: 50}}><Checkbox checked={Object.values(namespaces).every(tap => tap === true)}
onToggle={toggleAll}/></th>
<th>Namespace</th>
</tr>
</thead>
<tbody>
{filteredNamespaces?.map(namespace => {
return <tr key={namespace}>
<td style={{width: 50}}>
<Checkbox checked={namespaces[namespace]} onToggle={() => toggleTapNamespace(namespace)}/>
</td>
<td>{namespace}</td>
</tr>
}
)}
</tbody>
</table>
}
const onModalClose = (reason) => {
if(reason === 'backdropClick' && isFirstLogin) return;
onClose();
}
return <Modal
open={isOpen}
onClose={(event, reason) => onModalClose(reason)}
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500,
}}
style={{overflow: 'auto'}}
>
<Fade in={isOpen}>
<Box sx={modalStyle} style={{width: "40vw", maxWidth: 600, height: "70vh", padding: 0, display: "flex", justifyContent: "space-between", flexDirection: "column"}}>
<div style={{padding: 32, paddingBottom: 0}}>
<div className="settingsTitle">Tapping Settings</div>
<div className="settingsSubtitle" style={{marginTop: 20}}>
Please choose from below the namespaces for tapping, traffic for namespaces selected will be displayed
</div>
{isLoading ? <div style={{textAlign: "center", padding: 20}}>
<img alt="spinner" src={spinner} style={{height: 35}}/>
</div> : <>
<div className="namespacesSettingsContainer">
<div style={{margin: "10px 0"}}>
<input className="searchNamespace" placeholder="Search" value={searchValue}
onChange={(event) => setSearchValue(event.target.value)}/></div>
<div className="namespacesTable">
{buildNamespacesTable()}
</div>
</div>
</>}
</div>
<div className="settingsActionsContainer">
{!isFirstLogin &&
<Button style={{width: 100}} className={classes.outlinedButton} size={"small"}
onClick={onClose} variant='outlined'>Cancel</Button>}
<Button style={{width: 100, marginLeft: 20}} className={classes.button} size={"small"}
onClick={updateTappingSettings}>OK</Button>
</div>
</Box>
</Fade>
</Modal>
}

View File

@@ -0,0 +1,61 @@
@import "../../variables.module"
.settingsTitle
font-size: 28px
color: $blue-gray
font-weight: 600
.settingsSubtitle
font-size: 14px
color: $light-gray
margin-top: 20px
line-height: 20px
.namespacesSettingsContainer
border-radius: 4px
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
justify-content: flex-end
padding: 20px
border-top: 2px $data-background-color solid
.namespacesTable
table
width: 100%
margin-top: 20px
tbody
max-height: calc(70vh - 355px)
overflow-y: auto
display: block
th
color: $blue-gray
text-align: left
padding: 10px
tr
border-bottom-width: 1px
border-bottom-color: $data-background-color
border-bottom-style: solid
display: table
table-layout: fixed
width: 100%
td
color: $light-gray
padding: 10px
font-size: 16px

View File

@@ -29,7 +29,7 @@ export const TLSWarning: React.FC<TLSWarningProps> = ({showTLSWarning, setShowT
console.error(e);
}
})();
}, []);
}, [setShowTLSWarning, setAddressesWithTLS]);
return (<Snackbar open={showTLSWarning && !userDismissedTLSWarning}>
<MuiAlert classes={{filledWarning: 'customWarningStyle'}} elevation={6} variant="filled"

View File

@@ -11,7 +11,6 @@ import variables from '../variables.module.scss';
import {StatusBar} from "./UI/StatusBar";
import Api, {MizuWebsocketURL} from "../helpers/api";
import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import debounce from 'lodash/debounce';
const useLayoutStyles = makeStyles(() => ({
@@ -40,13 +39,13 @@ enum ConnectionStatus {
}
interface TrafficPageProps {
setAnalyzeStatus: (status: any) => void;
onTLSDetected: (destAddress: string) => void;
setAnalyzeStatus?: (status: any) => void;
}
const api = Api.getInstance();
export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLSDetected}) => {
export const TrafficPage: React.FC<TrafficPageProps> = ({onTLSDetected, setAnalyzeStatus}) => {
const classes = useLayoutStyles();
@@ -152,7 +151,8 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLS
setTappingStatus(message.tappingStatus);
break
case "analyzeStatus":
setAnalyzeStatus(message.analyzeStatus);
if(setAnalyzeStatus)
setAnalyzeStatus(message.analyzeStatus);
break
case "outboundLink":
onTLSDetected(message.Data.DstIP);
@@ -193,8 +193,10 @@ export const TrafficPage: React.FC<TrafficPageProps> = ({setAnalyzeStatus, onTLS
try{
const tapStatusResponse = await api.tapStatus();
setTappingStatus(tapStatusResponse);
const analyzeStatusResponse = await api.analyzeStatus();
setAnalyzeStatus(analyzeStatusResponse);
if(setAnalyzeStatus) {
const analyzeStatusResponse = await api.analyzeStatus();
setAnalyzeStatus(analyzeStatusResponse);
}
} catch (error) {
console.error(error);
}

View File

@@ -8,8 +8,8 @@ export interface Props {
const Checkbox: React.FC<Props> = ({checked, onToggle}) => {
return (
<div className="checkboxWrapper">
<input type="checkbox" className="checkbox" checked={checked} onChange={(event) => onToggle(event.target.checked)}/>
<div>
<input style={{cursor: "pointer"}} type="checkbox" checked={checked} onChange={(event) => onToggle(event.target.checked)}/>
</div>
);
};

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.8199 22H10.1799C9.95182 22 9.73059 21.9221 9.55289 21.7792C9.37519 21.6362 9.25169 21.4368 9.20288 21.214L8.79588 19.33C8.25294 19.0921 7.73812 18.7946 7.26088 18.443L5.42388 19.028C5.20645 19.0973 4.97183 19.0902 4.759 19.0078C4.54617 18.9254 4.36794 18.7727 4.25388 18.575L2.42988 15.424C2.31703 15.2261 2.27467 14.9958 2.30973 14.7708C2.34479 14.5457 2.45519 14.3392 2.62288 14.185L4.04788 12.885C3.98308 12.2961 3.98308 11.7019 4.04788 11.113L2.62288 9.816C2.45496 9.66177 2.3444 9.45507 2.30933 9.22978C2.27427 9.00449 2.31677 8.77397 2.42988 8.576L4.24988 5.423C4.36394 5.22532 4.54218 5.07259 4.755 4.99019C4.96783 4.90778 5.20245 4.90066 5.41988 4.97L7.25688 5.555C7.50088 5.375 7.75488 5.207 8.01688 5.055C8.26988 4.913 8.52988 4.784 8.79588 4.669L9.20388 2.787C9.25246 2.5642 9.37572 2.36469 9.55323 2.22155C9.73074 2.07841 9.95185 2.00024 10.1799 2H13.8199C14.0479 2.00024 14.269 2.07841 14.4465 2.22155C14.6241 2.36469 14.7473 2.5642 14.7959 2.787L15.2079 4.67C15.7505 4.9079 16.265 5.20539 16.7419 5.557L18.5799 4.972C18.7972 4.90292 19.0316 4.91017 19.2442 4.99256C19.4568 5.07495 19.6349 5.22753 19.7489 5.425L21.5689 8.578C21.8009 8.985 21.7209 9.5 21.3759 9.817L19.9509 11.117C20.0157 11.7059 20.0157 12.3001 19.9509 12.889L21.3759 14.189C21.7209 14.507 21.8009 15.021 21.5689 15.428L19.7489 18.581C19.6348 18.7787 19.4566 18.9314 19.2438 19.0138C19.0309 19.0962 18.7963 19.1033 18.5789 19.034L16.7419 18.449C16.265 18.8003 15.7505 19.0975 15.2079 19.335L14.7959 21.214C14.7471 21.4366 14.6238 21.6359 14.4463 21.7788C14.2688 21.9218 14.0478 21.9998 13.8199 22ZM7.61988 16.229L8.43988 16.829C8.62488 16.965 8.81688 17.09 9.01688 17.204C9.20488 17.313 9.39688 17.411 9.59588 17.5L10.5289 17.909L10.9859 20H13.0159L13.4729 17.908L14.4059 17.499C14.8129 17.319 15.1999 17.096 15.5589 16.833L16.3789 16.233L18.4209 16.883L19.4359 15.125L17.8529 13.682L17.9649 12.67C18.0149 12.227 18.0149 11.78 17.9649 11.338L17.8529 10.326L19.4369 8.88L18.4209 7.121L16.3799 7.771L15.5589 7.171C15.1997 6.90669 14.8131 6.68173 14.4059 6.5L13.4729 6.091L13.0159 4H10.9859L10.5259 6.092L9.59588 6.5C9.18807 6.67861 8.80136 6.90198 8.44288 7.166L7.62188 7.766L5.58188 7.116L4.56488 8.88L6.14788 10.321L6.03588 11.334C5.98588 11.777 5.98588 12.224 6.03588 12.666L6.14788 13.678L4.56488 15.121L5.57988 16.879L7.61988 16.229ZM11.9959 16C10.935 16 9.9176 15.5786 9.16746 14.8284C8.41731 14.0783 7.99588 13.0609 7.99588 12C7.99588 10.9391 8.41731 9.92172 9.16746 9.17157C9.9176 8.42143 10.935 8 11.9959 8C13.0568 8 14.0742 8.42143 14.8243 9.17157C15.5745 9.92172 15.9959 10.9391 15.9959 12C15.9959 13.0609 15.5745 14.0783 14.8243 14.8284C14.0742 15.5786 13.0568 16 11.9959 16ZM11.9959 10C11.6042 10.0004 11.2213 10.1158 10.8947 10.3318C10.568 10.5479 10.3119 10.855 10.1583 11.2153C10.0046 11.5755 9.9601 11.9729 10.0303 12.3583C10.1004 12.7436 10.2822 13.0998 10.5529 13.3828C10.8237 13.6657 11.1716 13.863 11.5534 13.95C11.9353 14.037 12.3343 14.01 12.7009 13.8724C13.0676 13.7347 13.3858 13.4924 13.616 13.1756C13.8462 12.8587 13.9783 12.4812 13.9959 12.09V12.49V12C13.9959 11.4696 13.7852 10.9609 13.4101 10.5858C13.035 10.2107 12.5263 10 11.9959 10Z" fill="#627EF7"/>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2C9.35831 2.03369 6.8343 3.09807 4.96618 4.96618C3.09807 6.8343 2.03369 9.35831 2 12C2.01235 13.5389 2.37972 15.0542 3.07351 16.4279C3.7673 17.8016 4.76879 18.9967 6 19.92V20H6.1C7.79318 21.2975 9.86685 22.0006 12 22.0006C14.1332 22.0006 16.2068 21.2975 17.9 20H18V19.92C19.2312 18.9967 20.2327 17.8016 20.9265 16.4279C21.6203 15.0542 21.9877 13.5389 22 12C21.9663 9.35831 20.9019 6.8343 19.0338 4.96618C17.1657 3.09807 14.6417 2.03369 12 2ZM8.07 18.93C8.21599 18.2614 8.58615 17.6629 9.11908 17.2336C9.65202 16.8044 10.3157 16.5702 11 16.57H13C13.6843 16.5702 14.348 16.8044 14.8809 17.2336C15.4138 17.6629 15.784 18.2614 15.93 18.93C14.7389 19.6308 13.382 20.0004 12 20.0004C10.618 20.0004 9.26113 19.6308 8.07 18.93ZM17.61 17.64C17.2296 16.7309 16.5891 15.9546 15.7689 15.4084C14.9487 14.8622 13.9854 14.5705 13 14.57H11C10.0146 14.5705 9.05127 14.8622 8.23108 15.4084C7.41088 15.9546 6.77037 16.7309 6.39 17.64C5.64066 16.903 5.0439 16.0255 4.63381 15.0578C4.22373 14.0901 4.00835 13.051 4 12C4.02594 9.88633 4.87712 7.86653 6.37183 6.37183C7.86653 4.87712 9.88633 4.02594 12 4C14.1137 4.02594 16.1335 4.87712 17.6282 6.37183C19.1229 7.86653 19.9741 9.88633 20 12C19.9916 13.051 19.7763 14.0901 19.3662 15.0578C18.9561 16.0255 18.3593 16.903 17.61 17.64Z" fill="#627EF7"/>
<path d="M12 6.00002C11.4714 5.98771 10.9457 6.08275 10.4548 6.27941C9.96397 6.47607 9.51809 6.77026 9.14417 7.14418C8.77026 7.51809 8.47607 7.96397 8.27941 8.45484C8.08275 8.94571 7.98771 9.47137 8.00002 10C7.98771 10.5287 8.08275 11.0543 8.27941 11.5452C8.47607 12.0361 8.77026 12.482 9.14417 12.8559C9.51809 13.2298 9.96397 13.524 10.4548 13.7206C10.9457 13.9173 11.4714 14.0123 12 14C12.5287 14.0123 13.0543 13.9173 13.5452 13.7206C14.0361 13.524 14.482 13.2298 14.8559 12.8559C15.2298 12.482 15.524 12.0361 15.7206 11.5452C15.9173 11.0543 16.0123 10.5287 16 10C16.0123 9.47137 15.9173 8.94571 15.7206 8.45484C15.524 7.96397 15.2298 7.51809 14.8559 7.14418C14.482 6.77026 14.0361 6.47607 13.5452 6.27941C13.0543 6.08275 12.5287 5.98771 12 6.00002ZM12 12C11.734 12.0129 11.4682 11.97 11.2197 11.874C10.9712 11.778 10.7456 11.6312 10.5572 11.4428C10.3689 11.2545 10.222 11.0288 10.126 10.7803C10.0301 10.5319 9.98716 10.2661 10 10C9.98716 9.73397 10.0301 9.46817 10.126 9.2197C10.222 8.97122 10.3689 8.74557 10.5572 8.55722C10.7456 8.36888 10.9712 8.22201 11.2197 8.12605C11.4682 8.03009 11.734 7.98716 12 8.00002C12.2661 7.98716 12.5319 8.03009 12.7803 8.12605C13.0288 8.22201 13.2545 8.36888 13.4428 8.55722C13.6312 8.74557 13.778 8.97122 13.874 9.2197C13.97 9.46817 14.0129 9.73397 14 10C14.0129 10.2661 13.97 10.5319 13.874 10.7803C13.778 11.0288 13.6312 11.2545 13.4428 11.4428C13.2545 11.6312 13.0288 11.778 12.7803 11.874C12.5319 11.97 12.2661 12.0129 12 12Z" fill="#627EF7"/>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -84,6 +84,16 @@ export default class Api {
return response.data;
}
getTapConfig = async () => {
const response = await this.client.get("/config/tapConfig");
return response.data;
}
setTapConfig = async (config) => {
const response = await this.client.post("/config/tapConfig", {tappedNamespaces: config});
return response.data;
}
isInstallNeeded = async () => {
const response = await this.client.get("/install/isNeeded");
return response.data;
@@ -94,7 +104,7 @@ export default class Api {
await this.client.get("/status/tap");
return false;
} catch (e) {
if (e.response.status == 401) {
if (e.response.status === 401) {
return true;
}
throw e;
@@ -112,10 +122,11 @@ export default class Api {
return response;
} catch (e) {
if (e.response.status === 400) {
throw {
const error = {
'type': FormValidationErrorType,
'messages': e.response.data
};
throw error;
} else {
throw e;
}

View File

@@ -0,0 +1,30 @@
import {makeStyles} from "@material-ui/core";
// @ts-ignore
export const useCommonStyles = makeStyles(() => ({
button: {
backgroundColor: "#205cf5",
color: "white",
fontWeight: "600 !important",
fontSize: 12,
padding: "8px 12px",
borderRadius: "6px ! important",
"&:hover": {
backgroundColor: "#205cf5",
},
},
outlinedButton: {
backgroundColor: "transparent",
color: "#205cf5",
fontWeight: "600 !important",
fontSize: 12,
padding: "8px 12px",
border: "1px #205cf5 solid",
borderRadius: "6px ! important",
"&:hover": {
backgroundColor: "transparent",
},
}
}));

View File

@@ -146,4 +146,3 @@ button
::-webkit-scrollbar-corner
display: none

View File

@@ -2,10 +2,26 @@ import React from 'react';
import ReactDOM from 'react-dom';
import './index.sass';
import App from './App';
import EntApp from "./EntApp";
import {ToastContainer} from "react-toastify";
import 'react-toastify/dist/ReactToastify.css';
ReactDOM.render(
<React.StrictMode>
<App />
<>
{window["isEnt"] ? <EntApp/> : <App/>}
<ToastContainer
position="bottom-right"
autoClose={5000}
hideProgressBar={false}
newestOnTop={false}
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
/>
</>
</React.StrictMode>,
document.getElementById('root')
);

View File

@@ -8,6 +8,7 @@ $light-blue-color: #BCCEFD;
$success-color: #27AE60;
$failure-color: #EB5757;
$blue-gray: #494677;
$light-gray: #8F9BB2;
:export {
mainBackgroundColor: $main-background-color;
@@ -19,4 +20,5 @@ $blue-gray: #494677;
successColor: $success-color;
failureColor: $failure-color;
blueGray: $blue-gray;
lightGray: $light-gray;
}