Compare commits

...

19 Commits

Author SHA1 Message Date
Igor Gov
cbe04af801 Revert "Policy rules remove redundant function (#193)" (#199)
This reverts commit c4afeee5b3.
2021-08-10 18:04:30 +03:00
Igor Gov
59dec1a547 Readme fixes (#194) 2021-08-10 16:45:57 +03:00
Igor Gov
c4afeee5b3 Policy rules remove redundant function (#193) 2021-08-10 16:45:47 +03:00
Selton Fiuza
8c9b8d3217 Redesign test rules entry component (#174) 2021-08-10 16:20:16 +03:00
RoyUP9
d705ae3eb6 added support of slice in set, removed support of allowed set flags (#191) 2021-08-10 16:16:58 +03:00
RoyUP9
c53b2148d1 add readonly tag (#190) 2021-08-10 09:51:35 +03:00
Igor Gov
ca897dd3c7 Update issue templates (#189) 2021-08-09 18:36:56 +03:00
RoyUP9
4406919565 added test workflow, added test for contains func (#184) 2021-08-09 16:04:00 +03:00
gadotroee
413fb5b3f5 Add option to supply user agents to ignore via config (#173) 2021-08-09 12:27:13 +03:00
RoyUP9
e36c146979 temp fix - ignore agent image in config command (#186) 2021-08-09 12:17:01 +03:00
Nimrod Gilboa Markevich
1cf9c29ef0 Remove hardump flag (#183)
Removed hardump flag and made it the default (and only) behavior.
2021-08-08 17:31:45 +03:00
Nimrod Gilboa Markevich
02e02718d2 Fixed fetch not using from/to options (#179) 2021-08-08 14:36:24 +03:00
Alex Haiut
1a0517f46b TRA-3547 separated permissions section into separate file (#181) 2021-08-08 14:21:33 +03:00
Alex Haiut
efbb432df9 TRA-3547 separated permissions section into separate file (#181) 2021-08-08 14:19:49 +03:00
RoyUP9
dfea8884d4 Adding 'configuration' section in readme (#180) 2021-08-08 14:05:15 +03:00
Igor Gov
d34dacbbe2 View command - moving version check after proxy creation (#177) 2021-08-08 12:26:58 +03:00
Nimrod Gilboa Markevich
0595df8b87 Adds Namespace-Restricted Mode to README. (#178) 2021-08-08 12:23:11 +03:00
Nimrod Gilboa Markevich
ebbe6458a8 Do not tap pods whose names start with "mizu-". (#176) 2021-08-08 10:51:39 +03:00
Igor Gov
7f2021c312 Several fixes for the release (#175) 2021-08-08 10:32:21 +03:00
44 changed files with 925 additions and 540 deletions

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Run mizu <command> '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Logs**
Upload logs:
1. Run the mizu command with `--set dump-logs=true` (e.g `mizu tap --set dump-logs=true`)
2. Try to reproduce the issue
3. CNTRL+C on terminal tab which runs mizu
4. Upload the logs zip file from ~/.mizu/mizu_logs_**.zip
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome]
**Additional context**
Add any other context about the problem here.

22
.github/workflows/test.yaml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: test
on:
pull_request:
branches:
- 'develop'
- 'main'
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Test
run: make test
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2

View File

@@ -48,8 +48,6 @@ WORKDIR /app
COPY --from=builder ["/app/agent-build/mizuagent", "."]
COPY --from=site-build ["/app/ui-build/build", "site"]
COPY agent/start.sh .
# gin-gonic runs in debug mode without this
ENV GIN_MODE=release

View File

@@ -65,3 +65,5 @@ clean-cli: ## Clean CLI.
clean-docker:
@(echo "DOCKER cleanup - NOT IMPLEMENTED YET " )
test: ## Run tests.
@echo "running cli tests"; cd cli && $(MAKE) test

328
PERMISSIONS.md Normal file
View File

@@ -0,0 +1,328 @@
![Mizu: The API Traffic Viewer for Kubernetes](assets/mizu-logo.svg)
# Kubernetes permissions for MIZU
This document describes in details all permissions required for full and correct operation of Mizu
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
# Required permissions
Mizu needs following permissions on your Kubernetes cluster to run properly
```yaml
- apiGroups:
- ""
resources:
- pods
verbs:
- list
- watch
- create
- delete
- apiGroups:
- ""
resources:
- services
verbs:
- create
- delete
- apiGroups:
- apps
resources:
- daemonsets
verbs:
- create
- patch
- delete
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
- list
- watch
- create
- delete
- apiGroups:
- ""
resources:
- services/proxy
verbs:
- get
```
## Permissions required for service / pod name resolving (opt)
Optionally, for proper resolving of IP addresses to Kubernetes service name, Mizu needs below permissions:
```yaml
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
- create
- delete
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- watch
- create
- delete
- apiGroups:
- apps
resources:
- daemonsets
verbs:
- create
- patch
- delete
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
- list
- watch
- create
- delete
- apiGroups:
- ""
resources:
- services/proxy
verbs:
- get
- apiGroups:
- ""
resources:
- serviceaccounts
verbs:
- get
- create
- delete
- apiGroups:
- rbac.authorization.k8s.io
resources:
- clusterroles
verbs:
- get
- create
- delete
- apiGroups:
- rbac.authorization.k8s.io
resources:
- clusterrolebindings
verbs:
- get
- create
- delete
- apiGroups:
- rbac.authorization.k8s.io
resources:
- roles
verbs:
- get
- create
- delete
- apiGroups:
- rbac.authorization.k8s.io
resources:
- rolebindings
verbs:
- get
- create
- delete
- apiGroups:
- apps
- extensions
resources:
- pods
verbs:
- get
- list
- watch
- apiGroups:
- apps
- extensions
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- ""
- apps
- extensions
resources:
- endpoints
verbs:
- get
- list
- watch
```
## Permissions for Policy rules validation feature (opt)
Optionally, in order to use the policy rules validation feature, Mizu requires the following additional permissions:
```yaml
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- create
- delete
```
- - -
## 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:
```yaml
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
- create
- delete
- apiGroups:
- ""
resources:
- services
verbs:
- get
- create
- delete
- apiGroups:
- apps
resources:
- daemonsets
verbs:
- get
- create
- patch
- delete
- apiGroups:
- ""
resources:
- services/proxy
verbs:
- get
```
### 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:
```yaml
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
- create
- delete
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- watch
- create
- delete
- apiGroups:
- apps
resources:
- daemonsets
verbs:
- get
- create
- patch
- delete
- apiGroups:
- ""
resources:
- services/proxy
verbs:
- get
- apiGroups:
- ""
resources:
- serviceaccounts
verbs:
- get
- create
- delete
- apiGroups:
- rbac.authorization.k8s.io
resources:
- roles
verbs:
- get
- create
- delete
- apiGroups:
- rbac.authorization.k8s.io
resources:
- rolebindings
verbs:
- get
- create
- delete
- apiGroups:
- apps
- extensions
resources:
- pods
verbs:
- get
- list
- watch
- apiGroups:
- apps
- extensions
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- ""
- apps
- extensions
resources:
- endpoints
verbs:
- get
- list
- watch
```

396
README.md
View File

@@ -1,22 +1,23 @@
![Mizu: The API Traffic Viewer for Kubernetes](assets/mizu-logo.svg)
# The API Traffic Viewer for Kubernetes
A simple-yet-powerful API traffic viewer for Kubernetes to help you troubleshoot and debug your microservices. Think TCPDump and Chrome Dev Tools combined.
A simple-yet-powerful API traffic viewer for Kubernetes to help you troubleshoot and debug your microservices. Think TCPDump and Chrome Dev Tools combined
![Simple UI](assets/mizu-ui.png)
## Features
- Simple and powerful CLI
- Real time view of all HTTP requests, REST and gRPC API calls
- Real-time view of all HTTP requests, REST and gRPC API calls
- No installation or code instrumentation
- Works completely on premises (on-prem)
- Works completely on premises
## Download
Download `mizu` for your platform and operating system
Download Mizu for your platform and operating system
### Latest stable release
### Latest Stable Release
* for MacOS - Intel
```
@@ -32,335 +33,50 @@ https://github.com/up9inc/mizu/releases/latest/download/mizu_linux_amd64 \
&& chmod 755 mizu
```
SHA256 checksums are available on the [Releases](https://github.com/up9inc/mizu/releases) page.
SHA256 checksums are available on the [Releases](https://github.com/up9inc/mizu/releases) page
### Development (unstable) build
Pick one from the [Releases](https://github.com/up9inc/mizu/releases) page.
### Development (unstable) Build
Pick one from the [Releases](https://github.com/up9inc/mizu/releases) page
## Prerequisites
1. Set `KUBECONFIG` environment variable to your kubernetes configuration. If this is not set, mizu assumes that configuration is at `${HOME}/.kube/config`
2. mizu needs following permissions on your kubernetes cluster to run
1. Set `KUBECONFIG` environment variable to your Kubernetes configuration. If this is not set, Mizu assumes that configuration is at `${HOME}/.kube/config`
2. `mizu` assumes user running the command has permissions to create resources (such as pods, services, namespaces) on your Kubernetes cluster (no worries - `mizu` resources are cleaned up upon termination)
```yaml
- apiGroups:
- ""
resources:
- pods
verbs:
- list
- watch
- create
- delete
- apiGroups:
- ""
resources:
- services
verbs:
- create
- delete
- apiGroups:
- apps
resources:
- daemonsets
verbs:
- create
- patch
- delete
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
- list
- watch
- create
- delete
- apiGroups:
- ""
resources:
- services/proxy
verbs:
- get
```
For detailed list of k8s permissions see [PERMISSIONS](PERMISSIONS.md) document
3. Optionally, for resolving traffic IP to kubernetes service name, mizu needs below permissions
```yaml
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
- create
- delete
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- watch
- create
- delete
- apiGroups:
- apps
resources:
- daemonsets
verbs:
- create
- patch
- delete
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
- list
- watch
- create
- delete
- apiGroups:
- ""
resources:
- services/proxy
verbs:
- get
- apiGroups:
- ""
resources:
- serviceaccounts
verbs:
- get
- create
- delete
- apiGroups:
- rbac.authorization.k8s.io
resources:
- clusterroles
verbs:
- get
- create
- delete
- apiGroups:
- rbac.authorization.k8s.io
resources:
- clusterrolebindings
verbs:
- get
- create
- delete
- apiGroups:
- rbac.authorization.k8s.io
resources:
- roles
verbs:
- get
- create
- delete
- apiGroups:
- rbac.authorization.k8s.io
resources:
- rolebindings
verbs:
- get
- create
- delete
- apiGroups:
- apps
- extensions
resources:
- pods
verbs:
- get
- list
- watch
- apiGroups:
- apps
- extensions
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- ""
- apps
- extensions
resources:
- endpoints
verbs:
- get
- list
- watch
```
4. Optionally, in order to use the policy rules validation feature, mizu requires the following additional permissions:
```yaml
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- create
- delete
```
5. 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:
```yaml
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
- create
- delete
- apiGroups:
- ""
resources:
- services
verbs:
- get
- create
- delete
- apiGroups:
- apps
resources:
- daemonsets
verbs:
- get
- create
- patch
- delete
- apiGroups:
- ""
resources:
- services/proxy
verbs:
- get
```
6. To restrict mizu to one namespace while also resolving IPs, mizu needs the following permissions in that namespace:
```yaml
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
- create
- delete
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- watch
- create
- delete
- apiGroups:
- apps
resources:
- daemonsets
verbs:
- get
- create
- patch
- delete
- apiGroups:
- ""
resources:
- services/proxy
verbs:
- get
- apiGroups:
- ""
resources:
- serviceaccounts
verbs:
- get
- create
- delete
- apiGroups:
- rbac.authorization.k8s.io
resources:
- roles
verbs:
- get
- create
- delete
- apiGroups:
- rbac.authorization.k8s.io
resources:
- rolebindings
verbs:
- get
- create
- delete
- apiGroups:
- apps
- extensions
resources:
- pods
verbs:
- get
- list
- watch
- apiGroups:
- apps
- extensions
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- ""
- apps
- extensions
resources:
- endpoints
verbs:
- get
- list
- watch
```
See `examples/roles` for example `clusterroles`.
## How to run
## How to Run
1. Find pods you'd like to tap to in your Kubernetes cluster
2. Run `mizu tap PODNAME` or `mizu tap REGEX`
3. Open browser on `http://localhost:8899/mizu` **or** as instructed in the CLI ..
4. Watch the API traffic flowing ..
2. Run `mizu tap` or `mizu tap PODNAME`
3. Open browser on `http://localhost:8899/mizu` **or** as instructed in the CLI
4. Watch the API traffic flowing
5. Type ^C to stop
## Examples
Run `mizu help` for usage options
To tap all pods in current namespace -
```
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
carts-66c77f5fbb-fq65r 2/2 Running 0 20m
catalogue-5f4cb7cf5-7zrmn 2/2 Running 0 20m
front-end-649fc5fd6-kqbtn 2/2 Running 0 20m
..
$ mizu tap
+carts-66c77f5fbb-fq65r
+catalogue-5f4cb7cf5-7zrmn
+front-end-649fc5fd6-kqbtn
Web interface is now available at http://localhost:8899
^C
```
To tap specific pod -
```
```bash
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
front-end-649fc5fd6-kqbtn 2/2 Running 0 7m
@@ -373,7 +89,7 @@ To tap specific pod -
```
To tap multiple pods using regex -
```
```bash
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
carts-66c77f5fbb-fq65r 2/2 Running 0 20m
@@ -388,3 +104,45 @@ To tap multiple pods using regex -
^C
```
## Configuration
Mizu can work with config file which should be stored in ${HOME}/.mizu/config.yaml (macOS: ~/.mizu/config.yaml) <br />
In case no config file found, defaults will be used <br />
In case of partial configuration defined, all other fields will be used with defaults <br />
You can always override the defaults or config file with CLI flags
To get the default config params run `mizu config` <br />
To generate a new config file with default values use `mizu config -r`
### Telemetry
By default, mizu reports usage telemetry. It can be disabled by adding a line of telemetry: false in the ${HOME}/.mizu/config.yaml file
## Advanced Usage
### Namespace-Restricted Mode
Some users have permission to only manage resources in one particular namespace assigned to them
By default `mizu tap` creates a new namespace `mizu` for all of its Kubernetes resources. In order to instead install
Mizu in an existing namespace, set the `mizu-resources-namespace` config option
If `mizu-resources-namespace` is set to a value other than the default `mizu`, Mizu will operate in a
Namespace-Restricted mode. It will only tap pods in `mizu-resources-namespace`. This way Mizu only requires permissions
to the namespace set by `mizu-resources-namespace`. The user must set the tapped namespace to the same namespace by
using the `--namespace` flag or by setting `tap.namespaces` in the config file
Setting `mizu-resources-namespace=mizu` resets Mizu to its default behavior
### User agent filtering
User-agent filtering (like health checks) - can be configured:
Any request that contains one of those values in the user-agent header will not be captured
```bash
$ mizu tap "^ca.*" --set ignored-user-agents=kube-probe --set ignored-user-agents=prometheus
+carts-66c77f5fbb-fq65r
+catalogue-5f4cb7cf5-7zrmn
Web interface is now available at http://localhost:8899
^C
```

View File

@@ -540,8 +540,9 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM=
gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=

View File

@@ -149,15 +149,13 @@ func getTrafficFilteringOptions() *shared.TrafficFilteringOptions {
return &filteringOptions
}
var userAgentsToFilter = []string{"kube-probe", "prometheus"}
func filterHarItems(inChannel <-chan *tap.OutputChannelItem, outChannel chan *tap.OutputChannelItem, filterOptions *shared.TrafficFilteringOptions) {
for message := range inChannel {
if message.ConnectionInfo.IsOutgoing && api.CheckIsServiceIP(message.ConnectionInfo.ServerIP) {
continue
}
// TODO: move this to tappers https://up9.atlassian.net/browse/TRA-3441
if filterOptions.HideHealthChecks && isHealthCheckByUserAgent(message) {
if isHealthCheckByUserAgent(message, filterOptions.HealthChecksUserAgentHeaders) {
continue
}
@@ -169,11 +167,11 @@ func filterHarItems(inChannel <-chan *tap.OutputChannelItem, outChannel chan *ta
}
}
func isHealthCheckByUserAgent(message *tap.OutputChannelItem) bool {
func isHealthCheckByUserAgent(message *tap.OutputChannelItem, userAgentsToIgnore []string) bool {
for _, header := range message.HarEntry.Request.Headers {
if strings.ToLower(header.Name) == "user-agent" {
for _, userAgent := range userAgentsToFilter {
if strings.Contains(strings.ToLower(header.Value), userAgent) {
for _, userAgent := range userAgentsToIgnore {
if strings.Contains(strings.ToLower(header.Value), strings.ToLower(userAgent)) {
return true
}
}

View File

@@ -57,7 +57,7 @@ func GetEntries(c *gin.Context) {
}
func GetHARs(c *gin.Context) {
entriesFilter := &models.HarFetchRequestBody{}
entriesFilter := &models.HarFetchRequestQuery{}
order := database.OrderDesc
if err := c.BindQuery(entriesFilter); err != nil {
c.JSON(http.StatusBadRequest, err)
@@ -146,12 +146,12 @@ func GetHARs(c *gin.Context) {
func UploadEntries(c *gin.Context) {
rlog.Infof("Upload entries - started\n")
uploadRequestBody := &models.UploadEntriesRequestBody{}
if err := c.BindQuery(uploadRequestBody); err != nil {
uploadParams := &models.UploadEntriesRequestQuery{}
if err := c.BindQuery(uploadParams); err != nil {
c.JSON(http.StatusBadRequest, err)
return
}
if err := validation.Validate(uploadRequestBody); err != nil {
if err := validation.Validate(uploadParams); err != nil {
c.JSON(http.StatusBadRequest, err)
return
}
@@ -160,19 +160,19 @@ func UploadEntries(c *gin.Context) {
return
}
rlog.Infof("Upload entries - creating token. dest %s\n", uploadRequestBody.Dest)
token, err := up9.CreateAnonymousToken(uploadRequestBody.Dest)
rlog.Infof("Upload entries - creating token. dest %s\n", uploadParams.Dest)
token, err := up9.CreateAnonymousToken(uploadParams.Dest)
if err != nil {
c.String(http.StatusServiceUnavailable, "Cannot analyze, mizu is already analyzing")
return
}
rlog.Infof("Upload entries - uploading. token: %s model: %s\n", token.Token, token.Model)
go up9.UploadEntriesImpl(token.Token, token.Model, uploadRequestBody.Dest, uploadRequestBody.SleepIntervalSec)
go up9.UploadEntriesImpl(token.Token, token.Model, uploadParams.Dest, uploadParams.SleepIntervalSec)
c.String(http.StatusOK, "OK")
}
func GetFullEntries(c *gin.Context) {
entriesFilter := &models.HarFetchRequestBody{}
entriesFilter := &models.HarFetchRequestQuery{}
if err := c.BindQuery(entriesFilter); err != nil {
c.JSON(http.StatusBadRequest, err)
}

View File

@@ -56,12 +56,14 @@ type BaseEntryDetails struct {
type ApplicableRules struct {
Latency int64 `json:"latency,omitempty"`
Status bool `json:"status,omitempty"`
NumberOfRules int `json:"numberOfRules,omitempty"`
}
func NewApplicableRules(status bool, latency int64) ApplicableRules {
func NewApplicableRules(status bool, latency int64, number int) ApplicableRules {
ar := ApplicableRules{}
ar.Status = status
ar.Latency = latency
ar.NumberOfRules = number
return ar
}
@@ -119,19 +121,19 @@ func (fedex *FullEntryDetailsExtra) UnmarshalData(entry *MizuEntry) error {
}
type EntriesFilter struct {
Limit int `query:"limit" validate:"required,min=1,max=200"`
Operator string `query:"operator" validate:"required,oneof='lt' 'gt'"`
Timestamp int64 `query:"timestamp" validate:"required,min=1"`
Limit int `form:"limit" validate:"required,min=1,max=200"`
Operator string `form:"operator" validate:"required,oneof='lt' 'gt'"`
Timestamp int64 `form:"timestamp" validate:"required,min=1"`
}
type UploadEntriesRequestBody struct {
type UploadEntriesRequestQuery struct {
Dest string `form:"dest"`
SleepIntervalSec int `form:"interval"`
}
type HarFetchRequestBody struct {
From int64 `query:"from"`
To int64 `query:"to"`
type HarFetchRequestQuery struct {
From int64 `form:"from"`
To int64 `form:"to"`
}
type WebSocketEntryMessage struct {
@@ -218,7 +220,7 @@ func (fewp *FullEntryWithPolicy) UnmarshalData(entry *MizuEntry) error {
func RunValidationRulesState(harEntry har.Entry, service string) ApplicableRules {
numberOfRules, resultPolicyToSend := rules.MatchRequestPolicy(harEntry, service)
statusPolicyToSend, latency := rules.PassedValidationRules(resultPolicyToSend, numberOfRules)
ar := NewApplicableRules(statusPolicyToSend, latency)
statusPolicyToSend, latency, numberOfRules := rules.PassedValidationRules(resultPolicyToSend, numberOfRules)
ar := NewApplicableRules(statusPolicyToSend, latency, numberOfRules)
return ar
}

View File

@@ -92,19 +92,19 @@ func MatchRequestPolicy(harEntry har.Entry, service string) (int, []RulesMatched
return len(enforcePolicy.Rules), resultPolicyToSend
}
func PassedValidationRules(rulesMatched []RulesMatched, numberOfRules int) (bool, int64) {
func PassedValidationRules(rulesMatched []RulesMatched, numberOfRules int) (bool, int64, int) {
if len(rulesMatched) == 0 {
return false, 0
return false, 0, 0
}
for _, rule := range rulesMatched {
if rule.Matched == false {
return false, -1
return false, -1, len(rulesMatched)
}
}
for _, rule := range rulesMatched {
if strings.ToLower(rule.Rule.Type) == "latency" {
return true, rule.Rule.Latency
return true, rule.Rule.Latency, len(rulesMatched)
}
}
return true, -1
return true, -1, len(rulesMatched)
}

View File

@@ -1,2 +0,0 @@
#!/bin/bash
./mizuagent -i any -hardump -targets ${TAPPED_ADDRESSES}

View File

@@ -39,3 +39,6 @@ build-all: ## Build for all supported platforms.
clean: ## Clean all build artifacts.
go clean
rm -rf ./bin/*
test: ## Run cli tests.
@go test ./... -race -coverprofile=coverage.out -covermode=atomic

View File

@@ -1,26 +0,0 @@
# mizu CLI
## Usage
`./mizu {pod_name_regex}`
### Optional Flags
| flag | default | purpose |
|----------------------|------------------|--------------------------------------------------------------------------------------------------------------|
| `--no-gui` | `false` | Don't host the web interface (not applicable at the moment) |
| `--gui-port` | `8899` | local port that web interface will be forwarded to |
| `--namespace` | | use namespace different than the one found in kubeconfig |
| `--kubeconfig` | | Path to custom kubeconfig file |
There are some extra flags defined in code that will show up in `./mizu --help`, these are non functional stubs for now
## Installation
Make sure your go version is at least 1.11
1. cd to `mizu/cli`
2. Run `go mod download` (may take a moment)
3. Run `go build mizu.go`
Alternatively, you can build+run directly using `go run mizu.go {pod_name_regex}`
## Known issues
* mid-flight port forwarding failures are not detected and no indication will be shown when this occurs

View File

@@ -12,7 +12,7 @@ var fetchCmd = &cobra.Command{
Short: "Download recorded traffic to files",
RunE: func(cmd *cobra.Command, args []string) error {
go mizu.ReportRun("fetch", mizu.Config.Fetch)
if isCompatible, err := mizu.CheckVersionCompatibility(mizu.Config.Fetch.MizuPort); err != nil {
if isCompatible, err := mizu.CheckVersionCompatibility(mizu.Config.Fetch.GuiPort); err != nil {
return err
} else if !isCompatible {
return nil
@@ -31,5 +31,5 @@ func init() {
fetchCmd.Flags().StringP(configStructs.DirectoryFetchName, "d", defaultFetchConfig.Directory, "Provide a custom directory for fetched entries")
fetchCmd.Flags().Int(configStructs.FromTimestampFetchName, defaultFetchConfig.FromTimestamp, "Custom start timestamp for fetched entries")
fetchCmd.Flags().Int(configStructs.ToTimestampFetchName, defaultFetchConfig.ToTimestamp, "Custom end timestamp fetched entries")
fetchCmd.Flags().Uint16P(configStructs.MizuPortFetchName, "p", defaultFetchConfig.MizuPort, "Custom port for mizu")
fetchCmd.Flags().Uint16P(configStructs.GuiPortFetchName, "p", defaultFetchConfig.GuiPort, "Provide a custom port for the web interface webserver")
}

View File

@@ -16,7 +16,7 @@ import (
)
func RunMizuFetch() {
mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Fetch.MizuPort)
mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Fetch.GuiPort)
resp, err := http.Get(fmt.Sprintf("http://%s/api/har?from=%v&to=%v", mizuProxiedUrl, mizu.Config.Fetch.FromTimestamp, mizu.Config.Fetch.ToTimestamp))
if err != nil {
log.Fatal(err)

View File

@@ -1,7 +1,6 @@
package cmd
import (
"errors"
"fmt"
"github.com/spf13/cobra"
"github.com/up9inc/mizu/cli/fsUtils"
@@ -19,8 +18,7 @@ Further info is available at https://github.com/up9inc/mizu`,
}
mizu.InitLogger()
if err := mizu.InitConfig(cmd); err != nil {
mizu.Log.Errorf("Invalid config, Exit %s", err)
return errors.New(fmt.Sprintf("%v", err))
mizu.Log.Fatal(err)
}
return nil

View File

@@ -60,14 +60,12 @@ func init() {
defaults.Set(&defaultTapConfig)
tapCmd.Flags().Uint16P(configStructs.GuiPortTapName, "p", defaultTapConfig.GuiPort, "Provide a custom port for the web interface webserver")
tapCmd.Flags().StringArrayP(configStructs.NamespacesTapName, "n", defaultTapConfig.Namespaces, "Namespaces selector")
tapCmd.Flags().StringSliceP(configStructs.NamespacesTapName, "n", defaultTapConfig.Namespaces, "Namespaces selector")
tapCmd.Flags().Bool(configStructs.AnalysisTapName, defaultTapConfig.Analysis, "Uploads traffic to UP9 for further analysis (Beta)")
tapCmd.Flags().BoolP(configStructs.AllNamespacesTapName, "A", defaultTapConfig.AllNamespaces, "Tap all namespaces")
tapCmd.Flags().StringP(configStructs.KubeConfigPathTapName, "k", defaultTapConfig.KubeConfigPath, "Path to kube-config file")
tapCmd.Flags().StringArrayP(configStructs.PlainTextFilterRegexesTapName, "r", defaultTapConfig.PlainTextFilterRegexes, "List of regex expressions that are used to filter matching values from text/plain http bodies")
tapCmd.Flags().Bool(configStructs.HideHealthChecksTapName, defaultTapConfig.HideHealthChecks, "hides requests with kube-probe or prometheus user-agent headers")
tapCmd.Flags().StringSliceP(configStructs.PlainTextFilterRegexesTapName, "r", defaultTapConfig.PlainTextFilterRegexes, "List of regex expressions that are used to filter matching values from text/plain http bodies")
tapCmd.Flags().Bool(configStructs.DisableRedactionTapName, defaultTapConfig.DisableRedaction, "Disables redaction of potentially sensitive request/response headers and body values")
tapCmd.Flags().String(configStructs.HumanMaxEntriesDBSizeTapName, defaultTapConfig.HumanMaxEntriesDBSize, "override the default max entries db size of 200mb")
tapCmd.Flags().String(configStructs.HumanMaxEntriesDBSizeTapName, defaultTapConfig.HumanMaxEntriesDBSize, "Override the default max entries db size")
tapCmd.Flags().String(configStructs.DirectionTapName, defaultTapConfig.Direction, "Record traffic that goes in this direction (relative to the tapped pod): in/any")
tapCmd.Flags().Bool(configStructs.DryRunTapName, defaultTapConfig.DryRun, "Preview of all pods matching the regex, without tapping them")
tapCmd.Flags().String(configStructs.EnforcePolicyFile, defaultTapConfig.EnforcePolicyFile, "Yaml file with policy rules")

View File

@@ -6,6 +6,8 @@ import (
"encoding/json"
"fmt"
"github.com/up9inc/mizu/cli/fsUtils"
"github.com/up9inc/mizu/cli/goUtils"
"github.com/up9inc/mizu/cli/mizu/configStructs"
"net/http"
"net/url"
"os"
@@ -56,7 +58,7 @@ func RunMizuTap() {
}
}
kubernetesProvider, err := kubernetes.NewProvider(mizu.Config.Tap.KubeConfigPath)
kubernetesProvider, err := kubernetes.NewProvider(mizu.Config.KubeConfigPath)
if err != nil {
mizu.Log.Error(err)
return
@@ -68,7 +70,7 @@ func RunMizuTap() {
targetNamespaces := getNamespaces(kubernetesProvider)
var namespacesStr string
if targetNamespaces[0] != mizu.K8sAllNamespaces {
if !mizu.Contains(targetNamespaces, mizu.K8sAllNamespaces) {
namespacesStr = fmt.Sprintf("namespaces \"%s\"", strings.Join(targetNamespaces, "\", \""))
} else {
namespacesStr = "all namespaces"
@@ -83,7 +85,7 @@ func RunMizuTap() {
if len(state.currentlyTappedPods) == 0 {
var suggestionStr string
if targetNamespaces[0] != mizu.K8sAllNamespaces {
if !mizu.Contains(targetNamespaces, mizu.K8sAllNamespaces) {
suggestionStr = ". Select a different namespace with -n or tap all namespaces with -A"
}
mizu.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Did not find any pods matching the regex argument%s", suggestionStr))
@@ -101,8 +103,8 @@ func RunMizuTap() {
return
}
go createProxyToApiServerPod(ctx, kubernetesProvider, cancel)
go watchPodsForTapping(ctx, kubernetesProvider, targetNamespaces, cancel)
go goUtils.HandleExcWrapper(createProxyToApiServerPod, ctx, kubernetesProvider, cancel)
go goUtils.HandleExcWrapper(watchPodsForTapping, ctx, kubernetesProvider, targetNamespaces, cancel)
//block until exit signal or error
waitForFinish(ctx, cancel)
@@ -205,7 +207,11 @@ func getMizuApiFilteringOptions() (*shared.TrafficFilteringOptions, error) {
}
}
return &shared.TrafficFilteringOptions{PlainTextMaskingRegexes: compiledRegexSlice, HideHealthChecks: mizu.Config.Tap.HideHealthChecks, DisableRedaction: mizu.Config.Tap.DisableRedaction}, nil
return &shared.TrafficFilteringOptions{
PlainTextMaskingRegexes: compiledRegexSlice,
HealthChecksUserAgentHeaders: mizu.Config.Tap.HealthChecksUserAgentHeaders,
DisableRedaction: mizu.Config.Tap.DisableRedaction,
}, nil
}
func updateMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string) error {
@@ -327,7 +333,7 @@ func waitUntilNamespaceDeleted(ctx context.Context, cancel context.CancelFunc, k
}
func reportTappedPods() {
mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Fetch.MizuPort)
mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Tap.GuiPort)
tappedPodsUrl := fmt.Sprintf("http://%s/status/tappedPods", mizuProxiedUrl)
podInfos := make([]shared.PodInfo, 0)
@@ -399,13 +405,13 @@ func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Pro
}
case err := <-errorChan:
mizu.Log.Debugf("Watching pods loop, got error %v, stopping restart tappers debouncer", err)
mizu.Log.Debugf("Watching pods loop, got error %v, stopping `restart tappers debouncer`", err)
restartTappersDebouncer.Cancel()
// TODO: Does this also perform cleanup?
cancel()
case <-ctx.Done():
mizu.Log.Debugf("Watching pods loop, context done, stopping restart tappers debouncer")
mizu.Log.Debugf("Watching pods loop, context done, stopping `restart tappers debouncer`")
restartTappersDebouncer.Cancel()
return
}
@@ -417,7 +423,8 @@ func updateCurrentlyTappedPods(kubernetesProvider *kubernetes.Provider, ctx cont
if matchingPods, err := kubernetesProvider.ListAllRunningPodsMatchingRegex(ctx, mizu.Config.Tap.PodRegex(), targetNamespaces); err != nil {
return err, false
} else {
addedPods, removedPods := getPodArrayDiff(state.currentlyTappedPods, matchingPods)
podsToTap := excludeMizuPods(matchingPods)
addedPods, removedPods := getPodArrayDiff(state.currentlyTappedPods, podsToTap)
for _, addedPod := range addedPods {
changeFound = true
mizu.Log.Infof(uiUtils.Green, fmt.Sprintf("+%s", addedPod.Name))
@@ -426,12 +433,25 @@ func updateCurrentlyTappedPods(kubernetesProvider *kubernetes.Provider, ctx cont
changeFound = true
mizu.Log.Infof(uiUtils.Red, fmt.Sprintf("-%s", removedPod.Name))
}
state.currentlyTappedPods = matchingPods
state.currentlyTappedPods = podsToTap
}
return nil, changeFound
}
func excludeMizuPods(pods []core.Pod) []core.Pod {
mizuPrefixRegex := regexp.MustCompile("^" + mizu.MizuResourcesPrefix)
nonMizuPods := make([]core.Pod, 0)
for _, pod := range pods {
if !mizuPrefixRegex.MatchString(pod.Name) {
nonMizuPods = append(nonMizuPods, pod)
}
}
return nonMizuPods
}
func getPodArrayDiff(oldPods []core.Pod, newPods []core.Pod) (added []core.Pod, removed []core.Pod) {
added = getMissingPods(newPods, oldPods)
removed = getMissingPods(oldPods, newPods)
@@ -465,9 +485,10 @@ func createProxyToApiServerPod(ctx context.Context, kubernetesProvider *kubernet
for {
select {
case <-ctx.Done():
mizu.Log.Debugf("Watching API Server pod loop, ctx done")
return
case <-added:
mizu.Log.Debugf("Got agent pod added event")
mizu.Log.Debugf("Watching API Server pod loop, added")
continue
case <-removed:
mizu.Log.Infof("%s removed", mizu.ApiServerPodName)
@@ -475,22 +496,14 @@ func createProxyToApiServerPod(ctx context.Context, kubernetesProvider *kubernet
return
case modifiedPod := <-modified:
if modifiedPod == nil {
mizu.Log.Debugf("Got agent pod modified event, status phase: %v", modifiedPod.Status.Phase)
mizu.Log.Debugf("Watching API Server pod loop, modifiedPod with nil")
continue
}
mizu.Log.Debugf("Got agent pod modified event, status phase: %v", modifiedPod.Status.Phase)
mizu.Log.Debugf("Watching API Server pod loop, modified: %v", modifiedPod.Status.Phase)
if modifiedPod.Status.Phase == core.PodRunning && !isPodReady {
isPodReady = true
go func() {
err := kubernetes.StartProxy(kubernetesProvider, mizu.Config.Tap.GuiPort, mizu.Config.MizuResourcesNamespace, mizu.ApiServerPodName)
if err != nil {
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error occured while running k8s proxy %v", errormessage.FormatError(err)))
cancel()
}
}()
mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Tap.GuiPort)
mizu.Log.Infof("Mizu is available at http://%s\n", mizuProxiedUrl)
go startProxyReportErrorIfAny(kubernetesProvider, cancel)
mizu.Log.Infof("Mizu is available at http://%s\n", kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Tap.GuiPort))
time.Sleep(time.Second * 5) // Waiting to be sure the proxy is ready
requestForAnalysis()
reportTappedPods()
@@ -507,6 +520,15 @@ func createProxyToApiServerPod(ctx context.Context, kubernetesProvider *kubernet
}
}
func startProxyReportErrorIfAny(kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
err := kubernetes.StartProxy(kubernetesProvider, mizu.Config.Tap.GuiPort, mizu.Config.MizuResourcesNamespace, mizu.ApiServerPodName)
if err != nil {
mizu.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error occured while running k8s proxy %v\n"+
"Try setting different port by using --%s", errormessage.FormatError(err), configStructs.GuiPortTapName))
cancel()
}
}
func requestForAnalysis() {
if !mizu.Config.Tap.Analysis {
return
@@ -530,21 +552,15 @@ func requestForAnalysis() {
}
func createRBACIfNecessary(ctx context.Context, kubernetesProvider *kubernetes.Provider) (bool, error) {
mizuRBACExists, err := kubernetesProvider.DoesServiceAccountExist(ctx, mizu.Config.MizuResourcesNamespace, mizu.ServiceAccountName)
if err != nil {
return false, err
}
if !mizuRBACExists {
if !mizu.Config.IsNsRestrictedMode() {
err := kubernetesProvider.CreateMizuRBAC(ctx, mizu.Config.MizuResourcesNamespace, mizu.ServiceAccountName, mizu.ClusterRoleName, mizu.ClusterRoleBindingName, mizu.RBACVersion)
if err != nil {
return false, err
}
} else {
err := kubernetesProvider.CreateMizuRBACNamespaceRestricted(ctx, mizu.Config.MizuResourcesNamespace, mizu.ServiceAccountName, mizu.RoleName, mizu.RoleBindingName, mizu.RBACVersion)
if err != nil {
return false, err
}
if !mizu.Config.IsNsRestrictedMode() {
err := kubernetesProvider.CreateMizuRBAC(ctx, mizu.Config.MizuResourcesNamespace, mizu.ServiceAccountName, mizu.ClusterRoleName, mizu.ClusterRoleBindingName, mizu.RBACVersion)
if err != nil {
return false, err
}
} else {
err := kubernetesProvider.CreateMizuRBACNamespaceRestricted(ctx, mizu.Config.MizuResourcesNamespace, mizu.ServiceAccountName, mizu.RoleName, mizu.RoleBindingName, mizu.RBACVersion)
if err != nil {
return false, err
}
}
return true, nil

View File

@@ -12,11 +12,6 @@ var viewCmd = &cobra.Command{
Short: "Open GUI in browser",
RunE: func(cmd *cobra.Command, args []string) error {
go mizu.ReportRun("view", mizu.Config.View)
if isCompatible, err := mizu.CheckVersionCompatibility(mizu.Config.View.GuiPort); err != nil {
return err
} else if !isCompatible {
return nil
}
runMizuView()
return nil
},

View File

@@ -20,10 +20,13 @@ func runMizuView() {
exists, err := kubernetesProvider.DoesServicesExist(ctx, mizu.Config.MizuResourcesNamespace, mizu.ApiServerPodName)
if err != nil {
panic(err)
mizu.Log.Errorf("Failed to found mizu service %v", err)
cancel()
return
}
if !exists {
mizu.Log.Infof("The %s service not found", mizu.ApiServerPodName)
mizu.Log.Infof("%s service not found, you should run `mizu tap` command first", mizu.ApiServerPodName)
cancel()
return
}
@@ -33,11 +36,19 @@ func runMizuView() {
mizu.Log.Infof("Found a running service %s and open port %d", mizu.ApiServerPodName, mizu.Config.View.GuiPort)
return
}
mizu.Log.Infof("Found service %s, creating k8s proxy", mizu.ApiServerPodName)
mizu.Log.Debugf("Found service %s, creating k8s proxy", mizu.ApiServerPodName)
go startProxyReportErrorIfAny(kubernetesProvider, cancel)
mizu.Log.Infof("Mizu is available at http://%s\n", kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.View.GuiPort))
err = kubernetes.StartProxy(kubernetesProvider, mizu.Config.View.GuiPort, mizu.Config.MizuResourcesNamespace, mizu.ApiServerPodName)
if err != nil {
mizu.Log.Infof("Error occured while running k8s proxy %v", err)
if isCompatible, err := mizu.CheckVersionCompatibility(mizu.Config.View.GuiPort); err != nil {
mizu.Log.Errorf("Failed to check versions compatibility %v", err)
cancel()
return
} else if !isCompatible {
cancel()
return
}
waitForFinish(ctx, cancel)
}

View File

@@ -17,8 +17,12 @@ func FormatError(err error) error {
var errorNew error
if k8serrors.IsForbidden(err) {
errorNew = fmt.Errorf("insufficient permissions: %w. "+
"supply the required permission or control Mizu's access to namespaces by setting MizuResourcesNamespace "+
"in the config file or setting the tapped namespace with --%s %s=<NAMEPSACE>", err, mizu.SetCommandName, mizu.MizuResourcesNamespaceConfigName)
"supply the required permission or control Mizu's access to namespaces by setting %s "+
"in the config file or setting the tapped namespace with --%s %s=<NAMEPSACE>",
err,
mizu.MizuResourcesNamespaceConfigName,
mizu.SetCommandName,
mizu.MizuResourcesNamespaceConfigName)
} else if syntaxError, isSyntaxError := asRegexSyntaxError(err); isSyntaxError {
errorNew = fmt.Errorf("regex %s is invalid: %w", syntaxError.Expr, err)
} else {

View File

@@ -11,14 +11,14 @@ import (
)
func DumpLogs(provider *kubernetes.Provider, ctx context.Context, filePath string) error {
podExactRegex := regexp.MustCompile(fmt.Sprintf("^mizu-"))
podExactRegex := regexp.MustCompile("^" + mizu.MizuResourcesPrefix)
pods, err := provider.ListAllPodsMatchingRegex(ctx, podExactRegex, []string{mizu.Config.MizuResourcesNamespace})
if err != nil {
return err
}
if len(pods) == 0 {
return fmt.Errorf("no pods found in namespace %s", mizu.Config.MizuResourcesNamespace)
return fmt.Errorf("no mizu pods found in namespace %s", mizu.Config.MizuResourcesNamespace)
}
newZipFile, err := os.Create(filePath)
@@ -49,7 +49,7 @@ func DumpLogs(provider *kubernetes.Provider, ctx context.Context, filePath strin
mizu.Log.Infof("Successfully added file %s", mizu.GetConfigFilePath())
}
if err := AddFileToZip(zipWriter, mizu.GetLogFilePath()); err != nil {
mizu.Log.Errorf("Failed write file, %v", err)
mizu.Log.Debugf("Failed write file, %v", err)
} else {
mizu.Log.Infof("Successfully added file %s", mizu.GetLogFilePath())
}

View File

@@ -217,7 +217,6 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -411,10 +410,9 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=

View File

@@ -0,0 +1,25 @@
package goUtils
import (
"github.com/up9inc/mizu/cli/mizu"
"reflect"
"runtime/debug"
)
func HandleExcWrapper(fn interface{}, params ...interface{}) (result []reflect.Value) {
defer func() {
if panicMessage := recover(); panicMessage != nil {
stack := debug.Stack()
mizu.Log.Fatalf("Unhandled panic: %v\n stack: %s", panicMessage, stack)
}
}()
f := reflect.ValueOf(fn)
if f.Type().NumIn() != len(params) {
panic("incorrect number of parameters!")
}
inputs := make([]reflect.Value, len(params))
for k, in := range params {
inputs[k] = reflect.ValueOf(in)
}
return f.Call(inputs)
}

View File

@@ -13,17 +13,14 @@ import (
"strconv"
"github.com/up9inc/mizu/cli/mizu"
"io"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/homedir"
"github.com/up9inc/mizu/shared"
"io"
core "k8s.io/api/core/v1"
rbac "k8s.io/api/rbac/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
resource "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/watch"
applyconfapp "k8s.io/client-go/applyconfigurations/apps/v1"
@@ -35,9 +32,11 @@ import (
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
_ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/clientcmd"
_ "k8s.io/client-go/tools/portforward"
watchtools "k8s.io/client-go/tools/watch"
"k8s.io/client-go/util/homedir"
)
type Provider struct {
@@ -358,15 +357,15 @@ func (provider *Provider) CreateMizuRBAC(ctx context.Context, namespace string,
},
}
_, err := provider.clientSet.CoreV1().ServiceAccounts(namespace).Create(ctx, serviceAccount, metav1.CreateOptions{})
if err != nil {
if err != nil && !k8serrors.IsAlreadyExists(err) {
return err
}
_, err = provider.clientSet.RbacV1().ClusterRoles().Create(ctx, clusterRole, metav1.CreateOptions{})
if err != nil {
if err != nil && !k8serrors.IsAlreadyExists(err) {
return err
}
_, err = provider.clientSet.RbacV1().ClusterRoleBindings().Create(ctx, clusterRoleBinding, metav1.CreateOptions{})
if err != nil {
if err != nil && !k8serrors.IsAlreadyExists(err) {
return err
}
return nil
@@ -412,15 +411,15 @@ func (provider *Provider) CreateMizuRBACNamespaceRestricted(ctx context.Context,
},
}
_, err := provider.clientSet.CoreV1().ServiceAccounts(namespace).Create(ctx, serviceAccount, metav1.CreateOptions{})
if err != nil {
if err != nil && !k8serrors.IsAlreadyExists(err) {
return err
}
_, err = provider.clientSet.RbacV1().Roles(namespace).Create(ctx, role, metav1.CreateOptions{})
if err != nil {
if err != nil && !k8serrors.IsAlreadyExists(err) {
return err
}
_, err = provider.clientSet.RbacV1().RoleBindings(namespace).Create(ctx, roleBinding, metav1.CreateOptions{})
if err != nil {
if err != nil && !k8serrors.IsAlreadyExists(err) {
return err
}
return nil
@@ -578,7 +577,6 @@ func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespac
"./mizuagent",
"-i", "any",
"--tap",
"--hardump",
"--api-server-address", fmt.Sprintf("ws://%s/wsTapper", apiServerPodIp),
}
if tapOutgoing {

View File

@@ -2,8 +2,9 @@ package main
import (
"github.com/up9inc/mizu/cli/cmd"
"github.com/up9inc/mizu/cli/goUtils"
)
func main() {
cmd.Execute()
goUtils.HandleExcWrapper(cmd.Execute)
}

View File

@@ -21,6 +21,8 @@ import (
const (
Separator = "="
SetCommandName = "set"
FieldNameTag = "yaml"
ReadonlyTag = "readonly"
)
var allowedSetFlags = []string{
@@ -28,8 +30,10 @@ var allowedSetFlags = []string{
MizuResourcesNamespaceConfigName,
TelemetryConfigName,
DumpLogsConfigName,
KubeConfigPathName,
configStructs.AnalysisDestinationTapName,
configStructs.SleepIntervalSecTapName,
configStructs.IgnoredUserAgentsTapName,
}
var Config = ConfigStruct{}
@@ -51,8 +55,8 @@ func InitConfig(cmd *cobra.Command) error {
}
if err := mergeConfigFile(); err != nil {
Log.Errorf("Could not load config file, error %v", err)
Log.Fatalf("You can regenerate the file using `mizu config -r` or just remove it %v", GetConfigFilePath())
return fmt.Errorf("invalid config %w\n"+
"you can regenerate the file using `mizu config -r` or just remove it %v", err, GetConfigFilePath())
}
cmd.Flags().Visit(initFlag)
@@ -68,6 +72,10 @@ func GetConfigWithDefaults() (string, error) {
if err := defaults.Set(&defaultConf); err != nil {
return "", err
}
configElem := reflect.ValueOf(&defaultConf).Elem()
setZeroForReadonlyFields(configElem)
return uiUtils.PrettyYaml(defaultConf)
}
@@ -104,33 +112,44 @@ func initFlag(f *pflag.Flag) {
}
if f.Name == SetCommandName {
mergeSetFlag(sliceValue.GetSlice())
mergeSetFlag(configElem, sliceValue.GetSlice())
return
}
mergeFlagValues(configElem, f.Name, sliceValue.GetSlice())
}
func mergeSetFlag(setValues []string) {
configElem := reflect.ValueOf(&Config).Elem()
func mergeSetFlag(configElem reflect.Value, setValues []string) {
setMap := map[string][]string{}
for _, setValue := range setValues {
if !strings.Contains(setValue, Separator) {
Log.Warningf(uiUtils.Warning, fmt.Sprintf("Ignoring set argument %s (set argument format: <flag name>=<flag value>)", setValue))
continue
}
split := strings.SplitN(setValue, Separator, 2)
if len(split) != 2 {
Log.Warningf(uiUtils.Warning, fmt.Sprintf("Ignoring set argument %s (set argument format: <flag name>=<flag value>)", setValue))
continue
}
argumentKey, argumentValue := split[0], split[1]
setMap[argumentKey] = append(setMap[argumentKey], argumentValue)
}
for argumentKey, argumentValues := range setMap {
if !Contains(allowedSetFlags, argumentKey) {
Log.Warningf(uiUtils.Warning, fmt.Sprintf("Ignoring set argument %s, flag name must be one of the following: \"%s\"", setValue, strings.Join(allowedSetFlags, "\", \"")))
Log.Warningf(uiUtils.Warning, fmt.Sprintf("Ignoring set argument name \"%s\", flag name must be one of the following: \"%s\"", argumentKey, strings.Join(allowedSetFlags, "\", \"")))
continue
}
mergeFlagValue(configElem, argumentKey, argumentValue)
if len(argumentValues) > 1 {
mergeFlagValues(configElem, argumentKey, argumentValues)
} else {
mergeFlagValue(configElem, argumentKey, argumentValues[0])
}
}
}
@@ -138,21 +157,25 @@ func mergeFlagValue(currentElem reflect.Value, flagKey string, flagValue string)
for i := 0; i < currentElem.NumField(); i++ {
currentField := currentElem.Type().Field(i)
currentFieldByName := currentElem.FieldByName(currentField.Name)
currentFieldKind := currentField.Type.Kind()
if currentField.Type.Kind() == reflect.Struct {
if currentFieldKind == reflect.Struct {
mergeFlagValue(currentFieldByName, flagKey, flagValue)
continue
}
if currentField.Tag.Get("yaml") != flagKey {
if getFieldNameByTag(currentField) != flagKey {
continue
}
flagValueKind := currentField.Type.Kind()
if currentFieldKind == reflect.Slice {
mergeFlagValues(currentElem, flagKey, []string{flagValue})
return
}
parsedValue, err := getParsedValue(flagValueKind, flagValue)
parsedValue, err := getParsedValue(currentFieldKind, flagValue)
if err != nil {
Log.Warningf(uiUtils.Red, fmt.Sprintf("Invalid value %v for flag name %s, expected %s", flagValue, flagKey, flagValueKind))
Log.Warningf(uiUtils.Warning, fmt.Sprintf("Invalid value %s for flag name %s, expected %s", flagValue, flagKey, currentFieldKind))
return
}
@@ -164,23 +187,29 @@ func mergeFlagValues(currentElem reflect.Value, flagKey string, flagValues []str
for i := 0; i < currentElem.NumField(); i++ {
currentField := currentElem.Type().Field(i)
currentFieldByName := currentElem.FieldByName(currentField.Name)
currentFieldKind := currentField.Type.Kind()
if currentField.Type.Kind() == reflect.Struct {
if currentFieldKind == reflect.Struct {
mergeFlagValues(currentFieldByName, flagKey, flagValues)
continue
}
if currentField.Tag.Get("yaml") != flagKey {
if getFieldNameByTag(currentField) != flagKey {
continue
}
if currentFieldKind != reflect.Slice {
Log.Warningf(uiUtils.Warning, fmt.Sprintf("Invalid values %s for flag name %s, expected %s", strings.Join(flagValues, ","), flagKey, currentFieldKind))
return
}
flagValueKind := currentField.Type.Elem().Kind()
parsedValues := reflect.MakeSlice(reflect.SliceOf(currentField.Type.Elem()), 0, 0)
for _, flagValue := range flagValues {
parsedValue, err := getParsedValue(flagValueKind, flagValue)
if err != nil {
Log.Warningf(uiUtils.Red, fmt.Sprintf("Invalid value %v for flag name %s, expected %s", flagValue, flagKey, flagValueKind))
Log.Warningf(uiUtils.Warning, fmt.Sprintf("Invalid value %s for flag name %s, expected %s", flagValue, flagKey, flagValueKind))
return
}
@@ -191,6 +220,10 @@ func mergeFlagValues(currentElem reflect.Value, flagKey string, flagValues []str
}
}
func getFieldNameByTag(field reflect.StructField) string {
return strings.Split(field.Tag.Get(FieldNameTag), ",")[0]
}
func getParsedValue(kind reflect.Kind, value string) (reflect.Value, error) {
switch kind {
case reflect.String:
@@ -276,3 +309,19 @@ func getParsedValue(kind reflect.Kind, value string) (reflect.Value, error) {
return reflect.ValueOf(nil), errors.New("value to parse does not match type")
}
func setZeroForReadonlyFields(currentElem reflect.Value) {
for i := 0; i < currentElem.NumField(); i++ {
currentField := currentElem.Type().Field(i)
currentFieldByName := currentElem.FieldByName(currentField.Name)
if currentField.Type.Kind() == reflect.Struct {
setZeroForReadonlyFields(currentFieldByName)
continue
}
if _, ok := currentField.Tag.Lookup(ReadonlyTag); ok {
currentFieldByName.Set(reflect.Zero(currentField.Type))
}
}
}

View File

@@ -11,6 +11,7 @@ const (
MizuResourcesNamespaceConfigName = "mizu-resources-namespace"
TelemetryConfigName = "telemetry"
DumpLogsConfigName = "dump-logs"
KubeConfigPathName = "kube-config-path"
)
type ConfigStruct struct {
@@ -18,10 +19,11 @@ type ConfigStruct struct {
Fetch configStructs.FetchConfig `yaml:"fetch"`
Version configStructs.VersionConfig `yaml:"version"`
View configStructs.ViewConfig `yaml:"view"`
AgentImage string `yaml:"agent-image"`
AgentImage string `yaml:"agent-image,omitempty" readonly:""`
MizuResourcesNamespace string `yaml:"mizu-resources-namespace" default:"mizu"`
Telemetry bool `yaml:"telemetry" default:"true"`
DumpLogs bool `yaml:"dump-logs" default:"false"`
KubeConfigPath string `yaml:"kube-config-path" default:""`
}
func (config *ConfigStruct) SetDefaults() {

View File

@@ -4,12 +4,12 @@ const (
DirectoryFetchName = "directory"
FromTimestampFetchName = "from"
ToTimestampFetchName = "to"
MizuPortFetchName = "port"
GuiPortFetchName = "gui-port"
)
type FetchConfig struct {
Directory string `yaml:"directory" default:"."`
FromTimestamp int `yaml:"from" default:"0"`
ToTimestamp int `yaml:"to" default:"0"`
MizuPort uint16 `yaml:"port" default:"8899"`
GuiPort uint16 `yaml:"gui-port" default:"8899"`
}

View File

@@ -16,10 +16,9 @@ const (
NamespacesTapName = "namespaces"
AnalysisTapName = "analysis"
AllNamespacesTapName = "all-namespaces"
KubeConfigPathTapName = "kube-config"
PlainTextFilterRegexesTapName = "regex-masking"
HideHealthChecksTapName = "hide-healthchecks"
DisableRedactionTapName = "no-redact"
IgnoredUserAgentsTapName = "ignored-user-agents"
HumanMaxEntriesDBSizeTapName = "max-entries-db-size"
DirectionTapName = "direction"
DryRunTapName = "dry-run"
@@ -27,21 +26,20 @@ const (
)
type TapConfig struct {
AnalysisDestination string `yaml:"dest" default:"up9.app"`
SleepIntervalSec int `yaml:"upload-interval" default:"10"`
PodRegexStr string `yaml:"regex" default:".*"`
GuiPort uint16 `yaml:"gui-port" default:"8899"`
Namespaces []string `yaml:"namespaces"`
Analysis bool `yaml:"analysis" default:"false"`
AllNamespaces bool `yaml:"all-namespaces" default:"false"`
KubeConfigPath string `yaml:"kube-config"`
PlainTextFilterRegexes []string `yaml:"regex-masking"`
HideHealthChecks bool `yaml:"hide-healthchecks" default:"false"`
DisableRedaction bool `yaml:"no-redact" default:"false"`
HumanMaxEntriesDBSize string `yaml:"max-entries-db-size" default:"200MB"`
Direction string `yaml:"direction" default:"in"`
DryRun bool `yaml:"dry-run" default:"false"`
EnforcePolicyFile string `yaml:"test-rules"`
AnalysisDestination string `yaml:"dest" default:"up9.app"`
SleepIntervalSec int `yaml:"upload-interval" default:"10"`
PodRegexStr string `yaml:"regex" default:".*"`
GuiPort uint16 `yaml:"gui-port" default:"8899"`
Namespaces []string `yaml:"namespaces"`
Analysis bool `yaml:"analysis" default:"false"`
AllNamespaces bool `yaml:"all-namespaces" default:"false"`
PlainTextFilterRegexes []string `yaml:"regex-masking"`
HealthChecksUserAgentHeaders []string `yaml:"ignored-user-agents"`
DisableRedaction bool `yaml:"no-redact" default:"false"`
HumanMaxEntriesDBSize string `yaml:"max-entries-db-size" default:"200MB"`
Direction string `yaml:"direction" default:"in"`
DryRun bool `yaml:"dry-run" default:"false"`
EnforcePolicyFile string `yaml:"test-rules"`
}
func (config *TapConfig) PodRegex() *regexp.Regexp {

39
cli/mizu/config_test.go Normal file
View File

@@ -0,0 +1,39 @@
package mizu_test
import (
"github.com/up9inc/mizu/cli/mizu"
"reflect"
"strings"
"testing"
)
func TestConfigWriteIgnoresReadonlyFields(t *testing.T) {
var readonlyFields []string
configElem := reflect.ValueOf(&mizu.ConfigStruct{}).Elem()
getFieldsWithReadonlyTag(configElem, &readonlyFields)
config, _ := mizu.GetConfigWithDefaults()
for _, readonlyField := range readonlyFields {
if strings.Contains(config, readonlyField) {
t.Errorf("unexpected result - readonly field: %v, config: %v", readonlyField, config)
}
}
}
func getFieldsWithReadonlyTag(currentElem reflect.Value, readonlyFields *[]string) {
for i := 0; i < currentElem.NumField(); i++ {
currentField := currentElem.Type().Field(i)
currentFieldByName := currentElem.FieldByName(currentField.Name)
if currentField.Type.Kind() == reflect.Struct {
getFieldsWithReadonlyTag(currentFieldByName, readonlyFields)
continue
}
if _, ok := currentField.Tag.Lookup(mizu.ReadonlyTag); ok {
fieldNameByTag := strings.Split(currentField.Tag.Get(mizu.FieldNameTag), ",")[0]
*readonlyFields = append(*readonlyFields, fieldNameByTag)
}
}
}

View File

@@ -14,16 +14,17 @@ var (
)
const (
ApiServerPodName = "mizu-api-server"
ClusterRoleBindingName = "mizu-cluster-role-binding"
ClusterRoleName = "mizu-cluster-role"
MizuResourcesPrefix = "mizu-"
ApiServerPodName = MizuResourcesPrefix + "api-server"
ClusterRoleBindingName = MizuResourcesPrefix + "cluster-role-binding"
ClusterRoleName = MizuResourcesPrefix + "cluster-role"
K8sAllNamespaces = ""
RoleBindingName = "mizu-role-binding"
RoleName = "mizu-role"
ServiceAccountName = "mizu-service-account"
TapperDaemonSetName = "mizu-tapper-daemon-set"
TapperPodName = "mizu-tapper"
ConfigMapName = "mizu-policy"
RoleBindingName = MizuResourcesPrefix + "role-binding"
RoleName = MizuResourcesPrefix + "role"
ServiceAccountName = MizuResourcesPrefix + "service-account"
TapperDaemonSetName = MizuResourcesPrefix + "tapper-daemon-set"
TapperPodName = MizuResourcesPrefix + "tapper"
ConfigMapName = MizuResourcesPrefix + "policy"
)
func GetMizuFolderPath() string {

View File

@@ -0,0 +1,82 @@
package mizu_test
import (
"github.com/up9inc/mizu/cli/mizu"
"testing"
)
func TestContainsExists(t *testing.T) {
tests := []struct {
slice []string
containsValue string
expected bool
}{
{slice: []string{"apple", "orange", "banana", "grapes"}, containsValue: "apple", expected: true},
{slice: []string{"apple", "orange", "banana", "grapes"}, containsValue: "orange", expected: true},
{slice: []string{"apple", "orange", "banana", "grapes"}, containsValue: "banana", expected: true},
{slice: []string{"apple", "orange", "banana", "grapes"}, containsValue: "grapes", expected: true},
}
for _, test := range tests {
actual := mizu.Contains(test.slice, test.containsValue)
if actual != test.expected {
t.Errorf("unexpected result - expected: %v, actual: %v", test.expected, actual)
}
}
}
func TestContainsNotExists(t *testing.T) {
tests := []struct {
slice []string
containsValue string
expected bool
}{
{slice: []string{"apple", "orange", "banana", "grapes"}, containsValue: "cat", expected: false},
{slice: []string{"apple", "orange", "banana", "grapes"}, containsValue: "dog", expected: false},
{slice: []string{"apple", "orange", "banana", "grapes"}, containsValue: "apples", expected: false},
{slice: []string{"apple", "orange", "banana", "grapes"}, containsValue: "rapes", expected: false},
}
for _, test := range tests {
actual := mizu.Contains(test.slice, test.containsValue)
if actual != test.expected {
t.Errorf("unexpected result - expected: %v, actual: %v", test.expected, actual)
}
}
}
func TestContainsEmptySlice(t *testing.T) {
tests := []struct {
slice []string
containsValue string
expected bool
}{
{slice: []string{}, containsValue: "cat", expected: false},
{slice: []string{}, containsValue: "dog", expected: false},
}
for _, test := range tests {
actual := mizu.Contains(test.slice, test.containsValue)
if actual != test.expected {
t.Errorf("unexpected result - expected: %v, actual: %v", test.expected, actual)
}
}
}
func TestContainsNilSlice(t *testing.T) {
tests := []struct {
slice []string
containsValue string
expected bool
}{
{slice: nil, containsValue: "cat", expected: false},
{slice: nil, containsValue: "dog", expected: false},
}
for _, test := range tests {
actual := mizu.Contains(test.slice, test.containsValue)
if actual != test.expected {
t.Errorf("unexpected result - expected: %v, actual: %v", test.expected, actual)
}
}
}

View File

@@ -46,7 +46,7 @@ func CheckVersionCompatibility(port uint16) (bool, error) {
return true, nil
}
Log.Infof(uiUtils.Red, fmt.Sprintf("cli version (%s) is not compatible with api version (%s)", SemVer, apiSemVer))
Log.Errorf(uiUtils.Red, fmt.Sprintf("cli version (%s) is not compatible with api version (%s)", SemVer, apiSemVer))
return false, nil
}

View File

@@ -3,8 +3,7 @@ module github.com/up9inc/mizu/shared
go 1.16
require (
github.com/google/martian v2.1.0+incompatible // indirect
github.com/gorilla/websocket v1.4.2
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect
github.com/docker/go-units v0.4.0
github.com/gorilla/websocket v1.4.2
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)

View File

@@ -1,8 +1,8 @@
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -75,9 +75,9 @@ func CreateWebSocketMessageTypeAnalyzeStatus(analyzeStatus AnalyzeStatus) WebSoc
}
type TrafficFilteringOptions struct {
PlainTextMaskingRegexes []*SerializableRegexp
HideHealthChecks bool
DisableRedaction bool
HealthChecksUserAgentHeaders []string
PlainTextMaskingRegexes []*SerializableRegexp
DisableRedaction bool
}
type VersionResponse struct {

View File

@@ -84,7 +84,6 @@ var staleTimeoutSeconds = flag.Int("staletimout", 120, "Max time in seconds to k
var memprofile = flag.String("memprofile", "", "Write memory profile")
// output
var dumpToHar = flag.Bool("hardump", false, "Dump traffic to har files")
var HarOutputDir = flag.String("hardir", "", "Directory in which to store output har files")
var harEntriesPerFile = flag.Int("harentriesperfile", 200, "Number of max number of har entries to store in each file")
@@ -186,19 +185,12 @@ func (c *Context) GetCaptureInfo() gopacket.CaptureInfo {
func StartPassiveTapper(opts *TapOpts) (<-chan *OutputChannelItem, <-chan *OutboundLink) {
hostMode = opts.HostMode
var harWriter *HarWriter
if *dumpToHar {
harWriter = NewHarWriter(*HarOutputDir, *harEntriesPerFile)
}
harWriter := NewHarWriter(*HarOutputDir, *harEntriesPerFile)
outboundLinkWriter := NewOutboundLinkWriter()
go startPassiveTapper(harWriter, outboundLinkWriter)
if harWriter != nil {
return harWriter.OutChan, outboundLinkWriter.OutChan
}
return nil, outboundLinkWriter.OutChan
return harWriter.OutChan, outboundLinkWriter.OutChan
}
func startMemoryProfiler() {
@@ -321,10 +313,8 @@ func startPassiveTapper(harWriter *HarWriter, outboundLinkWriter *OutboundLinkWr
}
}
if *dumpToHar {
harWriter.Start()
defer harWriter.Stop()
}
harWriter.Start()
defer harWriter.Stop()
defer outboundLinkWriter.Stop()
var dec gopacket.Decoder

View File

@@ -25,7 +25,8 @@ interface HAREntry {
interface Rules {
status: boolean;
latency: number
latency: number;
numberOfRules: number;
}
interface HAREntryProps {
@@ -36,6 +37,7 @@ interface HAREntryProps {
export const HarEntry: React.FC<HAREntryProps> = ({entry, setFocusedEntryId, isSelected}) => {
const classification = getClassification(entry.statusCode)
const numberOfRules = entry.rules.numberOfRules
let ingoingIcon;
let outgoingIcon;
switch(classification) {
@@ -55,16 +57,36 @@ export const HarEntry: React.FC<HAREntryProps> = ({entry, setFocusedEntryId, isS
break;
}
}
let backgroundColor = "";
if ('latency' in entry.rules) {
let additionalRulesProperties = "";
let ruleSuccess: boolean;
let rule = 'latency' in entry.rules
if (rule) {
if (entry.rules.latency !== -1) {
backgroundColor = entry.rules.latency >= entry.latency ? styles.ruleSuccessRow : styles.ruleFailureRow
if (entry.rules.latency >= entry.latency) {
additionalRulesProperties = styles.ruleSuccessRow
ruleSuccess = true
} else {
additionalRulesProperties = styles.ruleFailureRow
ruleSuccess = false
}
if (isSelected) {
additionalRulesProperties += ` ${entry.rules.latency >= entry.latency ? styles.ruleSuccessRowSelected : styles.ruleFailureRowSelected}`
}
} else {
backgroundColor = entry.rules.status ? styles.ruleSuccessRow : styles.ruleFailureRow
if (entry.rules.status) {
additionalRulesProperties = styles.ruleSuccessRow
ruleSuccess = true
} else {
additionalRulesProperties = styles.ruleFailureRow
ruleSuccess = false
}
if (isSelected) {
additionalRulesProperties += ` ${entry.rules.status ? styles.ruleSuccessRowSelected : styles.ruleFailureRowSelected}`
}
}
}
return <>
<div id={entry.id} className={`${styles.row} ${isSelected ? styles.rowSelected : backgroundColor}`} onClick={() => setFocusedEntryId(entry.id)}>
<div id={entry.id} className={`${styles.row} ${isSelected && !rule ? styles.rowSelected : additionalRulesProperties}`} onClick={() => setFocusedEntryId(entry.id)}>
{entry.statusCode && <div>
<StatusCode statusCode={entry.statusCode}/>
</div>}
@@ -74,6 +96,13 @@ export const HarEntry: React.FC<HAREntryProps> = ({entry, setFocusedEntryId, isS
{entry.service}
</div>
</div>
{
rule ?
<div className={`${ruleSuccess ? styles.ruleNumberTextSuccess : styles.ruleNumberTextFailure}`}>
{`Rules (${numberOfRules})`}
</div>
: ""
}
<div className={styles.directionContainer}>
{entry.isOutgoing ?
<img src={outgoingIcon} alt="outgoing traffic" title="outgoing"/>

View File

@@ -43,7 +43,6 @@ const HarEntryTitle: React.FC<any> = ({har}) => {
<div style={{margin: "0 18px", opacity: 0.5}}>{formatSize(bodySize)}</div>
<div style={{marginRight: 18, opacity: 0.5}}>{status} {statusText}</div>
<div style={{marginRight: 18, opacity: 0.5}}>{Math.round(receive)}ms</div>
<div style={{opacity: 0.5}}>{'rulesMatched' in entries[0] ? entries[0].rulesMatched?.length : '0'} Rules Applied</div>
</div>;
};

View File

@@ -92,3 +92,6 @@
tr td:first-child
white-space: nowrap
padding-right: .5rem
.noRules
padding: 0 1rem 1rem

View File

@@ -260,7 +260,7 @@ export const HAREntryTablePolicySection: React.FC<HAREntryPolicySectionProps> =
</table>
</HAREntrySectionContainer>
</> : <span/>
</> : <span className={styles.noRules}>No rules could be applied to this request.</span>
}
</React.Fragment>
}

View File

@@ -24,12 +24,40 @@
margin-right: 3px
.ruleSuccessRow
border: 1px $success-color solid
border-left: 5px $success-color solid
background: #E8FFF1
.ruleSuccessRowSelected
border: 1px #6FCF97 solid
border-left: 5px #6FCF97 solid
margin-left: 10px
margin-right: 3px
.ruleFailureRow
background: #FFE9EF
.ruleFailureRowSelected
border: 1px $failure-color solid
border-left: 5px $failure-color solid
margin-left: 10px
margin-right: 3px
.ruleNumberTextFailure
color: #DB2156
font-family: Source Sans Pro;
font-style: normal;
font-weight: 600;
font-size: 12px;
line-height: 15px;
padding-right: 12px
.ruleNumberTextSuccess
color: #219653
font-family: Source Sans Pro;
font-style: normal;
font-weight: 600;
font-size: 12px;
line-height: 15px;
padding-right: 12px
.service
text-overflow: ellipsis