mirror of
https://github.com/kubeshark/kubeshark.git
synced 2026-05-05 16:57:48 +00:00
Compare commits
120 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30fce5d765 | ||
|
|
90040798b8 | ||
|
|
9eecddddd5 | ||
|
|
cc49e815d6 | ||
|
|
c26eb843e3 | ||
|
|
26efaa101d | ||
|
|
352567c56e | ||
|
|
51fc3307be | ||
|
|
cdf1c39a52 | ||
|
|
db1f7d34cf | ||
|
|
9212c195b4 | ||
|
|
7b333556d0 | ||
|
|
8ba96acf05 | ||
|
|
f164e54fee | ||
|
|
649b733ba1 | ||
|
|
e8ea93cb64 | ||
|
|
5dacd41ba9 | ||
|
|
7f837fe947 | ||
|
|
02bd7883cb | ||
|
|
1841798646 | ||
|
|
749bee6d55 | ||
|
|
043b845c06 | ||
|
|
8c7f82c6f0 | ||
|
|
ec4fa2ee4f | ||
|
|
b50eced489 | ||
|
|
5392475486 | ||
|
|
65bb262652 | ||
|
|
842d95c836 | ||
|
|
9fa9b67328 | ||
|
|
6337b75f0e | ||
|
|
b9d2e671c7 | ||
|
|
0840642c98 | ||
|
|
d5b01347df | ||
|
|
7dca1ad889 | ||
|
|
616eccb2cf | ||
|
|
30f07479cb | ||
|
|
7f880417e9 | ||
|
|
6b52458642 | ||
|
|
858a64687d | ||
|
|
819ccf54cd | ||
|
|
7cc077c8a0 | ||
|
|
fae5f22d25 | ||
|
|
eba7a3b476 | ||
|
|
cf231538f4 | ||
|
|
073b0b72d3 | ||
|
|
c8705822b3 | ||
|
|
d4436d9f15 | ||
|
|
4e0ff74944 | ||
|
|
366c1d0c6c | ||
|
|
17fa163ee3 | ||
|
|
3644fdb533 | ||
|
|
ab7c4e72c6 | ||
|
|
e25e7925b6 | ||
|
|
80237c8090 | ||
|
|
a310953f05 | ||
|
|
a9e92b60f5 | ||
|
|
35e40cd230 | ||
|
|
2575ad722a | ||
|
|
afd5757315 | ||
|
|
dba8b1f215 | ||
|
|
6dd0ef1268 | ||
|
|
83cfaed1a3 | ||
|
|
41cb9ee12e | ||
|
|
667f0dc87d | ||
|
|
a34c2fc0dc | ||
|
|
7a31263e4a | ||
|
|
7f9fd82c0e | ||
|
|
a37d1f4aeb | ||
|
|
acdbdedd5d | ||
|
|
a9b5eba9d4 | ||
|
|
80201224c6 | ||
|
|
e6e7d8d58b | ||
|
|
bf27e94003 | ||
|
|
2ae0a2400d | ||
|
|
db1f4458c5 | ||
|
|
5d5c11c37c | ||
|
|
b4f3b2c540 | ||
|
|
a427534605 | ||
|
|
1d6ca9d392 | ||
|
|
f74a52d4dc | ||
|
|
6d2e9af5d7 | ||
|
|
e4ff4a0745 | ||
|
|
f9677dbaa1 | ||
|
|
0afab6c068 | ||
|
|
1d1b62ec4f | ||
|
|
e2db5087b8 | ||
|
|
241477fb5c | ||
|
|
c8e5886a96 | ||
|
|
8a8cf4aa77 | ||
|
|
7b73004e85 | ||
|
|
56dc6843e0 | ||
|
|
0409eb239d | ||
|
|
cbe04af801 | ||
|
|
59dec1a547 | ||
|
|
c4afeee5b3 | ||
|
|
8c9b8d3217 | ||
|
|
d705ae3eb6 | ||
|
|
c53b2148d1 | ||
|
|
ca897dd3c7 | ||
|
|
4406919565 | ||
|
|
413fb5b3f5 | ||
|
|
e36c146979 | ||
|
|
1cf9c29ef0 | ||
|
|
02e02718d2 | ||
|
|
1a0517f46b | ||
|
|
efbb432df9 | ||
|
|
dfea8884d4 | ||
|
|
d34dacbbe2 | ||
|
|
0595df8b87 | ||
|
|
ebbe6458a8 | ||
|
|
7f2021c312 | ||
|
|
824945141a | ||
|
|
0244f12167 | ||
|
|
60533a9591 | ||
|
|
90f0f603c7 | ||
|
|
683d199774 | ||
|
|
fa632b49a7 | ||
|
|
04579eb03c | ||
|
|
dea223bfe1 | ||
|
|
06c8056443 |
@@ -2,7 +2,7 @@
|
|||||||
.dockerignore
|
.dockerignore
|
||||||
.editorconfig
|
.editorconfig
|
||||||
.gitignore
|
.gitignore
|
||||||
.env.*
|
**/.env*
|
||||||
Dockerfile
|
Dockerfile
|
||||||
Makefile
|
Makefile
|
||||||
LICENSE
|
LICENSE
|
||||||
|
|||||||
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal 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.
|
||||||
32
.github/workflows/acceptance_tests.yml
vendored
Normal file
32
.github/workflows/acceptance_tests.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: acceptance tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'develop'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: mizu-acceptance-tests-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run-acceptance-tests:
|
||||||
|
name: Run acceptance tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set up Go 1.16
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: '^1.16'
|
||||||
|
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Setup acceptance test
|
||||||
|
run: source ./acceptanceTests/setup.sh
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: make acceptance-test
|
||||||
46
.github/workflows/pr_validation.yml
vendored
Normal file
46
.github/workflows/pr_validation.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
name: PR validation
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- 'develop'
|
||||||
|
- 'main'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: mizu-pr-validation-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-cli:
|
||||||
|
name: Build CLI
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set up Go 1.16
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: '1.16'
|
||||||
|
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Build CLI
|
||||||
|
run: make cli
|
||||||
|
|
||||||
|
build-agent:
|
||||||
|
name: Build Agent
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set up Go 1.16
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: '1.16'
|
||||||
|
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- shell: bash
|
||||||
|
run: |
|
||||||
|
sudo apt-get install libpcap-dev
|
||||||
|
|
||||||
|
- name: Build Agent
|
||||||
|
run: make agent
|
||||||
7
.github/workflows/publish.yml
vendored
7
.github/workflows/publish.yml
vendored
@@ -1,9 +1,15 @@
|
|||||||
name: publish
|
name: publish
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'develop'
|
- 'develop'
|
||||||
- 'main'
|
- 'main'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: mizu-publish-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
docker:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -78,4 +84,3 @@ jobs:
|
|||||||
tag: ${{ steps.versioning.outputs.version }}
|
tag: ${{ steps.versioning.outputs.version }}
|
||||||
prerelease: ${{ github.ref != 'refs/heads/main' }}
|
prerelease: ${{ github.ref != 'refs/heads/main' }}
|
||||||
bodyFile: 'cli/bin/README.md'
|
bodyFile: 'cli/bin/README.md'
|
||||||
|
|
||||||
|
|||||||
56
.github/workflows/tests_validation.yml
vendored
Normal file
56
.github/workflows/tests_validation.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
name: tests validation
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- 'develop'
|
||||||
|
- 'main'
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'develop'
|
||||||
|
- 'main'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: mizu-tests-validation-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run-tests-cli:
|
||||||
|
name: Run CLI tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set up Go 1.16
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: '^1.16'
|
||||||
|
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: make test-cli
|
||||||
|
|
||||||
|
- name: Upload coverage to Codecov
|
||||||
|
uses: codecov/codecov-action@v2
|
||||||
|
|
||||||
|
run-tests-agent:
|
||||||
|
name: Run Agent tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set up Go 1.16
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: '^1.16'
|
||||||
|
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- shell: bash
|
||||||
|
run: |
|
||||||
|
sudo apt-get install libpcap-dev
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: make test-agent
|
||||||
|
|
||||||
|
- name: Upload coverage to Codecov
|
||||||
|
uses: codecov/codecov-action@v2
|
||||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -19,3 +19,13 @@ build
|
|||||||
|
|
||||||
# Mac OS
|
# Mac OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Ignore the scripts that are created for development
|
||||||
|
*dev.*
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
|
||||||
|
# pprof
|
||||||
|
pprof/*
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ FROM golang:1.16-alpine AS builder
|
|||||||
# Set necessary environment variables needed for our image.
|
# Set necessary environment variables needed for our image.
|
||||||
ENV CGO_ENABLED=1 GOOS=linux GOARCH=amd64
|
ENV CGO_ENABLED=1 GOOS=linux GOARCH=amd64
|
||||||
|
|
||||||
RUN apk add libpcap-dev gcc g++ make
|
RUN apk add libpcap-dev gcc g++ make bash
|
||||||
|
|
||||||
# Move to agent working directory (/agent-build).
|
# Move to agent working directory (/agent-build).
|
||||||
WORKDIR /app/agent-build
|
WORKDIR /app/agent-build
|
||||||
@@ -19,6 +19,7 @@ WORKDIR /app/agent-build
|
|||||||
COPY agent/go.mod agent/go.sum ./
|
COPY agent/go.mod agent/go.sum ./
|
||||||
COPY shared/go.mod shared/go.mod ../shared/
|
COPY shared/go.mod shared/go.mod ../shared/
|
||||||
COPY tap/go.mod tap/go.mod ../tap/
|
COPY tap/go.mod tap/go.mod ../tap/
|
||||||
|
COPY tap/api/go.* ../tap/api/
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
# cheap trick to make the build faster (As long as go.mod wasn't changes)
|
# cheap trick to make the build faster (As long as go.mod wasn't changes)
|
||||||
RUN go list -f '{{.Path}}@{{.Version}}' -m all | sed 1d | grep -e 'go-cache' -e 'sqlite' | xargs go get
|
RUN go list -f '{{.Path}}@{{.Version}}' -m all | sed 1d | grep -e 'go-cache' -e 'sqlite' | xargs go get
|
||||||
@@ -38,6 +39,8 @@ RUN go build -ldflags="-s -w \
|
|||||||
-X 'mizuserver/pkg/version.BuildTimestamp=${BUILD_TIMESTAMP}' \
|
-X 'mizuserver/pkg/version.BuildTimestamp=${BUILD_TIMESTAMP}' \
|
||||||
-X 'mizuserver/pkg/version.SemVer=${SEM_VER}'" -o mizuagent .
|
-X 'mizuserver/pkg/version.SemVer=${SEM_VER}'" -o mizuagent .
|
||||||
|
|
||||||
|
COPY devops/build_extensions.sh ..
|
||||||
|
RUN cd .. && /bin/bash build_extensions.sh
|
||||||
|
|
||||||
FROM alpine:3.13.5
|
FROM alpine:3.13.5
|
||||||
|
|
||||||
@@ -46,10 +49,9 @@ WORKDIR /app
|
|||||||
|
|
||||||
# Copy binary and config files from /build to root folder of scratch container.
|
# 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/mizuagent", "."]
|
||||||
|
COPY --from=builder ["/app/agent/build/extensions", "extensions"]
|
||||||
COPY --from=site-build ["/app/ui-build/build", "site"]
|
COPY --from=site-build ["/app/ui-build/build", "site"]
|
||||||
|
|
||||||
COPY agent/start.sh .
|
|
||||||
|
|
||||||
# gin-gonic runs in debug mode without this
|
# gin-gonic runs in debug mode without this
|
||||||
ENV GIN_MODE=release
|
ENV GIN_MODE=release
|
||||||
|
|
||||||
|
|||||||
33
Makefile
33
Makefile
@@ -23,29 +23,32 @@ export SEM_VER?=0.0.0
|
|||||||
|
|
||||||
ui: ## Build UI.
|
ui: ## Build UI.
|
||||||
@(cd ui; npm i ; npm run build; )
|
@(cd ui; npm i ; npm run build; )
|
||||||
@ls -l ui/build
|
@ls -l ui/build
|
||||||
|
|
||||||
cli: ## Build CLI.
|
cli: ## Build CLI.
|
||||||
@echo "building cli"; cd cli && $(MAKE) build
|
@echo "building cli"; cd cli && $(MAKE) build
|
||||||
|
|
||||||
|
build-cli-ci: ## Build CLI for CI.
|
||||||
|
@echo "building cli for ci"; cd cli && $(MAKE) build GIT_BRANCH=ci SUFFIX=ci
|
||||||
|
|
||||||
agent: ## Build agent.
|
agent: ## Build agent.
|
||||||
@(echo "building mizu agent .." )
|
@(echo "building mizu agent .." )
|
||||||
@(cd agent; go build -o build/mizuagent main.go)
|
@(cd agent; go build -o build/mizuagent main.go)
|
||||||
|
${MAKE} extensions
|
||||||
@ls -l agent/build
|
@ls -l agent/build
|
||||||
|
|
||||||
#tap: ## build tap binary
|
docker: ## Build and publish agent docker image.
|
||||||
# @(cd tap; go build -o build/tap ./src)
|
$(MAKE) push-docker
|
||||||
# @ls -l tap/build
|
|
||||||
|
|
||||||
docker: ## Build Docker image.
|
|
||||||
@(echo "building docker image" )
|
|
||||||
./build-push-featurebranch.sh
|
|
||||||
|
|
||||||
push: push-docker push-cli ## Build and publish agent docker image & CLI.
|
push: push-docker push-cli ## Build and publish agent docker image & CLI.
|
||||||
|
|
||||||
push-docker: ## Build and publish agent docker image.
|
push-docker: ## Build and publish agent docker image.
|
||||||
@echo "publishing Docker image .. "
|
@echo "publishing Docker image .. "
|
||||||
./build-push-featurebranch.sh
|
devops/build-push-featurebranch.sh
|
||||||
|
|
||||||
|
build-docker-ci: ## Build agent docker image for CI.
|
||||||
|
@echo "building docker image for ci"
|
||||||
|
devops/build-agent-ci.sh
|
||||||
|
|
||||||
push-cli: ## Build and publish CLI.
|
push-cli: ## Build and publish CLI.
|
||||||
@echo "publishing CLI .. "
|
@echo "publishing CLI .. "
|
||||||
@@ -55,7 +58,6 @@ push-cli: ## Build and publish CLI.
|
|||||||
gsutil cp -r ./cli/bin/* gs://${BUCKET_PATH}/
|
gsutil cp -r ./cli/bin/* gs://${BUCKET_PATH}/
|
||||||
gsutil setmeta -r -h "Cache-Control:public, max-age=30" gs://${BUCKET_PATH}/\*
|
gsutil setmeta -r -h "Cache-Control:public, max-age=30" gs://${BUCKET_PATH}/\*
|
||||||
|
|
||||||
|
|
||||||
clean: clean-ui clean-agent clean-cli clean-docker ## Clean all build artifacts.
|
clean: clean-ui clean-agent clean-cli clean-docker ## Clean all build artifacts.
|
||||||
|
|
||||||
clean-ui: ## Clean UI.
|
clean-ui: ## Clean UI.
|
||||||
@@ -70,3 +72,14 @@ clean-cli: ## Clean CLI.
|
|||||||
clean-docker:
|
clean-docker:
|
||||||
@(echo "DOCKER cleanup - NOT IMPLEMENTED YET " )
|
@(echo "DOCKER cleanup - NOT IMPLEMENTED YET " )
|
||||||
|
|
||||||
|
extensions:
|
||||||
|
devops/build_extensions.sh
|
||||||
|
|
||||||
|
test-cli:
|
||||||
|
@echo "running cli tests"; cd cli && $(MAKE) test
|
||||||
|
|
||||||
|
test-agent:
|
||||||
|
@echo "running agent tests"; cd agent && $(MAKE) test
|
||||||
|
|
||||||
|
acceptance-test:
|
||||||
|
@echo "running acceptance tests"; cd acceptanceTests && $(MAKE) test
|
||||||
|
|||||||
226
README.md
226
README.md
@@ -1,22 +1,25 @@
|
|||||||

|

|
||||||
|
|
||||||
# The API Traffic Viewer for Kubernetes
|
# 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 enabling you to view all API communication between microservices to help your debug and troubleshoot regressions.
|
||||||
|
|
||||||
|
Think TCPDump and Chrome Dev Tools combined.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Simple and powerful CLI
|
- 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
|
- No installation or code instrumentation
|
||||||
- Works completely on premises (on-prem)
|
- Works completely on premises
|
||||||
|
|
||||||
## Download
|
## 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
|
* for MacOS - Intel
|
||||||
```
|
```
|
||||||
@@ -32,140 +35,52 @@ https://github.com/up9inc/mizu/releases/latest/download/mizu_linux_amd64 \
|
|||||||
&& chmod 755 mizu
|
&& 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
|
### Development (unstable) Build
|
||||||
Pick one from the [Releases](https://github.com/up9inc/mizu/releases) page.
|
Pick one from the [Releases](https://github.com/up9inc/mizu/releases) page
|
||||||
|
|
||||||
## Prerequisites
|
## Kubeconfig & Permissions
|
||||||
1. Set `KUBECONFIG` environment variable to your kubernetes configuration. If this is not set, mizu assumes that configuration is at `${HOME}/.kube/config`
|
While `mizu`most often works out of the box, you can influence its behavior:
|
||||||
2. mizu needs following permissions on your kubernetes cluster to run
|
|
||||||
|
|
||||||
```yaml
|
1. [OPTIONAL] Set `KUBECONFIG` environment variable to your Kubernetes configuration. If this is not set, Mizu assumes that configuration is at `${HOME}/.kube/config`
|
||||||
- apiGroups:
|
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)
|
||||||
- ""
|
|
||||||
resources:
|
|
||||||
- pods
|
|
||||||
verbs:
|
|
||||||
- list
|
|
||||||
- watch
|
|
||||||
- create
|
|
||||||
- apiGroups:
|
|
||||||
- ""
|
|
||||||
resources:
|
|
||||||
- services
|
|
||||||
verbs:
|
|
||||||
- create
|
|
||||||
- apiGroups:
|
|
||||||
- apps
|
|
||||||
resources:
|
|
||||||
- daemonsets
|
|
||||||
verbs:
|
|
||||||
- create
|
|
||||||
- patch
|
|
||||||
- apiGroups:
|
|
||||||
- ""
|
|
||||||
resources:
|
|
||||||
- namespaces
|
|
||||||
verbs:
|
|
||||||
- list
|
|
||||||
- watch
|
|
||||||
- create
|
|
||||||
- delete
|
|
||||||
- apiGroups:
|
|
||||||
- ""
|
|
||||||
resources:
|
|
||||||
- services/proxy
|
|
||||||
verbs:
|
|
||||||
- get
|
|
||||||
```
|
|
||||||
3. Optionally, for resolving traffic ip to kubernetes service name, mizu needs below permissions
|
|
||||||
|
|
||||||
```yaml
|
For detailed list of k8s permissions see [PERMISSIONS](docs/PERMISSIONS.md) document
|
||||||
- apiGroups:
|
|
||||||
- ""
|
|
||||||
resources:
|
|
||||||
- pods
|
|
||||||
verbs:
|
|
||||||
- get
|
|
||||||
- apiGroups:
|
|
||||||
- ""
|
|
||||||
resources:
|
|
||||||
- services
|
|
||||||
verbs:
|
|
||||||
- get
|
|
||||||
- list
|
|
||||||
- watch
|
|
||||||
- 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
|
|
||||||
- apiGroups:
|
|
||||||
- ""
|
|
||||||
resources:
|
|
||||||
- serviceaccounts
|
|
||||||
verbs:
|
|
||||||
- get
|
|
||||||
- create
|
|
||||||
- apiGroups:
|
|
||||||
- rbac.authorization.k8s.io
|
|
||||||
resources:
|
|
||||||
- clusterroles
|
|
||||||
verbs:
|
|
||||||
- list
|
|
||||||
- create
|
|
||||||
- delete
|
|
||||||
- apiGroups:
|
|
||||||
- rbac.authorization.k8s.io
|
|
||||||
resources:
|
|
||||||
- clusterrolebindings
|
|
||||||
verbs:
|
|
||||||
- list
|
|
||||||
- create
|
|
||||||
- delete
|
|
||||||
```
|
|
||||||
|
|
||||||
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
|
1. Find pods you'd like to tap to in your Kubernetes cluster
|
||||||
2. Run `mizu tap PODNAME` or `mizu tap REGEX`
|
2. Run `mizu tap` or `mizu tap PODNAME`
|
||||||
3. Open browser on `http://localhost:8899/mizu` **or** as instructed in the CLI ..
|
3. Open browser on `http://localhost:8899/mizu` **or** as instructed in the CLI
|
||||||
4. Watch the API traffic flowing ..
|
4. Watch the API traffic flowing
|
||||||
5. Type ^C to stop
|
5. Type ^C to stop
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
Run `mizu help` for usage options
|
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 -
|
To tap specific pod -
|
||||||
```
|
```bash
|
||||||
$ kubectl get pods
|
$ kubectl get pods
|
||||||
NAME READY STATUS RESTARTS AGE
|
NAME READY STATUS RESTARTS AGE
|
||||||
front-end-649fc5fd6-kqbtn 2/2 Running 0 7m
|
front-end-649fc5fd6-kqbtn 2/2 Running 0 7m
|
||||||
@@ -178,7 +93,7 @@ To tap specific pod -
|
|||||||
```
|
```
|
||||||
|
|
||||||
To tap multiple pods using regex -
|
To tap multiple pods using regex -
|
||||||
```
|
```bash
|
||||||
$ kubectl get pods
|
$ kubectl get pods
|
||||||
NAME READY STATUS RESTARTS AGE
|
NAME READY STATUS RESTARTS AGE
|
||||||
carts-66c77f5fbb-fq65r 2/2 Running 0 20m
|
carts-66c77f5fbb-fq65r 2/2 Running 0 20m
|
||||||
@@ -193,3 +108,66 @@ To tap multiple pods using regex -
|
|||||||
^C
|
^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 using command-line options:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ mizu tap "^ca.*" --set tap.ignored-user-agents=kube-probe --set tap.ignored-user-agents=prometheus
|
||||||
|
+carts-66c77f5fbb-fq65r
|
||||||
|
+catalogue-5f4cb7cf5-7zrmn
|
||||||
|
Web interface is now available at http://localhost:8899
|
||||||
|
^C
|
||||||
|
|
||||||
|
```
|
||||||
|
Any request that contains `User-Agent` header with one of the specified values (`kube-probe` or `prometheus`) will not be captured
|
||||||
|
|
||||||
|
### Traffic validation rules
|
||||||
|
|
||||||
|
This feature allows you to define set of simple rules, and test the traffic against them.
|
||||||
|
Such validation may test response for specific JSON fields, headers, etc.
|
||||||
|
|
||||||
|
Please see [TRAFFIC RULES](docs/POLICY_RULES.md) page for more details and syntax.
|
||||||
|
|
||||||
|
|
||||||
|
## How to Run local UI
|
||||||
|
|
||||||
|
- run from mizu/agent `go run main.go --hars-read --hars-dir <folder>`
|
||||||
|
|
||||||
|
- copy Har files into the folder from last command
|
||||||
|
|
||||||
|
- change `MizuWebsocketURL` and `apiURL` in `api.js` file
|
||||||
|
|
||||||
|
- run from mizu/ui - `npm run start`
|
||||||
|
|
||||||
|
- open browser on `localhost:3000`
|
||||||
|
|||||||
2
acceptanceTests/Makefile
Normal file
2
acceptanceTests/Makefile
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
test: ## Run acceptance tests.
|
||||||
|
@go test ./... -timeout 1h
|
||||||
283
acceptanceTests/config_test.go
Normal file
283
acceptanceTests/config_test.go
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
package acceptanceTests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tapConfig struct {
|
||||||
|
GuiPort uint16 `yaml:"gui-port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type configStruct struct {
|
||||||
|
Tap tapConfig `yaml:"tap"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigRegenerate(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configPath, configPathErr := getConfigPath()
|
||||||
|
if configPathErr != nil {
|
||||||
|
t.Errorf("failed to get config path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configCmdArgs := getDefaultConfigCommandArgs()
|
||||||
|
|
||||||
|
configCmdArgs = append(configCmdArgs, "-r")
|
||||||
|
|
||||||
|
configCmd := exec.Command(cliPath, configCmdArgs...)
|
||||||
|
t.Logf("running command: %v", configCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := os.Remove(configPath); err != nil {
|
||||||
|
t.Logf("failed to delete config file, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := configCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start config command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := configCmd.Wait(); err != nil {
|
||||||
|
t.Errorf("failed to wait config command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, readFileErr := ioutil.ReadFile(configPath)
|
||||||
|
if readFileErr != nil {
|
||||||
|
t.Errorf("failed to read config file, err: %v", readFileErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigGuiPort(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []uint16{8898}
|
||||||
|
|
||||||
|
for _, guiPort := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%d", guiPort), func(t *testing.T) {
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configPath, configPathErr := getConfigPath()
|
||||||
|
if configPathErr != nil {
|
||||||
|
t.Errorf("failed to get config path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config := configStruct{}
|
||||||
|
config.Tap.GuiPort = guiPort
|
||||||
|
|
||||||
|
configBytes, marshalErr := yaml.Marshal(config)
|
||||||
|
if marshalErr != nil {
|
||||||
|
t.Errorf("failed to marshal config, err: %v", marshalErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if writeErr := ioutil.WriteFile(configPath, configBytes, 0644); writeErr != nil {
|
||||||
|
t.Errorf("failed to write config to file, err: %v", writeErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Remove(configPath); err != nil {
|
||||||
|
t.Logf("failed to delete config file, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(guiPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigSetGuiPort(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
ConfigFileGuiPort uint16
|
||||||
|
SetGuiPort uint16
|
||||||
|
}{
|
||||||
|
{ConfigFileGuiPort: 8898, SetGuiPort: 8897},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, guiPortStruct := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%d", guiPortStruct.SetGuiPort), func(t *testing.T) {
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configPath, configPathErr := getConfigPath()
|
||||||
|
if configPathErr != nil {
|
||||||
|
t.Errorf("failed to get config path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config := configStruct{}
|
||||||
|
config.Tap.GuiPort = guiPortStruct.ConfigFileGuiPort
|
||||||
|
|
||||||
|
configBytes, marshalErr := yaml.Marshal(config)
|
||||||
|
if marshalErr != nil {
|
||||||
|
t.Errorf("failed to marshal config, err: %v", marshalErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if writeErr := ioutil.WriteFile(configPath, configBytes, 0644); writeErr != nil {
|
||||||
|
t.Errorf("failed to write config to file, err: %v", writeErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
|
tapCmdArgs = append(tapCmdArgs, "--set", fmt.Sprintf("tap.gui-port=%v", guiPortStruct.SetGuiPort))
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Remove(configPath); err != nil {
|
||||||
|
t.Logf("failed to delete config file, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(guiPortStruct.SetGuiPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigFlagGuiPort(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
ConfigFileGuiPort uint16
|
||||||
|
FlagGuiPort uint16
|
||||||
|
}{
|
||||||
|
{ConfigFileGuiPort: 8898, FlagGuiPort: 8896},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, guiPortStruct := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%d", guiPortStruct.FlagGuiPort), func(t *testing.T) {
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configPath, configPathErr := getConfigPath()
|
||||||
|
if configPathErr != nil {
|
||||||
|
t.Errorf("failed to get config path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config := configStruct{}
|
||||||
|
config.Tap.GuiPort = guiPortStruct.ConfigFileGuiPort
|
||||||
|
|
||||||
|
configBytes, marshalErr := yaml.Marshal(config)
|
||||||
|
if marshalErr != nil {
|
||||||
|
t.Errorf("failed to marshal config, err: %v", marshalErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if writeErr := ioutil.WriteFile(configPath, configBytes, 0644); writeErr != nil {
|
||||||
|
t.Errorf("failed to write config to file, err: %v", writeErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
|
tapCmdArgs = append(tapCmdArgs, "-p", fmt.Sprintf("%v", guiPortStruct.FlagGuiPort))
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Remove(configPath); err != nil {
|
||||||
|
t.Logf("failed to delete config file, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(guiPortStruct.FlagGuiPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
5
acceptanceTests/go.mod
Normal file
5
acceptanceTests/go.mod
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module github.com/up9inc/mizu/tests
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||||
4
acceptanceTests/go.sum
Normal file
4
acceptanceTests/go.sum
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
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=
|
||||||
196
acceptanceTests/logs_test.go
Normal file
196
acceptanceTests/logs_test.go
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
package acceptanceTests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"os/exec"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLogs(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logsCmdArgs := getDefaultLogsCommandArgs()
|
||||||
|
|
||||||
|
logsCmd := exec.Command(cliPath, logsCmdArgs...)
|
||||||
|
t.Logf("running command: %v", logsCmd.String())
|
||||||
|
|
||||||
|
if err := logsCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start logs command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := logsCmd.Wait(); err != nil {
|
||||||
|
t.Errorf("failed to wait logs command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logsPath, logsPathErr := getLogsPath()
|
||||||
|
if logsPathErr != nil {
|
||||||
|
t.Errorf("failed to get logs path, err: %v", logsPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
zipReader, zipError := zip.OpenReader(logsPath)
|
||||||
|
if zipError != nil {
|
||||||
|
t.Errorf("failed to get zip reader, err: %v", zipError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := zipReader.Close(); err != nil {
|
||||||
|
t.Logf("failed to close zip reader, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
var logsFileNames []string
|
||||||
|
for _, file := range zipReader.File {
|
||||||
|
logsFileNames = append(logsFileNames, file.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Contains(logsFileNames, "mizu.mizu-api-server.log") {
|
||||||
|
t.Errorf("api server logs not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Contains(logsFileNames, "mizu_cli.log") {
|
||||||
|
t.Errorf("cli logs not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Contains(logsFileNames, "mizu_events.log") {
|
||||||
|
t.Errorf("events logs not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ContainsPartOfValue(logsFileNames, "mizu.mizu-tapper-daemon-set") {
|
||||||
|
t.Errorf("tapper logs not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLogsPath(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logsCmdArgs := getDefaultLogsCommandArgs()
|
||||||
|
|
||||||
|
logsPath := "../logs.zip"
|
||||||
|
logsCmdArgs = append(logsCmdArgs, "-f", logsPath)
|
||||||
|
|
||||||
|
logsCmd := exec.Command(cliPath, logsCmdArgs...)
|
||||||
|
t.Logf("running command: %v", logsCmd.String())
|
||||||
|
|
||||||
|
if err := logsCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start logs command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := logsCmd.Wait(); err != nil {
|
||||||
|
t.Errorf("failed to wait logs command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
zipReader, zipError := zip.OpenReader(logsPath)
|
||||||
|
if zipError != nil {
|
||||||
|
t.Errorf("failed to get zip reader, err: %v", zipError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := zipReader.Close(); err != nil {
|
||||||
|
t.Logf("failed to close zip reader, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
var logsFileNames []string
|
||||||
|
for _, file := range zipReader.File {
|
||||||
|
logsFileNames = append(logsFileNames, file.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Contains(logsFileNames, "mizu.mizu-api-server.log") {
|
||||||
|
t.Errorf("api server logs not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Contains(logsFileNames, "mizu_cli.log") {
|
||||||
|
t.Errorf("cli logs not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Contains(logsFileNames, "mizu_events.log") {
|
||||||
|
t.Errorf("events logs not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ContainsPartOfValue(logsFileNames, "mizu.mizu-tapper-daemon-set") {
|
||||||
|
t.Errorf("tapper logs not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
55
acceptanceTests/setup.sh
Normal file
55
acceptanceTests/setup.sh
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
PREFIX=$HOME/local/bin
|
||||||
|
VERSION=v1.22.0
|
||||||
|
|
||||||
|
echo "Attempting to install minikube and assorted tools to $PREFIX"
|
||||||
|
|
||||||
|
if ! [ -x "$(command -v kubectl)" ]; then
|
||||||
|
echo "Installing kubectl version $VERSION"
|
||||||
|
curl -LO "https://storage.googleapis.com/kubernetes-release/release/$VERSION/bin/linux/amd64/kubectl"
|
||||||
|
chmod +x kubectl
|
||||||
|
mv kubectl "$PREFIX"
|
||||||
|
else
|
||||||
|
echo "kubetcl is already installed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [ -x "$(command -v minikube)" ]; then
|
||||||
|
echo "Installing minikube version $VERSION"
|
||||||
|
curl -Lo minikube https://storage.googleapis.com/minikube/releases/$VERSION/minikube-linux-amd64
|
||||||
|
chmod +x minikube
|
||||||
|
mv minikube "$PREFIX"
|
||||||
|
else
|
||||||
|
echo "minikube is already installed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Starting minikube..."
|
||||||
|
minikube start
|
||||||
|
|
||||||
|
echo "Creating mizu tests namespaces"
|
||||||
|
kubectl create namespace mizu-tests
|
||||||
|
kubectl create namespace mizu-tests2
|
||||||
|
|
||||||
|
echo "Creating httpbin deployments"
|
||||||
|
kubectl create deployment httpbin --image=kennethreitz/httpbin -n mizu-tests
|
||||||
|
kubectl create deployment httpbin2 --image=kennethreitz/httpbin -n mizu-tests
|
||||||
|
|
||||||
|
kubectl create deployment httpbin --image=kennethreitz/httpbin -n mizu-tests2
|
||||||
|
|
||||||
|
echo "Creating httpbin services"
|
||||||
|
kubectl expose deployment httpbin --type=NodePort --port=80 -n mizu-tests
|
||||||
|
kubectl expose deployment httpbin2 --type=NodePort --port=80 -n mizu-tests
|
||||||
|
|
||||||
|
kubectl expose deployment httpbin --type=NodePort --port=80 -n mizu-tests2
|
||||||
|
|
||||||
|
echo "Starting proxy"
|
||||||
|
kubectl proxy --port=8080 &
|
||||||
|
|
||||||
|
echo "Setting minikube docker env"
|
||||||
|
eval $(minikube docker-env)
|
||||||
|
|
||||||
|
echo "Build agent image"
|
||||||
|
make build-docker-ci
|
||||||
|
|
||||||
|
echo "Build cli"
|
||||||
|
make build-cli-ci
|
||||||
865
acceptanceTests/tap_test.go
Normal file
865
acceptanceTests/tap_test.go
Normal file
@@ -0,0 +1,865 @@
|
|||||||
|
package acceptanceTests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTap(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []int{50}
|
||||||
|
|
||||||
|
for _, entriesCount := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%d", entriesCount), func(t *testing.T) {
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyUrl := getProxyUrl(defaultNamespaceName, defaultServiceName)
|
||||||
|
for i := 0; i < entriesCount; i++ {
|
||||||
|
if _, requestErr := executeHttpGetRequest(fmt.Sprintf("%v/get", proxyUrl)); requestErr != nil {
|
||||||
|
t.Errorf("failed to send proxy request, err: %v", requestErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entriesCheckFunc := func() error {
|
||||||
|
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
|
|
||||||
|
entriesUrl := fmt.Sprintf("%v/api/entries?limit=%v&operator=lt×tamp=%v", apiServerUrl, entriesCount, timestamp)
|
||||||
|
requestResult, requestErr := executeHttpGetRequest(entriesUrl)
|
||||||
|
if requestErr != nil {
|
||||||
|
return fmt.Errorf("failed to get entries, err: %v", requestErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
entries := requestResult.([]interface{})
|
||||||
|
if len(entries) == 0 {
|
||||||
|
return fmt.Errorf("unexpected entries result - Expected more than 0 entries")
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := entries[0].(map[string]interface{})
|
||||||
|
|
||||||
|
entryUrl := fmt.Sprintf("%v/api/entries/%v", apiServerUrl, entry["id"])
|
||||||
|
requestResult, requestErr = executeHttpGetRequest(entryUrl)
|
||||||
|
if requestErr != nil {
|
||||||
|
return fmt.Errorf("failed to get entry, err: %v", requestErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if requestResult == nil {
|
||||||
|
return fmt.Errorf("unexpected nil entry result")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := retriesExecute(shortRetriesCount, entriesCheckFunc); err != nil {
|
||||||
|
t.Errorf("%v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTapGuiPort(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []uint16{8898}
|
||||||
|
|
||||||
|
for _, guiPort := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%d", guiPort), func(t *testing.T) {
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
|
tapCmdArgs = append(tapCmdArgs, "-p", fmt.Sprintf("%d", guiPort))
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(guiPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTapAllNamespaces(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedPods := []struct{
|
||||||
|
Name string
|
||||||
|
Namespace string
|
||||||
|
}{
|
||||||
|
{Name: "httpbin", Namespace: "mizu-tests"},
|
||||||
|
{Name: "httpbin", Namespace: "mizu-tests2"},
|
||||||
|
}
|
||||||
|
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, "-A")
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
podsUrl := fmt.Sprintf("%v/api/tapStatus", apiServerUrl)
|
||||||
|
requestResult, requestErr := executeHttpGetRequest(podsUrl)
|
||||||
|
if requestErr != nil {
|
||||||
|
t.Errorf("failed to get tap status, err: %v", requestErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pods, err := getPods(requestResult)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to get pods, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, expectedPod := range expectedPods {
|
||||||
|
podFound := false
|
||||||
|
|
||||||
|
for _, pod := range pods {
|
||||||
|
podNamespace := pod["namespace"].(string)
|
||||||
|
podName := pod["name"].(string)
|
||||||
|
|
||||||
|
if expectedPod.Namespace == podNamespace && strings.Contains(podName, expectedPod.Name) {
|
||||||
|
podFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !podFound {
|
||||||
|
t.Errorf("unexpected result - expected pod not found, pod namespace: %v, pod name: %v", expectedPod.Namespace, expectedPod.Name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTapMultipleNamespaces(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedPods := []struct{
|
||||||
|
Name string
|
||||||
|
Namespace string
|
||||||
|
}{
|
||||||
|
{Name: "httpbin", Namespace: "mizu-tests"},
|
||||||
|
{Name: "httpbin2", Namespace: "mizu-tests"},
|
||||||
|
{Name: "httpbin", Namespace: "mizu-tests2"},
|
||||||
|
}
|
||||||
|
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
var namespacesCmd []string
|
||||||
|
for _, expectedPod := range expectedPods {
|
||||||
|
namespacesCmd = append(namespacesCmd, "-n", expectedPod.Namespace)
|
||||||
|
}
|
||||||
|
tapCmdArgs = append(tapCmdArgs, namespacesCmd...)
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
podsUrl := fmt.Sprintf("%v/api/tapStatus", apiServerUrl)
|
||||||
|
requestResult, requestErr := executeHttpGetRequest(podsUrl)
|
||||||
|
if requestErr != nil {
|
||||||
|
t.Errorf("failed to get tap status, err: %v", requestErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pods, err := getPods(requestResult)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to get pods, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(expectedPods) != len(pods) {
|
||||||
|
t.Errorf("unexpected result - expected pods length: %v, actual pods length: %v", len(expectedPods), len(pods))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, expectedPod := range expectedPods {
|
||||||
|
podFound := false
|
||||||
|
|
||||||
|
for _, pod := range pods {
|
||||||
|
podNamespace := pod["namespace"].(string)
|
||||||
|
podName := pod["name"].(string)
|
||||||
|
|
||||||
|
if expectedPod.Namespace == podNamespace && strings.Contains(podName, expectedPod.Name) {
|
||||||
|
podFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !podFound {
|
||||||
|
t.Errorf("unexpected result - expected pod not found, pod namespace: %v, pod name: %v", expectedPod.Namespace, expectedPod.Name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTapRegex(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
regexPodName := "httpbin2"
|
||||||
|
expectedPods := []struct{
|
||||||
|
Name string
|
||||||
|
Namespace string
|
||||||
|
}{
|
||||||
|
{Name: regexPodName, Namespace: "mizu-tests"},
|
||||||
|
}
|
||||||
|
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgsWithRegex(regexPodName)
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
podsUrl := fmt.Sprintf("%v/api/tapStatus", apiServerUrl)
|
||||||
|
requestResult, requestErr := executeHttpGetRequest(podsUrl)
|
||||||
|
if requestErr != nil {
|
||||||
|
t.Errorf("failed to get tap status, err: %v", requestErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pods, err := getPods(requestResult)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to get pods, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(expectedPods) != len(pods) {
|
||||||
|
t.Errorf("unexpected result - expected pods length: %v, actual pods length: %v", len(expectedPods), len(pods))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, expectedPod := range expectedPods {
|
||||||
|
podFound := false
|
||||||
|
|
||||||
|
for _, pod := range pods {
|
||||||
|
podNamespace := pod["namespace"].(string)
|
||||||
|
podName := pod["name"].(string)
|
||||||
|
|
||||||
|
if expectedPod.Namespace == podNamespace && strings.Contains(podName, expectedPod.Name) {
|
||||||
|
podFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !podFound {
|
||||||
|
t.Errorf("unexpected result - expected pod not found, pod namespace: %v, pod name: %v", expectedPod.Namespace, expectedPod.Name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTapDryRun(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
|
tapCmdArgs = append(tapCmdArgs, "--dry-run")
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resultChannel := make(chan string, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := tapCmd.Wait(); err != nil {
|
||||||
|
resultChannel <- "fail"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resultChannel <- "success"
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
time.Sleep(shortRetriesCount * time.Second)
|
||||||
|
resultChannel <- "fail"
|
||||||
|
}()
|
||||||
|
|
||||||
|
testResult := <- resultChannel
|
||||||
|
if testResult != "success" {
|
||||||
|
t.Errorf("unexpected result - dry run cmd not done")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTapRedact(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyUrl := getProxyUrl(defaultNamespaceName, defaultServiceName)
|
||||||
|
requestBody := map[string]string{"User": "Mizu"}
|
||||||
|
for i := 0; i < defaultEntriesCount; i++ {
|
||||||
|
if _, requestErr := executeHttpPostRequest(fmt.Sprintf("%v/post", proxyUrl), requestBody); requestErr != nil {
|
||||||
|
t.Errorf("failed to send proxy request, err: %v", requestErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
redactCheckFunc := func() error {
|
||||||
|
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
|
|
||||||
|
entriesUrl := fmt.Sprintf("%v/api/entries?limit=%v&operator=lt×tamp=%v", apiServerUrl, defaultEntriesCount, timestamp)
|
||||||
|
requestResult, requestErr := executeHttpGetRequest(entriesUrl)
|
||||||
|
if requestErr != nil {
|
||||||
|
return fmt.Errorf("failed to get entries, err: %v", requestErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
entries := requestResult.([]interface{})
|
||||||
|
if len(entries) == 0 {
|
||||||
|
return fmt.Errorf("unexpected entries result - Expected more than 0 entries")
|
||||||
|
}
|
||||||
|
|
||||||
|
firstEntry := entries[0].(map[string]interface{})
|
||||||
|
|
||||||
|
entryUrl := fmt.Sprintf("%v/api/entries/%v", apiServerUrl, firstEntry["id"])
|
||||||
|
requestResult, requestErr = executeHttpGetRequest(entryUrl)
|
||||||
|
if requestErr != nil {
|
||||||
|
return fmt.Errorf("failed to get entry, err: %v", requestErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := requestResult.(map[string]interface{})["data"].(map[string]interface{})
|
||||||
|
entryJson := data["entry"].(string)
|
||||||
|
|
||||||
|
var entry map[string]interface{}
|
||||||
|
if parseErr := json.Unmarshal([]byte(entryJson), &entry); parseErr != nil {
|
||||||
|
return fmt.Errorf("failed to parse entry, err: %v", parseErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
entryRequest := entry["request"].(map[string]interface{})
|
||||||
|
entryPayload := entryRequest["payload"].(map[string]interface{})
|
||||||
|
entryDetails := entryPayload["details"].(map[string]interface{})
|
||||||
|
|
||||||
|
headers := entryDetails["headers"].([]interface{})
|
||||||
|
for _, headerInterface := range headers {
|
||||||
|
header := headerInterface.(map[string]interface{})
|
||||||
|
if header["name"].(string) != "User-Agent" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
userAgent := header["value"].(string)
|
||||||
|
if userAgent != "[REDACTED]" {
|
||||||
|
return fmt.Errorf("unexpected result - user agent is not redacted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
postData := entryDetails["postData"].(map[string]interface{})
|
||||||
|
textDataStr := postData["text"].(string)
|
||||||
|
|
||||||
|
var textData map[string]string
|
||||||
|
if parseErr := json.Unmarshal([]byte(textDataStr), &textData); parseErr != nil {
|
||||||
|
return fmt.Errorf("failed to parse text data, err: %v", parseErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if textData["User"] != "[REDACTED]" {
|
||||||
|
return fmt.Errorf("unexpected result - user in body is not redacted")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := retriesExecute(shortRetriesCount, redactCheckFunc); err != nil {
|
||||||
|
t.Errorf("%v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTapNoRedact(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
|
tapCmdArgs = append(tapCmdArgs, "--no-redact")
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyUrl := getProxyUrl(defaultNamespaceName, defaultServiceName)
|
||||||
|
requestBody := map[string]string{"User": "Mizu"}
|
||||||
|
for i := 0; i < defaultEntriesCount; i++ {
|
||||||
|
if _, requestErr := executeHttpPostRequest(fmt.Sprintf("%v/post", proxyUrl), requestBody); requestErr != nil {
|
||||||
|
t.Errorf("failed to send proxy request, err: %v", requestErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
redactCheckFunc := func() error {
|
||||||
|
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
|
|
||||||
|
entriesUrl := fmt.Sprintf("%v/api/entries?limit=%v&operator=lt×tamp=%v", apiServerUrl, defaultEntriesCount, timestamp)
|
||||||
|
requestResult, requestErr := executeHttpGetRequest(entriesUrl)
|
||||||
|
if requestErr != nil {
|
||||||
|
return fmt.Errorf("failed to get entries, err: %v", requestErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
entries := requestResult.([]interface{})
|
||||||
|
if len(entries) == 0 {
|
||||||
|
return fmt.Errorf("unexpected entries result - Expected more than 0 entries")
|
||||||
|
}
|
||||||
|
|
||||||
|
firstEntry := entries[0].(map[string]interface{})
|
||||||
|
|
||||||
|
entryUrl := fmt.Sprintf("%v/api/entries/%v", apiServerUrl, firstEntry["id"])
|
||||||
|
requestResult, requestErr = executeHttpGetRequest(entryUrl)
|
||||||
|
if requestErr != nil {
|
||||||
|
return fmt.Errorf("failed to get entry, err: %v", requestErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := requestResult.(map[string]interface{})["data"].(map[string]interface{})
|
||||||
|
entryJson := data["entry"].(string)
|
||||||
|
|
||||||
|
var entry map[string]interface{}
|
||||||
|
if parseErr := json.Unmarshal([]byte(entryJson), &entry); parseErr != nil {
|
||||||
|
return fmt.Errorf("failed to parse entry, err: %v", parseErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
entryRequest := entry["request"].(map[string]interface{})
|
||||||
|
entryPayload := entryRequest["payload"].(map[string]interface{})
|
||||||
|
entryDetails := entryPayload["details"].(map[string]interface{})
|
||||||
|
|
||||||
|
headers := entryDetails["headers"].([]interface{})
|
||||||
|
for _, headerInterface := range headers {
|
||||||
|
header := headerInterface.(map[string]interface{})
|
||||||
|
if header["name"].(string) != "User-Agent" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
userAgent := header["value"].(string)
|
||||||
|
if userAgent == "[REDACTED]" {
|
||||||
|
return fmt.Errorf("unexpected result - user agent is redacted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
postData := entryDetails["postData"].(map[string]interface{})
|
||||||
|
textDataStr := postData["text"].(string)
|
||||||
|
|
||||||
|
var textData map[string]string
|
||||||
|
if parseErr := json.Unmarshal([]byte(textDataStr), &textData); parseErr != nil {
|
||||||
|
return fmt.Errorf("failed to parse text data, err: %v", parseErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if textData["User"] == "[REDACTED]" {
|
||||||
|
return fmt.Errorf("unexpected result - user in body is redacted")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := retriesExecute(shortRetriesCount, redactCheckFunc); err != nil {
|
||||||
|
t.Errorf("%v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTapRegexMasking(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
|
tapCmdArgs = append(tapCmdArgs, "-r", "Mizu")
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Logf("failed to cleanup tap command, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyUrl := getProxyUrl(defaultNamespaceName, defaultServiceName)
|
||||||
|
for i := 0; i < defaultEntriesCount; i++ {
|
||||||
|
response, requestErr := http.Post(fmt.Sprintf("%v/post", proxyUrl), "text/plain", bytes.NewBufferString("Mizu"))
|
||||||
|
if _, requestErr = executeHttpRequest(response, requestErr); requestErr != nil {
|
||||||
|
t.Errorf("failed to send proxy request, err: %v", requestErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
redactCheckFunc := func() error {
|
||||||
|
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
|
|
||||||
|
entriesUrl := fmt.Sprintf("%v/api/entries?limit=%v&operator=lt×tamp=%v", apiServerUrl, defaultEntriesCount, timestamp)
|
||||||
|
requestResult, requestErr := executeHttpGetRequest(entriesUrl)
|
||||||
|
if requestErr != nil {
|
||||||
|
return fmt.Errorf("failed to get entries, err: %v", requestErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
entries := requestResult.([]interface{})
|
||||||
|
if len(entries) == 0 {
|
||||||
|
return fmt.Errorf("unexpected entries result - Expected more than 0 entries")
|
||||||
|
}
|
||||||
|
|
||||||
|
firstEntry := entries[0].(map[string]interface{})
|
||||||
|
|
||||||
|
entryUrl := fmt.Sprintf("%v/api/entries/%v", apiServerUrl, firstEntry["id"])
|
||||||
|
requestResult, requestErr = executeHttpGetRequest(entryUrl)
|
||||||
|
if requestErr != nil {
|
||||||
|
return fmt.Errorf("failed to get entry, err: %v", requestErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := requestResult.(map[string]interface{})["data"].(map[string]interface{})
|
||||||
|
entryJson := data["entry"].(string)
|
||||||
|
|
||||||
|
var entry map[string]interface{}
|
||||||
|
if parseErr := json.Unmarshal([]byte(entryJson), &entry); parseErr != nil {
|
||||||
|
return fmt.Errorf("failed to parse entry, err: %v", parseErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
entryRequest := entry["request"].(map[string]interface{})
|
||||||
|
entryPayload := entryRequest["payload"].(map[string]interface{})
|
||||||
|
entryDetails := entryPayload["details"].(map[string]interface{})
|
||||||
|
|
||||||
|
postData := entryDetails["postData"].(map[string]interface{})
|
||||||
|
textData := postData["text"].(string)
|
||||||
|
|
||||||
|
if textData != "[REDACTED]" {
|
||||||
|
return fmt.Errorf("unexpected result - body is not redacted")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := retriesExecute(shortRetriesCount, redactCheckFunc); err != nil {
|
||||||
|
t.Errorf("%v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTapDumpLogs(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("ignored acceptance test")
|
||||||
|
}
|
||||||
|
|
||||||
|
cliPath, cliPathErr := getCliPath()
|
||||||
|
if cliPathErr != nil {
|
||||||
|
t.Errorf("failed to get cli path, err: %v", cliPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tapCmdArgs := getDefaultTapCommandArgs()
|
||||||
|
|
||||||
|
tapNamespace := getDefaultTapNamespace()
|
||||||
|
tapCmdArgs = append(tapCmdArgs, tapNamespace...)
|
||||||
|
|
||||||
|
tapCmdArgs = append(tapCmdArgs, "--set", "dump-logs=true")
|
||||||
|
|
||||||
|
tapCmd := exec.Command(cliPath, tapCmdArgs...)
|
||||||
|
t.Logf("running command: %v", tapCmd.String())
|
||||||
|
|
||||||
|
if err := tapCmd.Start(); err != nil {
|
||||||
|
t.Errorf("failed to start tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiServerUrl := getApiServerUrl(defaultApiServerPort)
|
||||||
|
|
||||||
|
if err := waitTapPodsReady(apiServerUrl); err != nil {
|
||||||
|
t.Errorf("failed to start tap pods on time, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cleanupCommand(tapCmd); err != nil {
|
||||||
|
t.Errorf("failed to cleanup tap command, err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mizuFolderPath, mizuPathErr := getMizuFolderPath()
|
||||||
|
if mizuPathErr != nil {
|
||||||
|
t.Errorf("failed to get mizu folder path, err: %v", mizuPathErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
files, readErr := ioutil.ReadDir(mizuFolderPath)
|
||||||
|
if readErr != nil {
|
||||||
|
t.Errorf("failed to read mizu folder files, err: %v", readErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var dumpsLogsPath string
|
||||||
|
for _, file := range files {
|
||||||
|
fileName := file.Name()
|
||||||
|
if strings.Contains(fileName, "mizu_logs") {
|
||||||
|
dumpsLogsPath = path.Join(mizuFolderPath, fileName)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dumpsLogsPath == "" {
|
||||||
|
t.Errorf("dump logs file not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
zipReader, zipError := zip.OpenReader(dumpsLogsPath)
|
||||||
|
if zipError != nil {
|
||||||
|
t.Errorf("failed to get zip reader, err: %v", zipError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := zipReader.Close(); err != nil {
|
||||||
|
t.Logf("failed to close zip reader, err: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
var logsFileNames []string
|
||||||
|
for _, file := range zipReader.File {
|
||||||
|
logsFileNames = append(logsFileNames, file.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Contains(logsFileNames, "mizu.mizu-api-server.log") {
|
||||||
|
t.Errorf("api server logs not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Contains(logsFileNames, "mizu_cli.log") {
|
||||||
|
t.Errorf("cli logs not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Contains(logsFileNames, "mizu_events.log") {
|
||||||
|
t.Errorf("events logs not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ContainsPartOfValue(logsFileNames, "mizu.mizu-tapper-daemon-set") {
|
||||||
|
t.Errorf("tapper logs not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
242
acceptanceTests/testsUtils.go
Normal file
242
acceptanceTests/testsUtils.go
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
package acceptanceTests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
longRetriesCount = 100
|
||||||
|
shortRetriesCount = 10
|
||||||
|
defaultApiServerPort = 8899
|
||||||
|
defaultNamespaceName = "mizu-tests"
|
||||||
|
defaultServiceName = "httpbin"
|
||||||
|
defaultEntriesCount = 50
|
||||||
|
)
|
||||||
|
|
||||||
|
func getCliPath() (string, error) {
|
||||||
|
dir, filePathErr := os.Getwd()
|
||||||
|
if filePathErr != nil {
|
||||||
|
return "", filePathErr
|
||||||
|
}
|
||||||
|
|
||||||
|
cliPath := path.Join(dir, "../cli/bin/mizu_ci")
|
||||||
|
return cliPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMizuFolderPath() (string, error) {
|
||||||
|
home, homeDirErr := os.UserHomeDir()
|
||||||
|
if homeDirErr != nil {
|
||||||
|
return "", homeDirErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.Join(home, ".mizu"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConfigPath() (string, error) {
|
||||||
|
mizuFolderPath, mizuPathError := getMizuFolderPath()
|
||||||
|
if mizuPathError != nil {
|
||||||
|
return "", mizuPathError
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.Join(mizuFolderPath, "config.yaml"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProxyUrl(namespace string, service string) string {
|
||||||
|
return fmt.Sprintf("http://localhost:8080/api/v1/namespaces/%v/services/%v/proxy", namespace, service)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getApiServerUrl(port uint16) string {
|
||||||
|
return fmt.Sprintf("http://localhost:%v/mizu", port)
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
return []string{setFlag, telemetry, setFlag, agentImage, setFlag, imagePullPolicy}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultTapCommandArgs() []string {
|
||||||
|
tapCommand := "tap"
|
||||||
|
defaultCmdArgs := getDefaultCommandArgs()
|
||||||
|
|
||||||
|
return append([]string{tapCommand}, defaultCmdArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultTapCommandArgsWithRegex(regex string) []string {
|
||||||
|
tapCommand := "tap"
|
||||||
|
defaultCmdArgs := getDefaultCommandArgs()
|
||||||
|
|
||||||
|
return append([]string{tapCommand, regex}, defaultCmdArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultLogsCommandArgs() []string {
|
||||||
|
logsCommand := "logs"
|
||||||
|
defaultCmdArgs := getDefaultCommandArgs()
|
||||||
|
|
||||||
|
return append([]string{logsCommand}, defaultCmdArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultTapNamespace() []string {
|
||||||
|
return []string{"-n", "mizu-tests"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDefaultConfigCommandArgs() []string {
|
||||||
|
configCommand := "config"
|
||||||
|
defaultCmdArgs := getDefaultCommandArgs()
|
||||||
|
|
||||||
|
return append([]string{configCommand}, defaultCmdArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func retriesExecute(retriesCount int, executeFunc func() error) error {
|
||||||
|
var lastError interface{}
|
||||||
|
|
||||||
|
for i := 0; i < retriesCount; i++ {
|
||||||
|
if err := tryExecuteFunc(executeFunc); err != nil {
|
||||||
|
lastError = err
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("reached max retries count, retries count: %v, last err: %v", retriesCount, lastError)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryExecuteFunc(executeFunc func() error) (err interface{}) {
|
||||||
|
defer func() {
|
||||||
|
if panicErr := recover(); panicErr != nil {
|
||||||
|
err = panicErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return executeFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitTapPodsReady(apiServerUrl string) error {
|
||||||
|
resolvingUrl := fmt.Sprintf("%v/status/tappersCount", apiServerUrl)
|
||||||
|
tapPodsReadyFunc := func() error {
|
||||||
|
requestResult, requestErr := executeHttpGetRequest(resolvingUrl)
|
||||||
|
if requestErr != nil {
|
||||||
|
return requestErr
|
||||||
|
}
|
||||||
|
|
||||||
|
tappersCount := requestResult.(float64)
|
||||||
|
if tappersCount == 0 {
|
||||||
|
return fmt.Errorf("no tappers running")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return retriesExecute(longRetriesCount, tapPodsReadyFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsonBytesToInterface(jsonBytes []byte) (interface{}, error) {
|
||||||
|
var result interface{}
|
||||||
|
if parseErr := json.Unmarshal(jsonBytes, &result); parseErr != nil {
|
||||||
|
return nil, parseErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeHttpRequest(response *http.Response, requestErr error) (interface{}, error) {
|
||||||
|
if requestErr != nil {
|
||||||
|
return nil, requestErr
|
||||||
|
} else if response.StatusCode != 200 {
|
||||||
|
return nil, fmt.Errorf("invalid status code %v", response.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { response.Body.Close() }()
|
||||||
|
|
||||||
|
data, readErr := ioutil.ReadAll(response.Body)
|
||||||
|
if readErr != nil {
|
||||||
|
return nil, readErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonBytesToInterface(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeHttpGetRequest(url string) (interface{}, error) {
|
||||||
|
response, requestErr := http.Get(url)
|
||||||
|
return executeHttpRequest(response, requestErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeHttpPostRequest(url string, body interface{}) (interface{}, error) {
|
||||||
|
requestBody, jsonErr := json.Marshal(body)
|
||||||
|
if jsonErr != nil {
|
||||||
|
return nil, jsonErr
|
||||||
|
}
|
||||||
|
|
||||||
|
response, requestErr := http.Post(url, "application/json", bytes.NewBuffer(requestBody))
|
||||||
|
return executeHttpRequest(response, requestErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanupCommand(cmd *exec.Cmd) error {
|
||||||
|
if err := cmd.Process.Signal(syscall.SIGQUIT); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cmd.Wait(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPods(tapStatusInterface interface{}) ([]map[string]interface{}, error) {
|
||||||
|
tapStatus := tapStatusInterface.(map[string]interface{})
|
||||||
|
podsInterface := tapStatus["pods"].([]interface{})
|
||||||
|
|
||||||
|
var pods []map[string]interface{}
|
||||||
|
for _, podInterface := range podsInterface {
|
||||||
|
pods = append(pods, podInterface.(map[string]interface{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return pods, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLogsPath() (string, error) {
|
||||||
|
dir, filePathErr := os.Getwd()
|
||||||
|
if filePathErr != nil {
|
||||||
|
return "", filePathErr
|
||||||
|
}
|
||||||
|
|
||||||
|
logsPath := path.Join(dir, "mizu_logs.zip")
|
||||||
|
return logsPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Contains(slice []string, containsValue string) bool {
|
||||||
|
for _, sliceValue := range slice {
|
||||||
|
if sliceValue == containsValue {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContainsPartOfValue(slice []string, containsValue string) bool {
|
||||||
|
for _, sliceValue := range slice {
|
||||||
|
if strings.Contains(sliceValue, containsValue) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
2
agent/Makefile
Normal file
2
agent/Makefile
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
test: ## Run agent tests.
|
||||||
|
@go test ./... -coverpkg=./... -race -coverprofile=coverage.out -covermode=atomic
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
# mizu agent
|
# mizu agent
|
||||||
Agent for MIZU (API server and tapper)
|
Agent for MIZU (API server and tapper)
|
||||||
Basic APIs:
|
Basic APIs:
|
||||||
* /fetch - retrieve traffic data
|
|
||||||
* /stats - retrieve statistics of collected data
|
* /stats - retrieve statistics of collected data
|
||||||
* /viewer - web ui
|
* /viewer - web ui
|
||||||
|
|
||||||
|
|||||||
10
agent/go.mod
10
agent/go.mod
@@ -3,7 +3,6 @@ module mizuserver
|
|||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/beevik/etree v1.1.0
|
|
||||||
github.com/djherbis/atime v1.0.0
|
github.com/djherbis/atime v1.0.0
|
||||||
github.com/fsnotify/fsnotify v1.4.9
|
github.com/fsnotify/fsnotify v1.4.9
|
||||||
github.com/gin-contrib/static v0.0.1
|
github.com/gin-contrib/static v0.0.1
|
||||||
@@ -13,18 +12,23 @@ require (
|
|||||||
github.com/go-playground/validator/v10 v10.5.0
|
github.com/go-playground/validator/v10 v10.5.0
|
||||||
github.com/google/martian v2.1.0+incompatible
|
github.com/google/martian v2.1.0+incompatible
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/gorilla/websocket v1.4.2
|
||||||
|
github.com/orcaman/concurrent-map v0.0.0-20210106121528-16402b402231
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/romana/rlog v0.0.0-20171115192701-f018bc92e7d7
|
github.com/romana/rlog v0.0.0-20171115192701-f018bc92e7d7
|
||||||
github.com/up9inc/mizu/shared v0.0.0
|
github.com/up9inc/mizu/shared v0.0.0
|
||||||
github.com/up9inc/mizu/tap v0.0.0
|
github.com/up9inc/mizu/tap v0.0.0
|
||||||
go.mongodb.org/mongo-driver v1.5.1
|
github.com/up9inc/mizu/tap/api v0.0.0
|
||||||
|
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0
|
||||||
|
go.mongodb.org/mongo-driver v1.7.1
|
||||||
gorm.io/driver/sqlite v1.1.4
|
gorm.io/driver/sqlite v1.1.4
|
||||||
gorm.io/gorm v1.21.8
|
gorm.io/gorm v1.21.8
|
||||||
k8s.io/api v0.21.0
|
k8s.io/api v0.21.0
|
||||||
k8s.io/apimachinery v0.21.0
|
k8s.io/apimachinery v0.21.0
|
||||||
k8s.io/client-go v0.21.0
|
k8s.io/client-go v0.21.0
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/up9inc/mizu/shared v0.0.0 => ../shared
|
replace github.com/up9inc/mizu/shared v0.0.0 => ../shared
|
||||||
|
|
||||||
replace github.com/up9inc/mizu/tap v0.0.0 => ../tap
|
replace github.com/up9inc/mizu/tap v0.0.0 => ../tap
|
||||||
|
|
||||||
|
replace github.com/up9inc/mizu/tap/api v0.0.0 => ../tap/api
|
||||||
|
|||||||
24
agent/go.sum
24
agent/go.sum
@@ -42,9 +42,6 @@ github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb0
|
|||||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
|
|
||||||
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
|
||||||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
|
||||||
github.com/bradleyfalzon/tlsx v0.0.0-20170624122154-28fd0e59bac4 h1:NJOOlc6ZJjix0A1rAU+nxruZtR8KboG1848yqpIUo4M=
|
github.com/bradleyfalzon/tlsx v0.0.0-20170624122154-28fd0e59bac4 h1:NJOOlc6ZJjix0A1rAU+nxruZtR8KboG1848yqpIUo4M=
|
||||||
github.com/bradleyfalzon/tlsx v0.0.0-20170624122154-28fd0e59bac4/go.mod h1:DQPxZS994Ld1Y8uwnJT+dRL04XPD0cElP/pHH/zEBHM=
|
github.com/bradleyfalzon/tlsx v0.0.0-20170624122154-28fd0e59bac4/go.mod h1:DQPxZS994Ld1Y8uwnJT+dRL04XPD0cElP/pHH/zEBHM=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
@@ -101,7 +98,6 @@ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO
|
|||||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||||
github.com/go-playground/validator/v10 v10.5.0 h1:X9rflw/KmpACwT8zdrm1upefpvdy6ur8d1kWyq6sg3E=
|
github.com/go-playground/validator/v10 v10.5.0 h1:X9rflw/KmpACwT8zdrm1upefpvdy6ur8d1kWyq6sg3E=
|
||||||
github.com/go-playground/validator/v10 v10.5.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
|
github.com/go-playground/validator/v10 v10.5.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
|
||||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
|
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
|
||||||
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
|
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
|
||||||
@@ -194,8 +190,6 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr
|
|||||||
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
|
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
|
||||||
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
|
||||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
@@ -287,11 +281,13 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY
|
|||||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||||
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
|
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
|
||||||
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
||||||
|
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/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
go.mongodb.org/mongo-driver v1.5.1 h1:9nOVLGDfOaZ9R0tBumx/BcuqkbFpyTCU2r/Po7A2azI=
|
go.mongodb.org/mongo-driver v1.7.1 h1:jwqTeEM3x6L9xDXrCxN0Hbg7vdGfPBOTIkr0+/LYZDA=
|
||||||
go.mongodb.org/mongo-driver v1.5.1/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw=
|
go.mongodb.org/mongo-driver v1.7.1/go.mod h1:Q4oFMbo1+MSNqICAdYMlC/zSTrwCogR4R8NzkI+yfU8=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
@@ -360,9 +356,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7 h1:OgUuv8lsRpBibGNbSizVwKWlysjaNzmC9gYMhPVfqFM=
|
||||||
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758 h1:aEpZnXcAmXkd6AvLb2OPt+EN1Zu/8Ne3pCqPjja5PXY=
|
|
||||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@@ -408,9 +403,8 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073 h1:8qxJSnu+7dRq6upnbntrmriWByIakBuct5OM/MdQC1M=
|
||||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe h1:WdX7u8s3yOigWAhHEaDl8r9G+4XwFQEQFtBMYyN+kXQ=
|
|
||||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
|
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
|
||||||
@@ -421,9 +415,8 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
|
|||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
@@ -538,8 +531,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.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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
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-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 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM=
|
||||||
gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
|
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=
|
gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||||
|
|||||||
238
agent/main.go
238
agent/main.go
@@ -4,47 +4,67 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"mizuserver/pkg/api"
|
||||||
|
"mizuserver/pkg/controllers"
|
||||||
|
"mizuserver/pkg/models"
|
||||||
|
"mizuserver/pkg/routes"
|
||||||
|
"mizuserver/pkg/utils"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"plugin"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-contrib/static"
|
"github.com/gin-contrib/static"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/romana/rlog"
|
"github.com/romana/rlog"
|
||||||
"github.com/up9inc/mizu/shared"
|
"github.com/up9inc/mizu/shared"
|
||||||
"github.com/up9inc/mizu/tap"
|
"github.com/up9inc/mizu/tap"
|
||||||
"mizuserver/pkg/api"
|
tapApi "github.com/up9inc/mizu/tap/api"
|
||||||
"mizuserver/pkg/models"
|
|
||||||
"mizuserver/pkg/routes"
|
|
||||||
"mizuserver/pkg/sensitiveDataFiltering"
|
|
||||||
"mizuserver/pkg/utils"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var shouldTap = flag.Bool("tap", false, "Run in tapper mode without API")
|
var tapperMode = flag.Bool("tap", false, "Run in tapper mode without API")
|
||||||
var apiServer = flag.Bool("api-server", false, "Run in API server mode with API")
|
var apiServerMode = flag.Bool("api-server", false, "Run in API server mode with API")
|
||||||
var standalone = flag.Bool("standalone", false, "Run in standalone tapper and API mode")
|
var standaloneMode = flag.Bool("standalone", false, "Run in standalone tapper and API mode")
|
||||||
var apiServerAddress = flag.String("api-server-address", "", "Address of mizu API server")
|
var apiServerAddress = flag.String("api-server-address", "", "Address of mizu API server")
|
||||||
|
var namespace = flag.String("namespace", "", "Resolve IPs if they belong to resources in this namespace (default is all)")
|
||||||
|
var harsReaderMode = flag.Bool("hars-read", false, "Run in hars-read mode")
|
||||||
|
var harsDir = flag.String("hars-dir", "", "Directory to read hars from")
|
||||||
|
|
||||||
|
var extensions []*tapApi.Extension // global
|
||||||
|
var extensionsMap map[string]*tapApi.Extension // global
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
loadExtensions()
|
||||||
hostMode := os.Getenv(shared.HostModeEnvVar) == "1"
|
hostMode := os.Getenv(shared.HostModeEnvVar) == "1"
|
||||||
tapOpts := &tap.TapOpts{HostMode: hostMode}
|
tapOpts := &tap.TapOpts{HostMode: hostMode}
|
||||||
|
|
||||||
if !*shouldTap && !*apiServer && !*standalone {
|
if !*tapperMode && !*apiServerMode && !*standaloneMode && !*harsReaderMode {
|
||||||
panic("One of the flags --tap, --api or --standalone must be provided")
|
panic("One of the flags --tap, --api or --standalone or --hars-read must be provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
if *standalone {
|
filteringOptions := getTrafficFilteringOptions()
|
||||||
harOutputChannel, outboundLinkOutputChannel := tap.StartPassiveTapper(tapOpts)
|
|
||||||
filteredHarChannel := make(chan *tap.OutputChannelItem)
|
|
||||||
|
|
||||||
go filterHarItems(harOutputChannel, filteredHarChannel, getTrafficFilteringOptions())
|
if *standaloneMode {
|
||||||
go api.StartReadingEntries(filteredHarChannel, nil)
|
api.StartResolving(*namespace)
|
||||||
go api.StartReadingOutbound(outboundLinkOutputChannel)
|
|
||||||
|
outputItemsChannel := make(chan *tapApi.OutputChannelItem)
|
||||||
|
filteredOutputItemsChannel := make(chan *tapApi.OutputChannelItem)
|
||||||
|
tap.StartPassiveTapper(tapOpts, outputItemsChannel, extensions, filteringOptions)
|
||||||
|
|
||||||
|
go filterItems(outputItemsChannel, filteredOutputItemsChannel, filteringOptions)
|
||||||
|
go api.StartReadingEntries(filteredOutputItemsChannel, nil, extensionsMap)
|
||||||
|
|
||||||
hostApi(nil)
|
hostApi(nil)
|
||||||
} else if *shouldTap {
|
} else if *tapperMode {
|
||||||
|
rlog.Infof("Starting tapper, websocket address: %s", *apiServerAddress)
|
||||||
if *apiServerAddress == "" {
|
if *apiServerAddress == "" {
|
||||||
panic("API server address must be provided with --api-server-address when using --tap")
|
panic("API server address must be provided with --api-server-address when using --tap")
|
||||||
}
|
}
|
||||||
@@ -55,23 +75,32 @@ func main() {
|
|||||||
rlog.Infof("Filtering for the following authorities: %v", tap.GetFilterIPs())
|
rlog.Infof("Filtering for the following authorities: %v", tap.GetFilterIPs())
|
||||||
}
|
}
|
||||||
|
|
||||||
harOutputChannel, outboundLinkOutputChannel := tap.StartPassiveTapper(tapOpts)
|
filteredOutputItemsChannel := make(chan *tapApi.OutputChannelItem)
|
||||||
|
tap.StartPassiveTapper(tapOpts, filteredOutputItemsChannel, extensions, filteringOptions)
|
||||||
socketConnection, err := shared.ConnectToSocketServer(*apiServerAddress, shared.DEFAULT_SOCKET_RETRIES, shared.DEFAULT_SOCKET_RETRY_SLEEP_TIME, false)
|
socketConnection, _, err := websocket.DefaultDialer.Dial(*apiServerAddress, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("Error connecting to socket server at %s %v", *apiServerAddress, err))
|
panic(fmt.Sprintf("Error connecting to socket server at %s %v", *apiServerAddress, err))
|
||||||
}
|
}
|
||||||
|
rlog.Infof("Connected successfully to websocket %s", *apiServerAddress)
|
||||||
|
|
||||||
go pipeTapChannelToSocket(socketConnection, harOutputChannel)
|
go pipeTapChannelToSocket(socketConnection, filteredOutputItemsChannel)
|
||||||
go pipeOutboundLinksChannelToSocket(socketConnection, outboundLinkOutputChannel)
|
} else if *apiServerMode {
|
||||||
} else if *apiServer {
|
api.StartResolving(*namespace)
|
||||||
socketHarOutChannel := make(chan *tap.OutputChannelItem, 1000)
|
|
||||||
filteredHarChannel := make(chan *tap.OutputChannelItem)
|
|
||||||
|
|
||||||
go filterHarItems(socketHarOutChannel, filteredHarChannel, getTrafficFilteringOptions())
|
outputItemsChannel := make(chan *tapApi.OutputChannelItem)
|
||||||
go api.StartReadingEntries(filteredHarChannel, nil)
|
filteredOutputItemsChannel := make(chan *tapApi.OutputChannelItem)
|
||||||
|
|
||||||
hostApi(socketHarOutChannel)
|
go filterItems(outputItemsChannel, filteredOutputItemsChannel, filteringOptions)
|
||||||
|
go api.StartReadingEntries(filteredOutputItemsChannel, nil, extensionsMap)
|
||||||
|
|
||||||
|
hostApi(outputItemsChannel)
|
||||||
|
} else if *harsReaderMode {
|
||||||
|
outputItemsChannel := make(chan *tapApi.OutputChannelItem, 1000)
|
||||||
|
filteredHarChannel := make(chan *tapApi.OutputChannelItem)
|
||||||
|
|
||||||
|
go filterItems(outputItemsChannel, filteredHarChannel, filteringOptions)
|
||||||
|
go api.StartReadingEntries(filteredHarChannel, harsDir, extensionsMap)
|
||||||
|
hostApi(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
signalChan := make(chan os.Signal, 1)
|
signalChan := make(chan os.Signal, 1)
|
||||||
@@ -81,7 +110,50 @@ func main() {
|
|||||||
rlog.Info("Exiting")
|
rlog.Info("Exiting")
|
||||||
}
|
}
|
||||||
|
|
||||||
func hostApi(socketHarOutputChannel chan<- *tap.OutputChannelItem) {
|
func loadExtensions() {
|
||||||
|
dir, _ := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||||
|
extensionsDir := path.Join(dir, "./extensions/")
|
||||||
|
|
||||||
|
files, err := ioutil.ReadDir(extensionsDir)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
extensions = make([]*tapApi.Extension, len(files))
|
||||||
|
extensionsMap = make(map[string]*tapApi.Extension)
|
||||||
|
for i, file := range files {
|
||||||
|
filename := file.Name()
|
||||||
|
rlog.Infof("Loading extension: %s\n", filename)
|
||||||
|
extension := &tapApi.Extension{
|
||||||
|
Path: path.Join(extensionsDir, filename),
|
||||||
|
}
|
||||||
|
plug, _ := plugin.Open(extension.Path)
|
||||||
|
extension.Plug = plug
|
||||||
|
symDissector, err := plug.Lookup("Dissector")
|
||||||
|
|
||||||
|
var dissector tapApi.Dissector
|
||||||
|
var ok bool
|
||||||
|
dissector, ok = symDissector.(tapApi.Dissector)
|
||||||
|
if err != nil || !ok {
|
||||||
|
panic(fmt.Sprintf("Failed to load the extension: %s\n", extension.Path))
|
||||||
|
}
|
||||||
|
dissector.Register(extension)
|
||||||
|
extension.Dissector = dissector
|
||||||
|
extensions[i] = extension
|
||||||
|
extensionsMap[extension.Protocol.Name] = extension
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(extensions, func(i, j int) bool {
|
||||||
|
return extensions[i].Protocol.Priority < extensions[j].Protocol.Priority
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, extension := range extensions {
|
||||||
|
log.Printf("Extension Properties: %+v\n", extension)
|
||||||
|
}
|
||||||
|
|
||||||
|
controllers.InitExtensionsMap(extensionsMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hostApi(socketHarOutputChannel chan<- *tapApi.OutputChannelItem) {
|
||||||
app := gin.Default()
|
app := gin.Default()
|
||||||
|
|
||||||
app.GET("/echo", func(c *gin.Context) {
|
app.GET("/echo", func(c *gin.Context) {
|
||||||
@@ -89,9 +161,10 @@ func hostApi(socketHarOutputChannel chan<- *tap.OutputChannelItem) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
eventHandlers := api.RoutesEventHandlers{
|
eventHandlers := api.RoutesEventHandlers{
|
||||||
SocketHarOutChannel: socketHarOutputChannel,
|
SocketOutChannel: socketHarOutputChannel,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.Use(DisableRootStaticCache())
|
||||||
app.Use(static.ServeRoot("/", "./site"))
|
app.Use(static.ServeRoot("/", "./site"))
|
||||||
app.Use(CORSMiddleware()) // This has to be called after the static middleware, does not work if its called before
|
app.Use(CORSMiddleware()) // This has to be called after the static middleware, does not work if its called before
|
||||||
|
|
||||||
@@ -104,6 +177,17 @@ func hostApi(socketHarOutputChannel chan<- *tap.OutputChannelItem) {
|
|||||||
utils.StartServer(app)
|
utils.StartServer(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DisableRootStaticCache() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
if c.Request.RequestURI == "/" {
|
||||||
|
// Disable cache only for the main static route
|
||||||
|
c.Writer.Header().Set("Cache-Control", "no-store")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func CORSMiddleware() gin.HandlerFunc {
|
func CORSMiddleware() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
@@ -120,55 +204,71 @@ func CORSMiddleware() gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseEnvVar(env string) map[string][]string {
|
||||||
|
var mapOfList map[string][]string
|
||||||
|
|
||||||
|
val, present := os.LookupEnv(env)
|
||||||
|
|
||||||
|
if !present {
|
||||||
|
return mapOfList
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.Unmarshal([]byte(val), &mapOfList)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("env var %s's value of %s is invalid! must be map[string][]string %v", env, mapOfList, err))
|
||||||
|
}
|
||||||
|
return mapOfList
|
||||||
|
}
|
||||||
|
|
||||||
func getTapTargets() []string {
|
func getTapTargets() []string {
|
||||||
nodeName := os.Getenv(shared.NodeNameEnvVar)
|
nodeName := os.Getenv(shared.NodeNameEnvVar)
|
||||||
var tappedAddressesPerNodeDict map[string][]string
|
tappedAddressesPerNodeDict := parseEnvVar(shared.TappedAddressesPerNodeDictEnvVar)
|
||||||
err := json.Unmarshal([]byte(os.Getenv(shared.TappedAddressesPerNodeDictEnvVar)), &tappedAddressesPerNodeDict)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("env var %s's value of %s is invalid! must be map[string][]string %v", shared.TappedAddressesPerNodeDictEnvVar, tappedAddressesPerNodeDict, err))
|
|
||||||
}
|
|
||||||
return tappedAddressesPerNodeDict[nodeName]
|
return tappedAddressesPerNodeDict[nodeName]
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTrafficFilteringOptions() *shared.TrafficFilteringOptions {
|
func getTrafficFilteringOptions() *tapApi.TrafficFilteringOptions {
|
||||||
filteringOptionsJson := os.Getenv(shared.MizuFilteringOptionsEnvVar)
|
filteringOptionsJson := os.Getenv(shared.MizuFilteringOptionsEnvVar)
|
||||||
if filteringOptionsJson == "" {
|
if filteringOptionsJson == "" {
|
||||||
return nil
|
return &tapApi.TrafficFilteringOptions{
|
||||||
|
HealthChecksUserAgentHeaders: []string{},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var filteringOptions shared.TrafficFilteringOptions
|
var filteringOptions tapApi.TrafficFilteringOptions
|
||||||
err := json.Unmarshal([]byte(filteringOptionsJson), &filteringOptions)
|
err := json.Unmarshal([]byte(filteringOptionsJson), &filteringOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("env var %s's value of %s is invalid! json must match the shared.TrafficFilteringOptions struct %v", shared.MizuFilteringOptionsEnvVar, filteringOptionsJson, err))
|
panic(fmt.Sprintf("env var %s's value of %s is invalid! json must match the api.TrafficFilteringOptions struct %v", shared.MizuFilteringOptionsEnvVar, filteringOptionsJson, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &filteringOptions
|
return &filteringOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
var userAgentsToFilter = []string{"kube-probe", "prometheus"}
|
func filterItems(inChannel <-chan *tapApi.OutputChannelItem, outChannel chan *tapApi.OutputChannelItem, filterOptions *tapApi.TrafficFilteringOptions) {
|
||||||
|
|
||||||
func filterHarItems(inChannel <-chan *tap.OutputChannelItem, outChannel chan *tap.OutputChannelItem, filterOptions *shared.TrafficFilteringOptions) {
|
|
||||||
for message := range inChannel {
|
for message := range inChannel {
|
||||||
if message.ConnectionInfo.IsOutgoing && api.CheckIsServiceIP(message.ConnectionInfo.ServerIP) {
|
if message.ConnectionInfo.IsOutgoing && api.CheckIsServiceIP(message.ConnectionInfo.ServerIP) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// TODO: move this to tappers https://up9.atlassian.net/browse/TRA-3441
|
// TODO: move this to tappers https://up9.atlassian.net/browse/TRA-3441
|
||||||
if filterOptions.HideHealthChecks && isHealthCheckByUserAgent(message) {
|
if isHealthCheckByUserAgent(message, filterOptions.HealthChecksUserAgentHeaders) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !filterOptions.DisableRedaction {
|
|
||||||
sensitiveDataFiltering.FilterSensitiveInfoFromHarRequest(message, filterOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
outChannel <- message
|
outChannel <- message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func isHealthCheckByUserAgent(message *tap.OutputChannelItem) bool {
|
func isHealthCheckByUserAgent(item *tapApi.OutputChannelItem, userAgentsToIgnore []string) bool {
|
||||||
for _, header := range message.HarEntry.Request.Headers {
|
if item.Protocol.Name != "http" {
|
||||||
if strings.ToLower(header.Name) == "user-agent" {
|
return false
|
||||||
for _, userAgent := range userAgentsToFilter {
|
}
|
||||||
if strings.Contains(strings.ToLower(header.Value), userAgent) {
|
|
||||||
|
request := item.Pair.Request.Payload.(map[string]interface{})
|
||||||
|
reqDetails := request["details"].(map[string]interface{})
|
||||||
|
|
||||||
|
for _, header := range reqDetails["headers"].([]interface{}) {
|
||||||
|
h := header.(map[string]interface{})
|
||||||
|
if strings.ToLower(h["name"].(string)) == "user-agent" {
|
||||||
|
for _, userAgent := range userAgentsToIgnore {
|
||||||
|
if strings.Contains(strings.ToLower(h["value"].(string)), strings.ToLower(userAgent)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,7 +278,7 @@ func isHealthCheckByUserAgent(message *tap.OutputChannelItem) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func pipeTapChannelToSocket(connection *websocket.Conn, messageDataChannel <-chan *tap.OutputChannelItem) {
|
func pipeTapChannelToSocket(connection *websocket.Conn, messageDataChannel <-chan *tapApi.OutputChannelItem) {
|
||||||
if connection == nil {
|
if connection == nil {
|
||||||
panic("Websocket connection is nil")
|
panic("Websocket connection is nil")
|
||||||
}
|
}
|
||||||
@@ -190,32 +290,16 @@ func pipeTapChannelToSocket(connection *websocket.Conn, messageDataChannel <-cha
|
|||||||
for messageData := range messageDataChannel {
|
for messageData := range messageDataChannel {
|
||||||
marshaledData, err := models.CreateWebsocketTappedEntryMessage(messageData)
|
marshaledData, err := models.CreateWebsocketTappedEntryMessage(messageData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rlog.Infof("error converting message to json %s, (%v,%+v)\n", err, err, err)
|
rlog.Errorf("error converting message to json %v, err: %s, (%v,%+v)", messageData, err, err, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: This is where the `*tapApi.OutputChannelItem` leaves the code
|
||||||
|
// and goes into the intermediate WebSocket.
|
||||||
err = connection.WriteMessage(websocket.TextMessage, marshaledData)
|
err = connection.WriteMessage(websocket.TextMessage, marshaledData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rlog.Infof("error sending message through socket server %s, (%v,%+v)\n", err, err, err)
|
rlog.Errorf("error sending message through socket server %v, err: %s, (%v,%+v)", messageData, err, err, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func pipeOutboundLinksChannelToSocket(connection *websocket.Conn, outboundLinkChannel <-chan *tap.OutboundLink) {
|
|
||||||
for outboundLink := range outboundLinkChannel {
|
|
||||||
if outboundLink.SuggestedProtocol == tap.TLSProtocol {
|
|
||||||
marshaledData, err := models.CreateWebsocketOutboundLinkMessage(outboundLink)
|
|
||||||
if err != nil {
|
|
||||||
rlog.Infof("Error converting outbound link to json %s, (%v,%+v)", err, err, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err = connection.WriteMessage(websocket.TextMessage, marshaledData)
|
|
||||||
if err != nil {
|
|
||||||
rlog.Infof("error sending outbound link message through socket server %s, (%v,%+v)", err, err, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,19 +5,21 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/google/martian/har"
|
"mizuserver/pkg/database"
|
||||||
"github.com/romana/rlog"
|
|
||||||
"github.com/up9inc/mizu/tap"
|
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
||||||
"mizuserver/pkg/holder"
|
"mizuserver/pkg/holder"
|
||||||
"net/url"
|
"mizuserver/pkg/providers"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"mizuserver/pkg/database"
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
|
||||||
|
"github.com/google/martian/har"
|
||||||
|
"github.com/romana/rlog"
|
||||||
|
tapApi "github.com/up9inc/mizu/tap/api"
|
||||||
|
|
||||||
"mizuserver/pkg/models"
|
"mizuserver/pkg/models"
|
||||||
"mizuserver/pkg/resolver"
|
"mizuserver/pkg/resolver"
|
||||||
"mizuserver/pkg/utils"
|
"mizuserver/pkg/utils"
|
||||||
@@ -25,9 +27,9 @@ import (
|
|||||||
|
|
||||||
var k8sResolver *resolver.Resolver
|
var k8sResolver *resolver.Resolver
|
||||||
|
|
||||||
func init() {
|
func StartResolving(namespace string) {
|
||||||
errOut := make(chan error, 100)
|
errOut := make(chan error, 100)
|
||||||
res, err := resolver.NewFromInCluster(errOut)
|
res, err := resolver.NewFromInCluster(errOut, namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rlog.Infof("error creating k8s resolver %s", err)
|
rlog.Infof("error creating k8s resolver %s", err)
|
||||||
return
|
return
|
||||||
@@ -47,17 +49,19 @@ func init() {
|
|||||||
holder.SetResolver(res)
|
holder.SetResolver(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartReadingEntries(harChannel <-chan *tap.OutputChannelItem, workingDir *string) {
|
func StartReadingEntries(harChannel <-chan *tapApi.OutputChannelItem, workingDir *string, extensionsMap map[string]*tapApi.Extension) {
|
||||||
if workingDir != nil && *workingDir != "" {
|
if workingDir != nil && *workingDir != "" {
|
||||||
startReadingFiles(*workingDir)
|
startReadingFiles(*workingDir)
|
||||||
} else {
|
} else {
|
||||||
startReadingChannel(harChannel)
|
startReadingChannel(harChannel, extensionsMap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func startReadingFiles(workingDir string) {
|
func startReadingFiles(workingDir string) {
|
||||||
err := os.MkdirAll(workingDir, os.ModePerm)
|
if err := os.MkdirAll(workingDir, os.ModePerm); err != nil {
|
||||||
utils.CheckErr(err)
|
rlog.Errorf("Failed to make dir: %s, err: %v", workingDir, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for true {
|
for true {
|
||||||
dir, _ := os.Open(workingDir)
|
dir, _ := os.Open(workingDir)
|
||||||
@@ -85,47 +89,41 @@ func startReadingFiles(workingDir string) {
|
|||||||
decErr := json.NewDecoder(bufio.NewReader(file)).Decode(&inputHar)
|
decErr := json.NewDecoder(bufio.NewReader(file)).Decode(&inputHar)
|
||||||
utils.CheckErr(decErr)
|
utils.CheckErr(decErr)
|
||||||
|
|
||||||
for _, entry := range inputHar.Log.Entries {
|
|
||||||
time.Sleep(time.Millisecond * 250)
|
|
||||||
connectionInfo := &tap.ConnectionInfo{
|
|
||||||
ClientIP: fileInfo.Name(),
|
|
||||||
ClientPort: "",
|
|
||||||
ServerIP: "",
|
|
||||||
ServerPort: "",
|
|
||||||
IsOutgoing: false,
|
|
||||||
}
|
|
||||||
saveHarToDb(entry, connectionInfo)
|
|
||||||
}
|
|
||||||
rmErr := os.Remove(inputFilePath)
|
rmErr := os.Remove(inputFilePath)
|
||||||
utils.CheckErr(rmErr)
|
utils.CheckErr(rmErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func startReadingChannel(outputItems <-chan *tap.OutputChannelItem) {
|
func startReadingChannel(outputItems <-chan *tapApi.OutputChannelItem, extensionsMap map[string]*tapApi.Extension) {
|
||||||
if outputItems == nil {
|
if outputItems == nil {
|
||||||
panic("Channel of captured messages is nil")
|
panic("Channel of captured messages is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
for item := range outputItems {
|
for item := range outputItems {
|
||||||
saveHarToDb(item.HarEntry, item.ConnectionInfo)
|
providers.EntryAdded()
|
||||||
|
|
||||||
|
extension := extensionsMap[item.Protocol.Name]
|
||||||
|
resolvedSource, resolvedDestionation := resolveIP(item.ConnectionInfo)
|
||||||
|
mizuEntry := extension.Dissector.Analyze(item, primitive.NewObjectID().Hex(), resolvedSource, resolvedDestionation)
|
||||||
|
baseEntry := extension.Dissector.Summarize(mizuEntry)
|
||||||
|
mizuEntry.EstimatedSizeBytes = getEstimatedEntrySizeBytes(mizuEntry)
|
||||||
|
database.CreateEntry(mizuEntry)
|
||||||
|
if extension.Protocol.Name == "http" {
|
||||||
|
var pair tapApi.RequestResponsePair
|
||||||
|
json.Unmarshal([]byte(mizuEntry.Entry), &pair)
|
||||||
|
harEntry, err := utils.NewEntry(&pair)
|
||||||
|
if err == nil {
|
||||||
|
rules, _, _ := models.RunValidationRulesState(*harEntry, mizuEntry.Service)
|
||||||
|
baseEntry.Rules = rules
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
baseEntryBytes, _ := models.CreateBaseEntryWebSocketMessage(baseEntry)
|
||||||
|
BroadcastToBrowserClients(baseEntryBytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartReadingOutbound(outboundLinkChannel <-chan *tap.OutboundLink) {
|
func resolveIP(connectionInfo *tapApi.ConnectionInfo) (resolvedSource string, resolvedDestination string) {
|
||||||
// tcpStreamFactory will block on write to channel. Empty channel to unblock.
|
|
||||||
// TODO: Make write to channel optional.
|
|
||||||
for range outboundLinkChannel {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveHarToDb(entry *har.Entry, connectionInfo *tap.ConnectionInfo) {
|
|
||||||
entryBytes, _ := json.Marshal(entry)
|
|
||||||
serviceName, urlPath := getServiceNameFromUrl(entry.Request.URL)
|
|
||||||
entryId := primitive.NewObjectID().Hex()
|
|
||||||
var (
|
|
||||||
resolvedSource string
|
|
||||||
resolvedDestination string
|
|
||||||
)
|
|
||||||
if k8sResolver != nil {
|
if k8sResolver != nil {
|
||||||
unresolvedSource := connectionInfo.ClientIP
|
unresolvedSource := connectionInfo.ClientIP
|
||||||
resolvedSource = k8sResolver.Resolve(unresolvedSource)
|
resolvedSource = k8sResolver.Resolve(unresolvedSource)
|
||||||
@@ -144,44 +142,18 @@ func saveHarToDb(entry *har.Entry, connectionInfo *tap.ConnectionInfo) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return resolvedSource, resolvedDestination
|
||||||
mizuEntry := models.MizuEntry{
|
|
||||||
EntryId: entryId,
|
|
||||||
Entry: string(entryBytes), // simple way to store it and not convert to bytes
|
|
||||||
Service: serviceName,
|
|
||||||
Url: entry.Request.URL,
|
|
||||||
Path: urlPath,
|
|
||||||
Method: entry.Request.Method,
|
|
||||||
Status: entry.Response.Status,
|
|
||||||
RequestSenderIp: connectionInfo.ClientIP,
|
|
||||||
Timestamp: entry.StartedDateTime.UnixNano() / int64(time.Millisecond),
|
|
||||||
ResolvedSource: resolvedSource,
|
|
||||||
ResolvedDestination: resolvedDestination,
|
|
||||||
IsOutgoing: connectionInfo.IsOutgoing,
|
|
||||||
}
|
|
||||||
mizuEntry.EstimatedSizeBytes = getEstimatedEntrySizeBytes(mizuEntry)
|
|
||||||
database.CreateEntry(&mizuEntry)
|
|
||||||
|
|
||||||
baseEntry := models.BaseEntryDetails{}
|
|
||||||
if err := models.GetEntry(&mizuEntry, &baseEntry); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
baseEntryBytes, _ := models.CreateBaseEntryWebSocketMessage(&baseEntry)
|
|
||||||
BroadcastToBrowserClients(baseEntryBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getServiceNameFromUrl(inputUrl string) (string, string) {
|
|
||||||
parsed, err := url.Parse(inputUrl)
|
|
||||||
utils.CheckErr(err)
|
|
||||||
return fmt.Sprintf("%s://%s", parsed.Scheme, parsed.Host), parsed.Path
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckIsServiceIP(address string) bool {
|
func CheckIsServiceIP(address string) bool {
|
||||||
|
if k8sResolver == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return k8sResolver.CheckIsServiceIP(address)
|
return k8sResolver.CheckIsServiceIP(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
// gives a rough estimate of the size this will take up in the db, good enough for maintaining db size limit accurately
|
// gives a rough estimate of the size this will take up in the db, good enough for maintaining db size limit accurately
|
||||||
func getEstimatedEntrySizeBytes(mizuEntry models.MizuEntry) int {
|
func getEstimatedEntrySizeBytes(mizuEntry *tapApi.MizuEntry) int {
|
||||||
sizeBytes := len(mizuEntry.Entry)
|
sizeBytes := len(mizuEntry.Entry)
|
||||||
sizeBytes += len(mizuEntry.EntryId)
|
sizeBytes += len(mizuEntry.EntryId)
|
||||||
sizeBytes += len(mizuEntry.Service)
|
sizeBytes += len(mizuEntry.Service)
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/romana/rlog"
|
||||||
"github.com/up9inc/mizu/shared/debounce"
|
"github.com/up9inc/mizu/shared/debounce"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -50,7 +50,7 @@ func WebSocketRoutes(app *gin.Engine, eventHandlers EventHandlers) {
|
|||||||
func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers EventHandlers, isTapper bool) {
|
func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers EventHandlers, isTapper bool) {
|
||||||
conn, err := websocketUpgrader.Upgrade(w, r, nil)
|
conn, err := websocketUpgrader.Upgrade(w, r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to set websocket upgrade: %+v", err)
|
rlog.Errorf("Failed to set websocket upgrade: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers Even
|
|||||||
for {
|
for {
|
||||||
_, msg, err := conn.ReadMessage()
|
_, msg, err := conn.ReadMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Conn err: %v\n", err)
|
rlog.Errorf("Error reading message, socket id: %d, error: %v", socketId, err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
eventHandlers.WebSocketMessage(socketId, msg)
|
eventHandlers.WebSocketMessage(socketId, msg)
|
||||||
@@ -81,7 +81,7 @@ func websocketHandler(w http.ResponseWriter, r *http.Request, eventHandlers Even
|
|||||||
func socketCleanup(socketId int, socketConnection *SocketConnection) {
|
func socketCleanup(socketId int, socketConnection *SocketConnection) {
|
||||||
err := socketConnection.connection.Close()
|
err := socketConnection.connection.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error closing socket connection for socket id %d: %v\n", socketId, err)
|
rlog.Errorf("Error closing socket connection for socket id %d: %v\n", socketId, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
websocketIdsLock.Lock()
|
websocketIdsLock.Lock()
|
||||||
@@ -92,7 +92,7 @@ func socketCleanup(socketId int, socketConnection *SocketConnection) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var db = debounce.NewDebouncer(time.Second*5, func() {
|
var db = debounce.NewDebouncer(time.Second*5, func() {
|
||||||
fmt.Println("Successfully sent to socket")
|
rlog.Error("Successfully sent to socket")
|
||||||
})
|
})
|
||||||
|
|
||||||
func SendToSocket(socketId int, message []byte) error {
|
func SendToSocket(socketId int, message []byte) error {
|
||||||
@@ -104,7 +104,7 @@ func SendToSocket(socketId int, message []byte) error {
|
|||||||
var sent = false
|
var sent = false
|
||||||
time.AfterFunc(time.Second*5, func() {
|
time.AfterFunc(time.Second*5, func() {
|
||||||
if !sent {
|
if !sent {
|
||||||
fmt.Println("Socket timed out")
|
rlog.Error("Socket timed out")
|
||||||
socketCleanup(socketId, socketObj)
|
socketCleanup(socketId, socketObj)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ package api
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/romana/rlog"
|
|
||||||
"github.com/up9inc/mizu/shared"
|
|
||||||
"github.com/up9inc/mizu/tap"
|
|
||||||
"mizuserver/pkg/models"
|
"mizuserver/pkg/models"
|
||||||
"mizuserver/pkg/providers"
|
"mizuserver/pkg/providers"
|
||||||
"mizuserver/pkg/up9"
|
"mizuserver/pkg/up9"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
tapApi "github.com/up9inc/mizu/tap/api"
|
||||||
|
|
||||||
|
"github.com/romana/rlog"
|
||||||
|
"github.com/up9inc/mizu/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
var browserClientSocketUUIDs = make([]int, 0)
|
var browserClientSocketUUIDs = make([]int, 0)
|
||||||
@@ -17,7 +19,7 @@ var socketListLock = sync.Mutex{}
|
|||||||
|
|
||||||
type RoutesEventHandlers struct {
|
type RoutesEventHandlers struct {
|
||||||
EventHandlers
|
EventHandlers
|
||||||
SocketHarOutChannel chan<- *tap.OutputChannelItem
|
SocketOutChannel chan<- *tapApi.OutputChannelItem
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -27,6 +29,7 @@ func init() {
|
|||||||
func (h *RoutesEventHandlers) WebSocketConnect(socketId int, isTapper bool) {
|
func (h *RoutesEventHandlers) WebSocketConnect(socketId int, isTapper bool) {
|
||||||
if isTapper {
|
if isTapper {
|
||||||
rlog.Infof("Websocket event - Tapper connected, socket ID: %d", socketId)
|
rlog.Infof("Websocket event - Tapper connected, socket ID: %d", socketId)
|
||||||
|
providers.TapperAdded()
|
||||||
} else {
|
} else {
|
||||||
rlog.Infof("Websocket event - Browser socket connected, socket ID: %d", socketId)
|
rlog.Infof("Websocket event - Browser socket connected, socket ID: %d", socketId)
|
||||||
socketListLock.Lock()
|
socketListLock.Lock()
|
||||||
@@ -38,6 +41,7 @@ func (h *RoutesEventHandlers) WebSocketConnect(socketId int, isTapper bool) {
|
|||||||
func (h *RoutesEventHandlers) WebSocketDisconnect(socketId int, isTapper bool) {
|
func (h *RoutesEventHandlers) WebSocketDisconnect(socketId int, isTapper bool) {
|
||||||
if isTapper {
|
if isTapper {
|
||||||
rlog.Infof("Websocket event - Tapper disconnected, socket ID: %d", socketId)
|
rlog.Infof("Websocket event - Tapper disconnected, socket ID: %d", socketId)
|
||||||
|
providers.TapperRemoved()
|
||||||
} else {
|
} else {
|
||||||
rlog.Infof("Websocket event - Browser socket disconnected, socket ID: %d", socketId)
|
rlog.Infof("Websocket event - Browser socket disconnected, socket ID: %d", socketId)
|
||||||
socketListLock.Lock()
|
socketListLock.Lock()
|
||||||
@@ -51,7 +55,7 @@ func BroadcastToBrowserClients(message []byte) {
|
|||||||
go func(socketId int) {
|
go func(socketId int) {
|
||||||
err := SendToSocket(socketId, message)
|
err := SendToSocket(socketId, message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("error sending message to socket ID %d: %v", socketId, err)
|
rlog.Errorf("error sending message to socket ID %d: %v", socketId, err)
|
||||||
}
|
}
|
||||||
}(socketId)
|
}(socketId)
|
||||||
}
|
}
|
||||||
@@ -70,7 +74,8 @@ func (h *RoutesEventHandlers) WebSocketMessage(_ int, message []byte) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
rlog.Infof("Could not unmarshal message of message type %s %v\n", socketMessageBase.MessageType, err)
|
rlog.Infof("Could not unmarshal message of message type %s %v\n", socketMessageBase.MessageType, err)
|
||||||
} else {
|
} else {
|
||||||
h.SocketHarOutChannel <- tappedEntryMessage.Data
|
// NOTE: This is where the message comes back from the intermediate WebSocket to code.
|
||||||
|
h.SocketOutChannel <- tappedEntryMessage.Data
|
||||||
}
|
}
|
||||||
case shared.WebSocketMessageTypeUpdateStatus:
|
case shared.WebSocketMessageTypeUpdateStatus:
|
||||||
var statusMessage shared.WebSocketStatusMessage
|
var statusMessage shared.WebSocketStatusMessage
|
||||||
@@ -113,7 +118,7 @@ func handleTLSLink(outboundLinkMessage models.WebsocketOutboundLinkMessage) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
rlog.Errorf("Error marshaling outbound link message for broadcasting: %v", err)
|
rlog.Errorf("Error marshaling outbound link message for broadcasting: %v", err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Broadcasting outboundlink message %s\n", string(marshaledMessage))
|
rlog.Errorf("Broadcasting outboundlink message %s", string(marshaledMessage))
|
||||||
BroadcastToBrowserClients(marshaledMessage)
|
BroadcastToBrowserClients(marshaledMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ package controllers
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/google/martian/har"
|
|
||||||
"github.com/romana/rlog"
|
|
||||||
"mizuserver/pkg/database"
|
"mizuserver/pkg/database"
|
||||||
"mizuserver/pkg/models"
|
"mizuserver/pkg/models"
|
||||||
"mizuserver/pkg/providers"
|
"mizuserver/pkg/providers"
|
||||||
@@ -13,10 +10,22 @@ import (
|
|||||||
"mizuserver/pkg/utils"
|
"mizuserver/pkg/utils"
|
||||||
"mizuserver/pkg/validation"
|
"mizuserver/pkg/validation"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/martian/har"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/romana/rlog"
|
||||||
|
|
||||||
|
tapApi "github.com/up9inc/mizu/tap/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var extensionsMap map[string]*tapApi.Extension // global
|
||||||
|
|
||||||
|
func InitExtensionsMap(ref map[string]*tapApi.Extension) {
|
||||||
|
extensionsMap = ref
|
||||||
|
}
|
||||||
|
|
||||||
func GetEntries(c *gin.Context) {
|
func GetEntries(c *gin.Context) {
|
||||||
entriesFilter := &models.EntriesFilter{}
|
entriesFilter := &models.EntriesFilter{}
|
||||||
|
|
||||||
@@ -30,7 +39,7 @@ func GetEntries(c *gin.Context) {
|
|||||||
|
|
||||||
order := database.OperatorToOrderMapping[entriesFilter.Operator]
|
order := database.OperatorToOrderMapping[entriesFilter.Operator]
|
||||||
operatorSymbol := database.OperatorToSymbolMapping[entriesFilter.Operator]
|
operatorSymbol := database.OperatorToSymbolMapping[entriesFilter.Operator]
|
||||||
var entries []models.MizuEntry
|
var entries []tapApi.MizuEntry
|
||||||
database.GetEntriesTable().
|
database.GetEntriesTable().
|
||||||
Order(fmt.Sprintf("timestamp %s", order)).
|
Order(fmt.Sprintf("timestamp %s", order)).
|
||||||
Where(fmt.Sprintf("timestamp %s %v", operatorSymbol, entriesFilter.Timestamp)).
|
Where(fmt.Sprintf("timestamp %s %v", operatorSymbol, entriesFilter.Timestamp)).
|
||||||
@@ -39,13 +48,13 @@ func GetEntries(c *gin.Context) {
|
|||||||
Find(&entries)
|
Find(&entries)
|
||||||
|
|
||||||
if len(entries) > 0 && order == database.OrderDesc {
|
if len(entries) > 0 && order == database.OrderDesc {
|
||||||
// the entries always order from oldest to newest so we should revers
|
// the entries always order from oldest to newest - we should reverse
|
||||||
utils.ReverseSlice(entries)
|
utils.ReverseSlice(entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
baseEntries := make([]models.BaseEntryDetails, 0)
|
baseEntries := make([]tapApi.BaseEntryDetails, 0)
|
||||||
for _, data := range entries {
|
for _, data := range entries {
|
||||||
harEntry := models.BaseEntryDetails{}
|
harEntry := tapApi.BaseEntryDetails{}
|
||||||
if err := models.GetEntry(&data, &harEntry); err != nil {
|
if err := models.GetEntry(&data, &harEntry); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -55,102 +64,15 @@ func GetEntries(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, baseEntries)
|
c.JSON(http.StatusOK, baseEntries)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetHARs(c *gin.Context) {
|
|
||||||
entriesFilter := &models.HarFetchRequestBody{}
|
|
||||||
order := database.OrderDesc
|
|
||||||
if err := c.BindQuery(entriesFilter); err != nil {
|
|
||||||
c.JSON(http.StatusBadRequest, err)
|
|
||||||
}
|
|
||||||
err := validation.Validate(entriesFilter)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusBadRequest, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var timestampFrom, timestampTo int64
|
|
||||||
|
|
||||||
if entriesFilter.From < 0 {
|
|
||||||
timestampFrom = 0
|
|
||||||
} else {
|
|
||||||
timestampFrom = entriesFilter.From
|
|
||||||
}
|
|
||||||
if entriesFilter.To <= 0 {
|
|
||||||
timestampTo = time.Now().UnixNano() / int64(time.Millisecond)
|
|
||||||
} else {
|
|
||||||
timestampTo = entriesFilter.To
|
|
||||||
}
|
|
||||||
|
|
||||||
var entries []models.MizuEntry
|
|
||||||
database.GetEntriesTable().
|
|
||||||
Where(fmt.Sprintf("timestamp BETWEEN %v AND %v", timestampFrom, timestampTo)).
|
|
||||||
Order(fmt.Sprintf("timestamp %s", order)).
|
|
||||||
Find(&entries)
|
|
||||||
|
|
||||||
if len(entries) > 0 {
|
|
||||||
// the entries always order from oldest to newest so we should revers
|
|
||||||
utils.ReverseSlice(entries)
|
|
||||||
}
|
|
||||||
|
|
||||||
harsObject := map[string]*models.ExtendedHAR{}
|
|
||||||
|
|
||||||
for _, entryData := range entries {
|
|
||||||
var harEntry har.Entry
|
|
||||||
_ = json.Unmarshal([]byte(entryData.Entry), &harEntry)
|
|
||||||
if entryData.ResolvedDestination != "" {
|
|
||||||
harEntry.Request.URL = utils.SetHostname(harEntry.Request.URL, entryData.ResolvedDestination)
|
|
||||||
}
|
|
||||||
|
|
||||||
var fileName string
|
|
||||||
sourceOfEntry := entryData.ResolvedSource
|
|
||||||
if sourceOfEntry != "" {
|
|
||||||
// naively assumes the proper service source is http
|
|
||||||
sourceOfEntry = fmt.Sprintf("http://%s", sourceOfEntry)
|
|
||||||
//replace / from the file name cause they end up creating a corrupted folder
|
|
||||||
fileName = fmt.Sprintf("%s.har", strings.ReplaceAll(sourceOfEntry, "/", "_"))
|
|
||||||
} else {
|
|
||||||
fileName = "unknown_source.har"
|
|
||||||
}
|
|
||||||
if harOfSource, ok := harsObject[fileName]; ok {
|
|
||||||
harOfSource.Log.Entries = append(harOfSource.Log.Entries, &harEntry)
|
|
||||||
} else {
|
|
||||||
var entriesHar []*har.Entry
|
|
||||||
entriesHar = append(entriesHar, &harEntry)
|
|
||||||
harsObject[fileName] = &models.ExtendedHAR{
|
|
||||||
Log: &models.ExtendedLog{
|
|
||||||
Version: "1.2",
|
|
||||||
Creator: &models.ExtendedCreator{
|
|
||||||
Creator: &har.Creator{
|
|
||||||
Name: "mizu",
|
|
||||||
Version: "0.0.2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Entries: entriesHar,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
// leave undefined when no source is present, otherwise modeler assumes source is empty string ""
|
|
||||||
if sourceOfEntry != "" {
|
|
||||||
harsObject[fileName].Log.Creator.Source = &sourceOfEntry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
retObj := map[string][]byte{}
|
|
||||||
for k, v := range harsObject {
|
|
||||||
bytesData, _ := json.Marshal(v)
|
|
||||||
retObj[k] = bytesData
|
|
||||||
}
|
|
||||||
buffer := utils.ZipData(retObj)
|
|
||||||
c.Data(http.StatusOK, "application/octet-stream", buffer.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
func UploadEntries(c *gin.Context) {
|
func UploadEntries(c *gin.Context) {
|
||||||
rlog.Infof("Upload entries - started\n")
|
rlog.Infof("Upload entries - started\n")
|
||||||
|
|
||||||
uploadRequestBody := &models.UploadEntriesRequestBody{}
|
uploadParams := &models.UploadEntriesRequestQuery{}
|
||||||
if err := c.BindQuery(uploadRequestBody); err != nil {
|
if err := c.BindQuery(uploadParams); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, err)
|
c.JSON(http.StatusBadRequest, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := validation.Validate(uploadRequestBody); err != nil {
|
if err := validation.Validate(uploadParams); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, err)
|
c.JSON(http.StatusBadRequest, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -159,19 +81,19 @@ func UploadEntries(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rlog.Infof("Upload entries - creating token. dest %s\n", uploadRequestBody.Dest)
|
rlog.Infof("Upload entries - creating token. dest %s\n", uploadParams.Dest)
|
||||||
token, err := up9.CreateAnonymousToken(uploadRequestBody.Dest)
|
token, err := up9.CreateAnonymousToken(uploadParams.Dest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.String(http.StatusServiceUnavailable, "Cannot analyze, mizu is already analyzing")
|
c.String(http.StatusServiceUnavailable, "Cannot analyze, mizu is already analyzing")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rlog.Infof("Upload entries - uploading. token: %s model: %s\n", token.Token, token.Model)
|
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")
|
c.String(http.StatusOK, "OK")
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFullEntries(c *gin.Context) {
|
func GetFullEntries(c *gin.Context) {
|
||||||
entriesFilter := &models.HarFetchRequestBody{}
|
entriesFilter := &models.HarFetchRequestQuery{}
|
||||||
if err := c.BindQuery(entriesFilter); err != nil {
|
if err := c.BindQuery(entriesFilter); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, err)
|
c.JSON(http.StatusBadRequest, err)
|
||||||
}
|
}
|
||||||
@@ -193,38 +115,59 @@ func GetFullEntries(c *gin.Context) {
|
|||||||
timestampTo = entriesFilter.To
|
timestampTo = entriesFilter.To
|
||||||
}
|
}
|
||||||
|
|
||||||
entriesArray := database.GetEntriesFromDb(timestampFrom, timestampTo)
|
entriesArray := database.GetEntriesFromDb(timestampFrom, timestampTo, nil)
|
||||||
result := make([]models.FullEntryDetails, 0)
|
|
||||||
|
result := make([]har.Entry, 0)
|
||||||
for _, data := range entriesArray {
|
for _, data := range entriesArray {
|
||||||
harEntry := models.FullEntryDetails{}
|
var pair tapApi.RequestResponsePair
|
||||||
if err := models.GetEntry(&data, &harEntry); err != nil {
|
if err := json.Unmarshal([]byte(data.Entry), &pair); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result = append(result, harEntry)
|
harEntry, err := utils.NewEntry(&pair)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = append(result, *harEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, result)
|
c.JSON(http.StatusOK, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetEntry(c *gin.Context) {
|
func GetEntry(c *gin.Context) {
|
||||||
var entryData models.MizuEntry
|
var entryData tapApi.MizuEntry
|
||||||
database.GetEntriesTable().
|
database.GetEntriesTable().
|
||||||
Where(map[string]string{"entryId": c.Param("entryId")}).
|
Where(map[string]string{"entryId": c.Param("entryId")}).
|
||||||
First(&entryData)
|
First(&entryData)
|
||||||
|
|
||||||
fullEntry := models.FullEntryDetails{}
|
extension := extensionsMap[entryData.ProtocolName]
|
||||||
if err := models.GetEntry(&entryData, &fullEntry); err != nil {
|
protocol, representation, bodySize, _ := extension.Dissector.Represent(&entryData)
|
||||||
c.JSON(http.StatusInternalServerError, map[string]interface{}{
|
|
||||||
"error": true,
|
var rules []map[string]interface{}
|
||||||
"msg": "Can't get entry details",
|
var isRulesEnabled bool
|
||||||
})
|
if entryData.ProtocolName == "http" {
|
||||||
|
var pair tapApi.RequestResponsePair
|
||||||
|
json.Unmarshal([]byte(entryData.Entry), &pair)
|
||||||
|
harEntry, _ := utils.NewEntry(&pair)
|
||||||
|
_, rulesMatched, _isRulesEnabled := models.RunValidationRulesState(*harEntry, entryData.Service)
|
||||||
|
isRulesEnabled = _isRulesEnabled
|
||||||
|
inrec, _ := json.Marshal(rulesMatched)
|
||||||
|
json.Unmarshal(inrec, &rules)
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, fullEntry)
|
|
||||||
|
c.JSON(http.StatusOK, tapApi.MizuEntryWrapper{
|
||||||
|
Protocol: protocol,
|
||||||
|
Representation: string(representation),
|
||||||
|
BodySize: bodySize,
|
||||||
|
Data: entryData,
|
||||||
|
Rules: rules,
|
||||||
|
IsRulesEnabled: isRulesEnabled,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteAllEntries(c *gin.Context) {
|
func DeleteAllEntries(c *gin.Context) {
|
||||||
database.GetEntriesTable().
|
database.GetEntriesTable().
|
||||||
Where("1 = 1").
|
Where("1 = 1").
|
||||||
Delete(&models.MizuEntry{})
|
Delete(&tapApi.MizuEntry{})
|
||||||
|
|
||||||
c.JSON(http.StatusOK, map[string]string{
|
c.JSON(http.StatusOK, map[string]string{
|
||||||
"msg": "Success",
|
"msg": "Success",
|
||||||
@@ -233,14 +176,7 @@ func DeleteAllEntries(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetGeneralStats(c *gin.Context) {
|
func GetGeneralStats(c *gin.Context) {
|
||||||
sqlQuery := "SELECT count(*) as count, min(timestamp) as min, max(timestamp) as max from mizu_entries"
|
c.JSON(http.StatusOK, providers.GetGeneralStats())
|
||||||
var result struct {
|
|
||||||
Count int
|
|
||||||
Min int
|
|
||||||
Max int
|
|
||||||
}
|
|
||||||
database.GetEntriesTable().Raw(sqlQuery).Scan(&result)
|
|
||||||
c.JSON(http.StatusOK, result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTappingStatus(c *gin.Context) {
|
func GetTappingStatus(c *gin.Context) {
|
||||||
|
|||||||
@@ -30,3 +30,7 @@ func PostTappedPods(c *gin.Context) {
|
|||||||
api.BroadcastToBrowserClients(jsonBytes)
|
api.BroadcastToBrowserClients(jsonBytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetTappersCount(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, providers.TappersCount)
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,16 +2,18 @@ package database
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"mizuserver/pkg/utils"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gorm.io/driver/sqlite"
|
"gorm.io/driver/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/logger"
|
"gorm.io/gorm/logger"
|
||||||
"mizuserver/pkg/models"
|
|
||||||
"mizuserver/pkg/utils"
|
tapApi "github.com/up9inc/mizu/tap/api"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DBPath = "./entries.db"
|
DBPath = "./entries.db"
|
||||||
OrderDesc = "desc"
|
OrderDesc = "desc"
|
||||||
OrderAsc = "asc"
|
OrderAsc = "asc"
|
||||||
LT = "lt"
|
LT = "lt"
|
||||||
@@ -19,8 +21,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
DB *gorm.DB
|
DB *gorm.DB
|
||||||
IsDBLocked = false
|
IsDBLocked = false
|
||||||
OperatorToSymbolMapping = map[string]string{
|
OperatorToSymbolMapping = map[string]string{
|
||||||
LT: "<",
|
LT: "<",
|
||||||
GT: ">",
|
GT: ">",
|
||||||
@@ -40,7 +42,7 @@ func GetEntriesTable() *gorm.DB {
|
|||||||
return DB.Table("mizu_entries")
|
return DB.Table("mizu_entries")
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateEntry(entry *models.MizuEntry) {
|
func CreateEntry(entry *tapApi.MizuEntry) {
|
||||||
if IsDBLocked {
|
if IsDBLocked {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -51,15 +53,20 @@ func initDataBase(databasePath string) *gorm.DB {
|
|||||||
temp, _ := gorm.Open(sqlite.Open(databasePath), &gorm.Config{
|
temp, _ := gorm.Open(sqlite.Open(databasePath), &gorm.Config{
|
||||||
Logger: &utils.TruncatingLogger{LogLevel: logger.Warn, SlowThreshold: 500 * time.Millisecond},
|
Logger: &utils.TruncatingLogger{LogLevel: logger.Warn, SlowThreshold: 500 * time.Millisecond},
|
||||||
})
|
})
|
||||||
_ = temp.AutoMigrate(&models.MizuEntry{}) // this will ensure table is created
|
_ = temp.AutoMigrate(&tapApi.MizuEntry{}) // this will ensure table is created
|
||||||
return temp
|
return temp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetEntriesFromDb(timestampFrom int64, timestampTo int64, protocolName *string) []tapApi.MizuEntry {
|
||||||
func GetEntriesFromDb(timestampFrom int64, timestampTo int64) []models.MizuEntry {
|
|
||||||
order := OrderDesc
|
order := OrderDesc
|
||||||
var entries []models.MizuEntry
|
protocolNameCondition := "1 = 1"
|
||||||
|
if protocolName != nil {
|
||||||
|
protocolNameCondition = fmt.Sprintf("protocolName = '%s'", *protocolName)
|
||||||
|
}
|
||||||
|
|
||||||
|
var entries []tapApi.MizuEntry
|
||||||
GetEntriesTable().
|
GetEntriesTable().
|
||||||
|
Where(protocolNameCondition).
|
||||||
Where(fmt.Sprintf("timestamp BETWEEN %v AND %v", timestampFrom, timestampTo)).
|
Where(fmt.Sprintf("timestamp BETWEEN %v AND %v", timestampFrom, timestampTo)).
|
||||||
Order(fmt.Sprintf("timestamp %s", order)).
|
Order(fmt.Sprintf("timestamp %s", order)).
|
||||||
Find(&entries)
|
Find(&entries)
|
||||||
@@ -70,4 +77,3 @@ func GetEntriesFromDb(timestampFrom int64, timestampTo int64) []models.MizuEntry
|
|||||||
}
|
}
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"github.com/fsnotify/fsnotify"
|
|
||||||
"github.com/up9inc/mizu/shared"
|
|
||||||
"github.com/up9inc/mizu/shared/debounce"
|
|
||||||
"github.com/up9inc/mizu/shared/units"
|
|
||||||
"log"
|
"log"
|
||||||
"mizuserver/pkg/models"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
"github.com/romana/rlog"
|
||||||
|
"github.com/up9inc/mizu/shared"
|
||||||
|
"github.com/up9inc/mizu/shared/debounce"
|
||||||
|
"github.com/up9inc/mizu/shared/units"
|
||||||
|
tapApi "github.com/up9inc/mizu/tap/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
const percentageOfMaxSizeBytesToPrune = 15
|
const percentageOfMaxSizeBytesToPrune = 15
|
||||||
@@ -47,7 +48,7 @@ func StartEnforcingDatabaseSize() {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return // closed channel
|
return // closed channel
|
||||||
}
|
}
|
||||||
fmt.Printf("filesystem watcher encountered error:%v\n", err)
|
rlog.Errorf("filesystem watcher encountered error:%v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -72,7 +73,7 @@ func getMaxEntriesDBByteSize() (int64, error) {
|
|||||||
func checkFileSize(maxSizeBytes int64) {
|
func checkFileSize(maxSizeBytes int64) {
|
||||||
fileStat, err := os.Stat(DBPath)
|
fileStat, err := os.Stat(DBPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error checking %s file size: %v\n", DBPath, err)
|
rlog.Errorf("Error checking %s file size: %v", DBPath, err)
|
||||||
} else {
|
} else {
|
||||||
if fileStat.Size() > maxSizeBytes {
|
if fileStat.Size() > maxSizeBytes {
|
||||||
pruneOldEntries(fileStat.Size())
|
pruneOldEntries(fileStat.Size())
|
||||||
@@ -83,13 +84,13 @@ func checkFileSize(maxSizeBytes int64) {
|
|||||||
func pruneOldEntries(currentFileSize int64) {
|
func pruneOldEntries(currentFileSize int64) {
|
||||||
// sqlite locks the database while delete or VACUUM are running and sqlite is terrible at handling its own db lock while a lot of inserts are attempted, we prevent a significant bottleneck by handling the db lock ourselves here
|
// sqlite locks the database while delete or VACUUM are running and sqlite is terrible at handling its own db lock while a lot of inserts are attempted, we prevent a significant bottleneck by handling the db lock ourselves here
|
||||||
IsDBLocked = true
|
IsDBLocked = true
|
||||||
defer func() {IsDBLocked = false}()
|
defer func() { IsDBLocked = false }()
|
||||||
|
|
||||||
amountOfBytesToTrim := currentFileSize / (100 / percentageOfMaxSizeBytesToPrune)
|
amountOfBytesToTrim := currentFileSize / (100 / percentageOfMaxSizeBytesToPrune)
|
||||||
|
|
||||||
rows, err := GetEntriesTable().Limit(10000).Order("id").Rows()
|
rows, err := GetEntriesTable().Limit(10000).Order("id").Rows()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error getting 10000 first db rows: %v\n", err)
|
rlog.Errorf("Error getting 10000 first db rows: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,10 +100,10 @@ func pruneOldEntries(currentFileSize int64) {
|
|||||||
if bytesToBeRemoved >= amountOfBytesToTrim {
|
if bytesToBeRemoved >= amountOfBytesToTrim {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
var entry models.MizuEntry
|
var entry tapApi.MizuEntry
|
||||||
err = DB.ScanRows(rows, &entry)
|
err = DB.ScanRows(rows, &entry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error scanning db row: %v\n", err)
|
rlog.Errorf("Error scanning db row: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,11 +112,11 @@ func pruneOldEntries(currentFileSize int64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(entryIdsToRemove) > 0 {
|
if len(entryIdsToRemove) > 0 {
|
||||||
GetEntriesTable().Where(entryIdsToRemove).Delete(models.MizuEntry{})
|
GetEntriesTable().Where(entryIdsToRemove).Delete(tapApi.MizuEntry{})
|
||||||
// VACUUM causes sqlite to shrink the db file after rows have been deleted, the db file will not shrink without this
|
// VACUUM causes sqlite to shrink the db file after rows have been deleted, the db file will not shrink without this
|
||||||
DB.Exec("VACUUM")
|
DB.Exec("VACUUM")
|
||||||
fmt.Printf("Removed %d rows and cleared %s\n", len(entryIdsToRemove), units.BytesToHumanReadable(bytesToBeRemoved))
|
rlog.Errorf("Removed %d rows and cleared %s", len(entryIdsToRemove), units.BytesToHumanReadable(bytesToBeRemoved))
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("Found no rows to remove when pruning")
|
rlog.Error("Found no rows to remove when pruning")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,134 +2,44 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
tapApi "github.com/up9inc/mizu/tap/api"
|
||||||
|
|
||||||
|
"mizuserver/pkg/rules"
|
||||||
|
|
||||||
"github.com/google/martian/har"
|
"github.com/google/martian/har"
|
||||||
"github.com/up9inc/mizu/shared"
|
"github.com/up9inc/mizu/shared"
|
||||||
"github.com/up9inc/mizu/tap"
|
"github.com/up9inc/mizu/tap"
|
||||||
"mizuserver/pkg/utils"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DataUnmarshaler interface {
|
func GetEntry(r *tapApi.MizuEntry, v tapApi.DataUnmarshaler) error {
|
||||||
UnmarshalData(*MizuEntry) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetEntry(r *MizuEntry, v DataUnmarshaler) error {
|
|
||||||
return v.UnmarshalData(r)
|
return v.UnmarshalData(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
type MizuEntry struct {
|
|
||||||
ID uint `gorm:"primarykey"`
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
Entry string `json:"entry,omitempty" gorm:"column:entry"`
|
|
||||||
EntryId string `json:"entryId" gorm:"column:entryId"`
|
|
||||||
Url string `json:"url" gorm:"column:url"`
|
|
||||||
Method string `json:"method" gorm:"column:method"`
|
|
||||||
Status int `json:"status" gorm:"column:status"`
|
|
||||||
RequestSenderIp string `json:"requestSenderIp" gorm:"column:requestSenderIp"`
|
|
||||||
Service string `json:"service" gorm:"column:service"`
|
|
||||||
Timestamp int64 `json:"timestamp" gorm:"column:timestamp"`
|
|
||||||
Path string `json:"path" gorm:"column:path"`
|
|
||||||
ResolvedSource string `json:"resolvedSource,omitempty" gorm:"column:resolvedSource"`
|
|
||||||
ResolvedDestination string `json:"resolvedDestination,omitempty" gorm:"column:resolvedDestination"`
|
|
||||||
IsOutgoing bool `json:"isOutgoing,omitempty" gorm:"column:isOutgoing"`
|
|
||||||
EstimatedSizeBytes int `json:"-" gorm:"column:estimatedSizeBytes"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BaseEntryDetails struct {
|
|
||||||
Id string `json:"id,omitempty"`
|
|
||||||
Url string `json:"url,omitempty"`
|
|
||||||
RequestSenderIp string `json:"requestSenderIp,omitempty"`
|
|
||||||
Service string `json:"service,omitempty"`
|
|
||||||
Path string `json:"path,omitempty"`
|
|
||||||
StatusCode int `json:"statusCode,omitempty"`
|
|
||||||
Method string `json:"method,omitempty"`
|
|
||||||
Timestamp int64 `json:"timestamp,omitempty"`
|
|
||||||
IsOutgoing bool `json:"isOutgoing,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FullEntryDetails struct {
|
|
||||||
har.Entry
|
|
||||||
}
|
|
||||||
|
|
||||||
type FullEntryDetailsExtra struct {
|
|
||||||
har.Entry
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bed *BaseEntryDetails) UnmarshalData(entry *MizuEntry) error {
|
|
||||||
entryUrl := entry.Url
|
|
||||||
service := entry.Service
|
|
||||||
if entry.ResolvedDestination != "" {
|
|
||||||
entryUrl = utils.SetHostname(entryUrl, entry.ResolvedDestination)
|
|
||||||
service = utils.SetHostname(service, entry.ResolvedDestination)
|
|
||||||
}
|
|
||||||
bed.Id = entry.EntryId
|
|
||||||
bed.Url = entryUrl
|
|
||||||
bed.Service = service
|
|
||||||
bed.Path = entry.Path
|
|
||||||
bed.StatusCode = entry.Status
|
|
||||||
bed.Method = entry.Method
|
|
||||||
bed.Timestamp = entry.Timestamp
|
|
||||||
bed.RequestSenderIp = entry.RequestSenderIp
|
|
||||||
bed.IsOutgoing = entry.IsOutgoing
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fed *FullEntryDetails) UnmarshalData(entry *MizuEntry) error {
|
|
||||||
if err := json.Unmarshal([]byte(entry.Entry), &fed.Entry); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if entry.ResolvedDestination != "" {
|
|
||||||
fed.Entry.Request.URL = utils.SetHostname(fed.Entry.Request.URL, entry.ResolvedDestination)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fedex *FullEntryDetailsExtra) UnmarshalData(entry *MizuEntry) error {
|
|
||||||
if err := json.Unmarshal([]byte(entry.Entry), &fedex.Entry); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if entry.ResolvedSource != "" {
|
|
||||||
fedex.Entry.Request.Headers = append(fedex.Request.Headers, har.Header{Name: "x-mizu-source", Value: entry.ResolvedSource})
|
|
||||||
}
|
|
||||||
if entry.ResolvedDestination != "" {
|
|
||||||
fedex.Entry.Request.Headers = append(fedex.Request.Headers, har.Header{Name: "x-mizu-destination", Value: entry.ResolvedDestination})
|
|
||||||
fedex.Entry.Request.URL = utils.SetHostname(fedex.Entry.Request.URL, entry.ResolvedDestination)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type EntryData struct {
|
|
||||||
Entry string `json:"entry,omitempty"`
|
|
||||||
ResolvedDestination string `json:"resolvedDestination,omitempty" gorm:"column:resolvedDestination"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type EntriesFilter struct {
|
type EntriesFilter struct {
|
||||||
Limit int `query:"limit" validate:"required,min=1,max=200"`
|
Limit int `form:"limit" validate:"required,min=1,max=200"`
|
||||||
Operator string `query:"operator" validate:"required,oneof='lt' 'gt'"`
|
Operator string `form:"operator" validate:"required,oneof='lt' 'gt'"`
|
||||||
Timestamp int64 `query:"timestamp" validate:"required,min=1"`
|
Timestamp int64 `form:"timestamp" validate:"required,min=1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UploadEntriesRequestBody struct {
|
type UploadEntriesRequestQuery struct {
|
||||||
Dest string `form:"dest"`
|
Dest string `form:"dest"`
|
||||||
SleepIntervalSec int `form:"interval"`
|
SleepIntervalSec int `form:"interval"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HarFetchRequestBody struct {
|
type HarFetchRequestQuery struct {
|
||||||
From int64 `query:"from"`
|
From int64 `form:"from"`
|
||||||
To int64 `query:"to"`
|
To int64 `form:"to"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebSocketEntryMessage struct {
|
type WebSocketEntryMessage struct {
|
||||||
*shared.WebSocketMessageMetadata
|
*shared.WebSocketMessageMetadata
|
||||||
Data *BaseEntryDetails `json:"data,omitempty"`
|
Data *tapApi.BaseEntryDetails `json:"data,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebSocketTappedEntryMessage struct {
|
type WebSocketTappedEntryMessage struct {
|
||||||
*shared.WebSocketMessageMetadata
|
*shared.WebSocketMessageMetadata
|
||||||
Data *tap.OutputChannelItem
|
Data *tapApi.OutputChannelItem
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebsocketOutboundLinkMessage struct {
|
type WebsocketOutboundLinkMessage struct {
|
||||||
@@ -137,7 +47,7 @@ type WebsocketOutboundLinkMessage struct {
|
|||||||
Data *tap.OutboundLink
|
Data *tap.OutboundLink
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateBaseEntryWebSocketMessage(base *BaseEntryDetails) ([]byte, error) {
|
func CreateBaseEntryWebSocketMessage(base *tapApi.BaseEntryDetails) ([]byte, error) {
|
||||||
message := &WebSocketEntryMessage{
|
message := &WebSocketEntryMessage{
|
||||||
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
|
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
|
||||||
MessageType: shared.WebSocketMessageTypeEntry,
|
MessageType: shared.WebSocketMessageTypeEntry,
|
||||||
@@ -147,7 +57,7 @@ func CreateBaseEntryWebSocketMessage(base *BaseEntryDetails) ([]byte, error) {
|
|||||||
return json.Marshal(message)
|
return json.Marshal(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateWebsocketTappedEntryMessage(base *tap.OutputChannelItem) ([]byte, error) {
|
func CreateWebsocketTappedEntryMessage(base *tapApi.OutputChannelItem) ([]byte, error) {
|
||||||
message := &WebSocketTappedEntryMessage{
|
message := &WebSocketTappedEntryMessage{
|
||||||
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
|
WebSocketMessageMetadata: &shared.WebSocketMessageMetadata{
|
||||||
MessageType: shared.WebSocketMessageTypeTappedEntry,
|
MessageType: shared.WebSocketMessageTypeTappedEntry,
|
||||||
@@ -186,3 +96,9 @@ type ExtendedCreator struct {
|
|||||||
*har.Creator
|
*har.Creator
|
||||||
Source *string `json:"_source"`
|
Source *string `json:"_source"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RunValidationRulesState(harEntry har.Entry, service string) (tapApi.ApplicableRules, []rules.RulesMatched, bool) {
|
||||||
|
resultPolicyToSend, isEnabled := rules.MatchRequestPolicy(harEntry, service)
|
||||||
|
statusPolicyToSend, latency, numberOfRules := rules.PassedValidationRules(resultPolicyToSend)
|
||||||
|
return tapApi.ApplicableRules{Status: statusPolicyToSend, Latency: latency, NumberOfRules: numberOfRules}, resultPolicyToSend, isEnabled
|
||||||
|
}
|
||||||
|
|||||||
36
agent/pkg/providers/stats_provider.go
Normal file
36
agent/pkg/providers/stats_provider.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package providers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GeneralStats struct {
|
||||||
|
EntriesCount int
|
||||||
|
FirstEntryTimestamp int
|
||||||
|
LastEntryTimestamp int
|
||||||
|
}
|
||||||
|
|
||||||
|
var generalStats = GeneralStats{}
|
||||||
|
|
||||||
|
func ResetGeneralStats() {
|
||||||
|
generalStats = GeneralStats{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGeneralStats() GeneralStats {
|
||||||
|
return generalStats
|
||||||
|
}
|
||||||
|
|
||||||
|
func EntryAdded() {
|
||||||
|
generalStats.EntriesCount++
|
||||||
|
|
||||||
|
currentTimestamp := int(time.Now().Unix())
|
||||||
|
|
||||||
|
if reflect.Value.IsZero(reflect.ValueOf(generalStats.FirstEntryTimestamp)) {
|
||||||
|
generalStats.FirstEntryTimestamp = currentTimestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
generalStats.LastEntryTimestamp = currentTimestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
35
agent/pkg/providers/stats_provider_test.go
Normal file
35
agent/pkg/providers/stats_provider_test.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package providers_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"mizuserver/pkg/providers"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNoEntryAddedCount(t *testing.T) {
|
||||||
|
entriesStats := providers.GetGeneralStats()
|
||||||
|
|
||||||
|
if entriesStats.EntriesCount != 0 {
|
||||||
|
t.Errorf("unexpected result - expected: %v, actual: %v", 0, entriesStats.EntriesCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEntryAddedCount(t *testing.T) {
|
||||||
|
tests := []int{1, 5, 10, 100, 500, 1000}
|
||||||
|
|
||||||
|
for _, entriesCount := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%d", entriesCount), func(t *testing.T) {
|
||||||
|
for i := 0; i < entriesCount; i++ {
|
||||||
|
providers.EntryAdded()
|
||||||
|
}
|
||||||
|
|
||||||
|
entriesStats := providers.GetGeneralStats()
|
||||||
|
|
||||||
|
if entriesStats.EntriesCount != entriesCount {
|
||||||
|
t.Errorf("unexpected result - expected: %v, actual: %v", entriesCount, entriesStats.EntriesCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Cleanup(providers.ResetGeneralStats)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,14 +4,18 @@ import (
|
|||||||
"github.com/patrickmn/go-cache"
|
"github.com/patrickmn/go-cache"
|
||||||
"github.com/up9inc/mizu/shared"
|
"github.com/up9inc/mizu/shared"
|
||||||
"github.com/up9inc/mizu/tap"
|
"github.com/up9inc/mizu/tap"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const tlsLinkRetainmentTime = time.Minute * 15
|
const tlsLinkRetainmentTime = time.Minute * 15
|
||||||
|
|
||||||
var (
|
var (
|
||||||
TapStatus shared.TapStatus
|
TappersCount int
|
||||||
|
TapStatus shared.TapStatus
|
||||||
RecentTLSLinks = cache.New(tlsLinkRetainmentTime, tlsLinkRetainmentTime)
|
RecentTLSLinks = cache.New(tlsLinkRetainmentTime, tlsLinkRetainmentTime)
|
||||||
|
|
||||||
|
tappersCountLock = sync.Mutex{}
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetAllRecentTLSAddresses() []string {
|
func GetAllRecentTLSAddresses() []string {
|
||||||
@@ -26,3 +30,15 @@ func GetAllRecentTLSAddresses() []string {
|
|||||||
|
|
||||||
return recentTLSLinks
|
return recentTLSLinks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TapperAdded() {
|
||||||
|
tappersCountLock.Lock()
|
||||||
|
TappersCount++
|
||||||
|
tappersCountLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TapperRemoved() {
|
||||||
|
tappersCountLock.Lock()
|
||||||
|
TappersCount--
|
||||||
|
tappersCountLock.Unlock()
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ Now you will be able to import `github.com/up9inc/mizu/resolver` in any `.go` fi
|
|||||||
errOut := make(chan error, 100)
|
errOut := make(chan error, 100)
|
||||||
k8sResolver, err := resolver.NewFromOutOfCluster("", errOut)
|
k8sResolver, err := resolver.NewFromOutOfCluster("", errOut)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("error creating k8s resolver %s", err)
|
rlog.Errorf("error creating k8s resolver %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
@@ -40,15 +40,15 @@ k8sResolver.Start(ctx)
|
|||||||
|
|
||||||
resolvedName := k8sResolver.Resolve("10.107.251.91") // will always return `nil` in real scenarios as the internal map takes a moment to populate after `Start` is called
|
resolvedName := k8sResolver.Resolve("10.107.251.91") // will always return `nil` in real scenarios as the internal map takes a moment to populate after `Start` is called
|
||||||
if resolvedName != nil {
|
if resolvedName != nil {
|
||||||
fmt.Printf("resolved 10.107.251.91=%s", *resolvedName)
|
rlog.Errorf("resolved 10.107.251.91=%s", *resolvedName)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Could not find a resolved name for 10.107.251.91")
|
rlog.Error("Could not find a resolved name for 10.107.251.91")
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case err := <- errOut:
|
case err := <- errOut:
|
||||||
fmt.Printf("name resolving error %s", err)
|
rlog.Errorf("name resolving error %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package resolver
|
package resolver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
cmap "github.com/orcaman/concurrent-map"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/azure"
|
_ "k8s.io/client-go/plugin/pkg/client/auth/azure"
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||||
@@ -9,7 +10,7 @@ import (
|
|||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewFromInCluster(errOut chan error) (*Resolver, error) {
|
func NewFromInCluster(errOut chan error, namesapce string) (*Resolver, error) {
|
||||||
config, err := restclient.InClusterConfig()
|
config, err := restclient.InClusterConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -18,5 +19,5 @@ func NewFromInCluster(errOut chan error) (*Resolver, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Resolver{clientConfig: config, clientSet: clientset, nameMap: make(map[string]string), serviceMap: make(map[string]string), errOut: errOut}, nil
|
return &Resolver{clientConfig: config, clientSet: clientset, nameMap: cmap.New(), serviceMap: cmap.New(), errOut: errOut, namespace: namesapce}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,31 +4,36 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/romana/rlog"
|
"github.com/romana/rlog"
|
||||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
|
||||||
|
cmap "github.com/orcaman/concurrent-map"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
kubClientNullString = "None"
|
kubClientNullString = "None"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Resolver struct {
|
type Resolver struct {
|
||||||
clientConfig *restclient.Config
|
clientConfig *restclient.Config
|
||||||
clientSet *kubernetes.Clientset
|
clientSet *kubernetes.Clientset
|
||||||
nameMap map[string]string
|
nameMap cmap.ConcurrentMap
|
||||||
serviceMap map[string]string
|
serviceMap cmap.ConcurrentMap
|
||||||
isStarted bool
|
isStarted bool
|
||||||
errOut chan error
|
errOut chan error
|
||||||
|
namespace string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resolver *Resolver) Start(ctx context.Context) {
|
func (resolver *Resolver) Start(ctx context.Context) {
|
||||||
if !resolver.isStarted {
|
if !resolver.isStarted {
|
||||||
resolver.isStarted = true
|
resolver.isStarted = true
|
||||||
|
|
||||||
go resolver.infiniteErrorHandleRetryFunc(ctx, resolver.watchServices)
|
go resolver.infiniteErrorHandleRetryFunc(ctx, resolver.watchServices)
|
||||||
go resolver.infiniteErrorHandleRetryFunc(ctx, resolver.watchEndpoints)
|
go resolver.infiniteErrorHandleRetryFunc(ctx, resolver.watchEndpoints)
|
||||||
go resolver.infiniteErrorHandleRetryFunc(ctx, resolver.watchPods)
|
go resolver.infiniteErrorHandleRetryFunc(ctx, resolver.watchPods)
|
||||||
@@ -36,97 +41,97 @@ func (resolver *Resolver) Start(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (resolver *Resolver) Resolve(name string) string {
|
func (resolver *Resolver) Resolve(name string) string {
|
||||||
resolvedName, isFound := resolver.nameMap[name]
|
resolvedName, isFound := resolver.nameMap.Get(name)
|
||||||
if !isFound {
|
if !isFound {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return resolvedName
|
return resolvedName.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resolver *Resolver) GetMap() map[string]string {
|
func (resolver *Resolver) GetMap() cmap.ConcurrentMap {
|
||||||
return resolver.nameMap
|
return resolver.nameMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resolver *Resolver) CheckIsServiceIP(address string) bool {
|
func (resolver *Resolver) CheckIsServiceIP(address string) bool {
|
||||||
_, isFound := resolver.serviceMap[address]
|
_, isFound := resolver.serviceMap.Get(address)
|
||||||
return isFound
|
return isFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resolver *Resolver) watchPods(ctx context.Context) error {
|
func (resolver *Resolver) watchPods(ctx context.Context) error {
|
||||||
// empty namespace makes the client watch all namespaces
|
// empty namespace makes the client watch all namespaces
|
||||||
watcher, err := resolver.clientSet.CoreV1().Pods("").Watch(ctx, metav1.ListOptions{Watch: true})
|
watcher, err := resolver.clientSet.CoreV1().Pods(resolver.namespace).Watch(ctx, metav1.ListOptions{Watch: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case event := <- watcher.ResultChan():
|
case event := <-watcher.ResultChan():
|
||||||
if event.Object == nil {
|
if event.Object == nil {
|
||||||
return errors.New("error in kubectl pod watch")
|
return errors.New("error in kubectl pod watch")
|
||||||
}
|
}
|
||||||
if event.Type == watch.Deleted {
|
if event.Type == watch.Deleted {
|
||||||
pod := event.Object.(*corev1.Pod)
|
pod := event.Object.(*corev1.Pod)
|
||||||
resolver.saveResolvedName(pod.Status.PodIP, "", event.Type)
|
resolver.saveResolvedName(pod.Status.PodIP, "", event.Type)
|
||||||
}
|
}
|
||||||
case <- ctx.Done():
|
case <-ctx.Done():
|
||||||
watcher.Stop()
|
watcher.Stop()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resolver *Resolver) watchEndpoints(ctx context.Context) error {
|
func (resolver *Resolver) watchEndpoints(ctx context.Context) error {
|
||||||
// empty namespace makes the client watch all namespaces
|
// empty namespace makes the client watch all namespaces
|
||||||
watcher, err := resolver.clientSet.CoreV1().Endpoints("").Watch(ctx, metav1.ListOptions{Watch: true})
|
watcher, err := resolver.clientSet.CoreV1().Endpoints(resolver.namespace).Watch(ctx, metav1.ListOptions{Watch: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case event := <- watcher.ResultChan():
|
case event := <-watcher.ResultChan():
|
||||||
if event.Object == nil {
|
if event.Object == nil {
|
||||||
return errors.New("error in kubectl endpoint watch")
|
return errors.New("error in kubectl endpoint watch")
|
||||||
}
|
}
|
||||||
endpoint := event.Object.(*corev1.Endpoints)
|
endpoint := event.Object.(*corev1.Endpoints)
|
||||||
serviceHostname := fmt.Sprintf("%s.%s", endpoint.Name, endpoint.Namespace)
|
serviceHostname := fmt.Sprintf("%s.%s", endpoint.Name, endpoint.Namespace)
|
||||||
if endpoint.Subsets != nil {
|
if endpoint.Subsets != nil {
|
||||||
for _, subset := range endpoint.Subsets {
|
for _, subset := range endpoint.Subsets {
|
||||||
var ports []int32
|
var ports []int32
|
||||||
if subset.Ports != nil {
|
if subset.Ports != nil {
|
||||||
for _, portMapping := range subset.Ports {
|
for _, portMapping := range subset.Ports {
|
||||||
if portMapping.Port > 0 {
|
if portMapping.Port > 0 {
|
||||||
ports = append(ports, portMapping.Port)
|
ports = append(ports, portMapping.Port)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if subset.Addresses != nil {
|
|
||||||
for _, address := range subset.Addresses {
|
|
||||||
resolver.saveResolvedName(address.IP, serviceHostname, event.Type)
|
|
||||||
for _, port := range ports {
|
|
||||||
ipWithPort := fmt.Sprintf("%s:%d", address.IP, port)
|
|
||||||
resolver.saveResolvedName(ipWithPort, serviceHostname, event.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
if subset.Addresses != nil {
|
||||||
|
for _, address := range subset.Addresses {
|
||||||
|
resolver.saveResolvedName(address.IP, serviceHostname, event.Type)
|
||||||
|
for _, port := range ports {
|
||||||
|
ipWithPort := fmt.Sprintf("%s:%d", address.IP, port)
|
||||||
|
resolver.saveResolvedName(ipWithPort, serviceHostname, event.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
case <- ctx.Done():
|
}
|
||||||
watcher.Stop()
|
case <-ctx.Done():
|
||||||
return nil
|
watcher.Stop()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resolver *Resolver) watchServices(ctx context.Context) error {
|
func (resolver *Resolver) watchServices(ctx context.Context) error {
|
||||||
// empty namespace makes the client watch all namespaces
|
// empty namespace makes the client watch all namespaces
|
||||||
watcher, err := resolver.clientSet.CoreV1().Services("").Watch(ctx, metav1.ListOptions{Watch: true})
|
watcher, err := resolver.clientSet.CoreV1().Services(resolver.namespace).Watch(ctx, metav1.ListOptions{Watch: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case event := <- watcher.ResultChan():
|
case event := <-watcher.ResultChan():
|
||||||
if event.Object == nil {
|
if event.Object == nil {
|
||||||
return errors.New("error in kubectl service watch")
|
return errors.New("error in kubectl service watch")
|
||||||
}
|
}
|
||||||
@@ -142,7 +147,7 @@ func (resolver *Resolver) watchServices(ctx context.Context) error {
|
|||||||
resolver.saveResolvedName(ingress.IP, serviceHostname, event.Type)
|
resolver.saveResolvedName(ingress.IP, serviceHostname, event.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case <- ctx.Done():
|
case <-ctx.Done():
|
||||||
watcher.Stop()
|
watcher.Stop()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -151,19 +156,19 @@ func (resolver *Resolver) watchServices(ctx context.Context) error {
|
|||||||
|
|
||||||
func (resolver *Resolver) saveResolvedName(key string, resolved string, eventType watch.EventType) {
|
func (resolver *Resolver) saveResolvedName(key string, resolved string, eventType watch.EventType) {
|
||||||
if eventType == watch.Deleted {
|
if eventType == watch.Deleted {
|
||||||
delete(resolver.nameMap, key)
|
resolver.nameMap.Remove(key)
|
||||||
rlog.Infof("setting %s=nil\n", key)
|
rlog.Infof("setting %s=nil\n", key)
|
||||||
} else {
|
} else {
|
||||||
resolver.nameMap[key] = resolved
|
resolver.nameMap.Set(key, resolved)
|
||||||
rlog.Infof("setting %s=%s\n", key, resolved)
|
rlog.Infof("setting %s=%s\n", key, resolved)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resolver *Resolver) saveServiceIP(key string, resolved string, eventType watch.EventType) {
|
func (resolver *Resolver) saveServiceIP(key string, resolved string, eventType watch.EventType) {
|
||||||
if eventType == watch.Deleted {
|
if eventType == watch.Deleted {
|
||||||
delete(resolver.serviceMap, key)
|
resolver.serviceMap.Remove(key)
|
||||||
} else {
|
} else {
|
||||||
resolver.serviceMap[key] = resolved
|
resolver.serviceMap.Set(key, resolved)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,4 +191,3 @@ func (resolver *Resolver) infiniteErrorHandleRetryFunc(ctx context.Context, fun
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ func EntriesRoutes(ginApp *gin.Engine) {
|
|||||||
routeGroup.GET("/uploadEntries", controllers.UploadEntries)
|
routeGroup.GET("/uploadEntries", controllers.UploadEntries)
|
||||||
routeGroup.GET("/resolving", controllers.GetCurrentResolvingInformation)
|
routeGroup.GET("/resolving", controllers.GetCurrentResolvingInformation)
|
||||||
|
|
||||||
routeGroup.GET("/har", controllers.GetHARs)
|
|
||||||
|
|
||||||
routeGroup.GET("/resetDB", controllers.DeleteAllEntries) // get single (full) entry
|
routeGroup.GET("/resetDB", controllers.DeleteAllEntries) // get single (full) entry
|
||||||
routeGroup.GET("/generalStats", controllers.GetGeneralStats) // get general stats about entries in DB
|
routeGroup.GET("/generalStats", controllers.GetGeneralStats) // get general stats about entries in DB
|
||||||
|
|
||||||
|
|||||||
@@ -9,4 +9,6 @@ func StatusRoutes(ginApp *gin.Engine) {
|
|||||||
routeGroup := ginApp.Group("/status")
|
routeGroup := ginApp.Group("/status")
|
||||||
|
|
||||||
routeGroup.POST("/tappedPods", controllers.PostTappedPods)
|
routeGroup.POST("/tappedPods", controllers.PostTappedPods)
|
||||||
|
|
||||||
|
routeGroup.GET("/tappersCount", controllers.GetTappersCount)
|
||||||
}
|
}
|
||||||
|
|||||||
123
agent/pkg/rules/rulesHTTP.go
Normal file
123
agent/pkg/rules/rulesHTTP.go
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
package rules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/romana/rlog"
|
||||||
|
|
||||||
|
"github.com/google/martian/har"
|
||||||
|
"github.com/up9inc/mizu/shared"
|
||||||
|
jsonpath "github.com/yalp/jsonpath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RulesMatched struct {
|
||||||
|
Matched bool `json:"matched"`
|
||||||
|
Rule shared.RulePolicy `json:"rule"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendRulesMatched(rulesMatched []RulesMatched, matched bool, rule shared.RulePolicy) []RulesMatched {
|
||||||
|
return append(rulesMatched, RulesMatched{Matched: matched, Rule: rule})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidatePath(URLFromRule string, URL string) bool {
|
||||||
|
if URLFromRule != "" {
|
||||||
|
matchPath, err := regexp.MatchString(URLFromRule, URL)
|
||||||
|
if err != nil || !matchPath {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateService(serviceFromRule string, service string) bool {
|
||||||
|
if serviceFromRule != "" {
|
||||||
|
matchService, err := regexp.MatchString(serviceFromRule, service)
|
||||||
|
if err != nil || !matchService {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func MatchRequestPolicy(harEntry har.Entry, service string) (resultPolicyToSend []RulesMatched, isEnabled bool) {
|
||||||
|
enforcePolicy, err := shared.DecodeEnforcePolicy(fmt.Sprintf("%s/%s", shared.RulePolicyPath, shared.RulePolicyFileName))
|
||||||
|
if err == nil {
|
||||||
|
isEnabled = true
|
||||||
|
}
|
||||||
|
for _, rule := range enforcePolicy.Rules {
|
||||||
|
if !ValidatePath(rule.Path, harEntry.Request.URL) || !ValidateService(rule.Service, service) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if rule.Type == "json" {
|
||||||
|
var bodyJsonMap interface{}
|
||||||
|
contentTextDecoded, _ := base64.StdEncoding.DecodeString(string(harEntry.Response.Content.Text))
|
||||||
|
if err := json.Unmarshal(contentTextDecoded, &bodyJsonMap); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out, err := jsonpath.Read(bodyJsonMap, rule.Key)
|
||||||
|
if err != nil || out == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var matchValue bool
|
||||||
|
if reflect.TypeOf(out).Kind() == reflect.String {
|
||||||
|
matchValue, err = regexp.MatchString(rule.Value, out.(string))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rlog.Info(matchValue, rule.Value)
|
||||||
|
} else {
|
||||||
|
val := fmt.Sprint(out)
|
||||||
|
matchValue, err = regexp.MatchString(rule.Value, val)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resultPolicyToSend = appendRulesMatched(resultPolicyToSend, matchValue, rule)
|
||||||
|
} else if rule.Type == "header" {
|
||||||
|
for j := range harEntry.Response.Headers {
|
||||||
|
matchKey, err := regexp.MatchString(rule.Key, harEntry.Response.Headers[j].Name)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if matchKey {
|
||||||
|
matchValue, err := regexp.MatchString(rule.Value, harEntry.Response.Headers[j].Value)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
resultPolicyToSend = appendRulesMatched(resultPolicyToSend, matchValue, rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resultPolicyToSend = appendRulesMatched(resultPolicyToSend, true, rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func PassedValidationRules(rulesMatched []RulesMatched) (bool, int64, int) {
|
||||||
|
var numberOfRulesMatched = len(rulesMatched)
|
||||||
|
var responseTime int64 = -1
|
||||||
|
|
||||||
|
if numberOfRulesMatched == 0 {
|
||||||
|
return false, 0, numberOfRulesMatched
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rule := range rulesMatched {
|
||||||
|
if rule.Matched == false {
|
||||||
|
return false, responseTime, numberOfRulesMatched
|
||||||
|
} else {
|
||||||
|
if strings.ToLower(rule.Rule.Type) == "responseTime" {
|
||||||
|
if rule.Rule.ResponseTime < responseTime || responseTime == -1 {
|
||||||
|
responseTime = rule.Rule.ResponseTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, responseTime, numberOfRulesMatched
|
||||||
|
}
|
||||||
@@ -1,198 +0,0 @@
|
|||||||
package sensitiveDataFiltering
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/xml"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/up9inc/mizu/tap"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/beevik/etree"
|
|
||||||
"github.com/google/martian/har"
|
|
||||||
"github.com/up9inc/mizu/shared"
|
|
||||||
)
|
|
||||||
|
|
||||||
func FilterSensitiveInfoFromHarRequest(harOutputItem *tap.OutputChannelItem, options *shared.TrafficFilteringOptions) {
|
|
||||||
harOutputItem.HarEntry.Request.Headers = filterHarHeaders(harOutputItem.HarEntry.Request.Headers)
|
|
||||||
harOutputItem.HarEntry.Response.Headers = filterHarHeaders(harOutputItem.HarEntry.Response.Headers)
|
|
||||||
|
|
||||||
harOutputItem.HarEntry.Request.Cookies = make([]har.Cookie, 0, 0)
|
|
||||||
harOutputItem.HarEntry.Response.Cookies = make([]har.Cookie, 0, 0)
|
|
||||||
|
|
||||||
harOutputItem.HarEntry.Request.URL = filterUrl(harOutputItem.HarEntry.Request.URL)
|
|
||||||
for i, queryString := range harOutputItem.HarEntry.Request.QueryString {
|
|
||||||
if isFieldNameSensitive(queryString.Name) {
|
|
||||||
harOutputItem.HarEntry.Request.QueryString[i].Value = maskedFieldPlaceholderValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if harOutputItem.HarEntry.Request.PostData != nil {
|
|
||||||
requestContentType := getContentTypeHeaderValue(harOutputItem.HarEntry.Request.Headers)
|
|
||||||
filteredRequestBody, err := filterHttpBody([]byte(harOutputItem.HarEntry.Request.PostData.Text), requestContentType, options)
|
|
||||||
if err == nil {
|
|
||||||
harOutputItem.HarEntry.Request.PostData.Text = string(filteredRequestBody)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if harOutputItem.HarEntry.Response.Content != nil {
|
|
||||||
responseContentType := getContentTypeHeaderValue(harOutputItem.HarEntry.Response.Headers)
|
|
||||||
filteredResponseBody, err := filterHttpBody(harOutputItem.HarEntry.Response.Content.Text, responseContentType, options)
|
|
||||||
if err == nil {
|
|
||||||
harOutputItem.HarEntry.Response.Content.Text = filteredResponseBody
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterHarHeaders(headers []har.Header) []har.Header {
|
|
||||||
newHeaders := make([]har.Header, 0)
|
|
||||||
for i, header := range headers {
|
|
||||||
if strings.ToLower(header.Name) == "cookie" {
|
|
||||||
continue
|
|
||||||
} else if isFieldNameSensitive(header.Name) {
|
|
||||||
newHeaders = append(newHeaders, har.Header{Name: header.Name, Value: maskedFieldPlaceholderValue})
|
|
||||||
headers[i].Value = maskedFieldPlaceholderValue
|
|
||||||
} else {
|
|
||||||
newHeaders = append(newHeaders, header)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newHeaders
|
|
||||||
}
|
|
||||||
|
|
||||||
func getContentTypeHeaderValue(headers []har.Header) string {
|
|
||||||
for _, header := range headers {
|
|
||||||
if strings.ToLower(header.Name) == "content-type" {
|
|
||||||
return header.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func isFieldNameSensitive(fieldName string) bool {
|
|
||||||
name := strings.ToLower(fieldName)
|
|
||||||
name = strings.ReplaceAll(name, "_", "")
|
|
||||||
name = strings.ReplaceAll(name, "-", "")
|
|
||||||
name = strings.ReplaceAll(name, " ", "")
|
|
||||||
|
|
||||||
for _, sensitiveField := range personallyIdentifiableDataFields {
|
|
||||||
if strings.Contains(name, sensitiveField) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterHttpBody(bytes []byte, contentType string, options *shared.TrafficFilteringOptions) ([]byte, error) {
|
|
||||||
mimeType := strings.Split(contentType, ";")[0]
|
|
||||||
switch strings.ToLower(mimeType) {
|
|
||||||
case "application/json":
|
|
||||||
return filterJsonBody(bytes)
|
|
||||||
case "text/html":
|
|
||||||
fallthrough
|
|
||||||
case "application/xhtml+xml":
|
|
||||||
fallthrough
|
|
||||||
case "text/xml":
|
|
||||||
fallthrough
|
|
||||||
case "application/xml":
|
|
||||||
return filterXmlEtree(bytes)
|
|
||||||
case "text/plain":
|
|
||||||
if options != nil && options.PlainTextMaskingRegexes != nil {
|
|
||||||
return filterPlainText(bytes, options), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterPlainText(bytes []byte, options *shared.TrafficFilteringOptions) []byte {
|
|
||||||
for _, regex := range options.PlainTextMaskingRegexes {
|
|
||||||
bytes = regex.ReplaceAll(bytes, []byte(maskedFieldPlaceholderValue))
|
|
||||||
}
|
|
||||||
return bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterXmlEtree(bytes []byte) ([]byte, error) {
|
|
||||||
if !IsValidXML(bytes) {
|
|
||||||
return nil, errors.New("Invalid XML")
|
|
||||||
}
|
|
||||||
xmlDoc := etree.NewDocument()
|
|
||||||
err := xmlDoc.ReadFromBytes(bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
filterXmlElement(xmlDoc.Root())
|
|
||||||
}
|
|
||||||
return xmlDoc.WriteToBytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsValidXML(data []byte) bool {
|
|
||||||
return xml.Unmarshal(data, new(interface{})) == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterXmlElement(element *etree.Element) {
|
|
||||||
for i, attribute := range element.Attr {
|
|
||||||
if isFieldNameSensitive(attribute.Key) {
|
|
||||||
element.Attr[i].Value = maskedFieldPlaceholderValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if element.ChildElements() == nil || len(element.ChildElements()) == 0 {
|
|
||||||
if isFieldNameSensitive(element.Tag) {
|
|
||||||
element.SetText(maskedFieldPlaceholderValue)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for _, element := range element.ChildElements() {
|
|
||||||
filterXmlElement(element)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterJsonBody(bytes []byte) ([]byte, error) {
|
|
||||||
var bodyJsonMap map[string] interface{}
|
|
||||||
err := json.Unmarshal(bytes ,&bodyJsonMap)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
filterJsonMap(bodyJsonMap)
|
|
||||||
return json.Marshal(bodyJsonMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterJsonMap(jsonMap map[string] interface{}) {
|
|
||||||
for key, value := range jsonMap {
|
|
||||||
if value == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
nestedMap, isNested := value.(map[string] interface{})
|
|
||||||
if isNested {
|
|
||||||
filterJsonMap(nestedMap)
|
|
||||||
} else {
|
|
||||||
if isFieldNameSensitive(key) {
|
|
||||||
jsonMap[key] = maskedFieldPlaceholderValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// receives string representing url, returns string url without sensitive query param values (http://service/api?userId=bob&password=123&type=login -> http://service/api?userId=[REDACTED]&password=[REDACTED]&type=login)
|
|
||||||
func filterUrl(originalUrl string) string {
|
|
||||||
parsedUrl, err := url.Parse(originalUrl)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Sprintf("http://%s", maskedFieldPlaceholderValue)
|
|
||||||
} else {
|
|
||||||
if len(parsedUrl.RawQuery) > 0 {
|
|
||||||
newQueryArgs := make([]string, 0)
|
|
||||||
for urlQueryParamName, urlQueryParamValues := range parsedUrl.Query() {
|
|
||||||
newValues := urlQueryParamValues
|
|
||||||
if isFieldNameSensitive(urlQueryParamName) {
|
|
||||||
newValues = []string {maskedFieldPlaceholderValue}
|
|
||||||
}
|
|
||||||
for _, paramValue := range newValues {
|
|
||||||
newQueryArgs = append(newQueryArgs, fmt.Sprintf("%s=%s", urlQueryParamName, paramValue))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedUrl.RawQuery = strings.Join(newQueryArgs, "&")
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsedUrl.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,12 +5,14 @@ import (
|
|||||||
"compress/zlib"
|
"compress/zlib"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/google/martian/har"
|
||||||
"github.com/romana/rlog"
|
"github.com/romana/rlog"
|
||||||
"github.com/up9inc/mizu/shared"
|
"github.com/up9inc/mizu/shared"
|
||||||
|
tapApi "github.com/up9inc/mizu/tap/api"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"mizuserver/pkg/database"
|
"mizuserver/pkg/database"
|
||||||
"mizuserver/pkg/models"
|
"mizuserver/pkg/utils"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -129,21 +131,33 @@ func UploadEntriesImpl(token string, model string, envPrefix string, sleepInterv
|
|||||||
for {
|
for {
|
||||||
timestampTo := time.Now().UnixNano() / int64(time.Millisecond)
|
timestampTo := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
rlog.Infof("Getting entries from %v, to %v\n", timestampFrom, timestampTo)
|
rlog.Infof("Getting entries from %v, to %v\n", timestampFrom, timestampTo)
|
||||||
entriesArray := database.GetEntriesFromDb(timestampFrom, timestampTo)
|
protocolFilter := "http"
|
||||||
|
entriesArray := database.GetEntriesFromDb(timestampFrom, timestampTo, &protocolFilter)
|
||||||
|
|
||||||
if len(entriesArray) > 0 {
|
if len(entriesArray) > 0 {
|
||||||
|
result := make([]har.Entry, 0)
|
||||||
fullEntriesExtra := make([]models.FullEntryDetailsExtra, 0)
|
|
||||||
for _, data := range entriesArray {
|
for _, data := range entriesArray {
|
||||||
harEntry := models.FullEntryDetailsExtra{}
|
var pair tapApi.RequestResponsePair
|
||||||
if err := models.GetEntry(&data, &harEntry); err != nil {
|
if err := json.Unmarshal([]byte(data.Entry), &pair); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fullEntriesExtra = append(fullEntriesExtra, harEntry)
|
harEntry, err := utils.NewEntry(&pair)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if data.ResolvedSource != "" {
|
||||||
|
harEntry.Request.Headers = append(harEntry.Request.Headers, har.Header{Name: "x-mizu-source", Value: data.ResolvedSource})
|
||||||
|
}
|
||||||
|
if data.ResolvedDestination != "" {
|
||||||
|
harEntry.Request.Headers = append(harEntry.Request.Headers, har.Header{Name: "x-mizu-destination", Value: data.ResolvedDestination})
|
||||||
|
harEntry.Request.URL = utils.SetHostname(harEntry.Request.URL, data.ResolvedDestination)
|
||||||
|
}
|
||||||
|
result = append(result, *harEntry)
|
||||||
}
|
}
|
||||||
rlog.Infof("About to upload %v entries\n", len(fullEntriesExtra))
|
|
||||||
|
|
||||||
body, jMarshalErr := json.Marshal(fullEntriesExtra)
|
rlog.Infof("About to upload %v entries\n", len(result))
|
||||||
|
|
||||||
|
body, jMarshalErr := json.Marshal(result)
|
||||||
if jMarshalErr != nil {
|
if jMarshalErr != nil {
|
||||||
analyzeInformation.Reset()
|
analyzeInformation.Reset()
|
||||||
rlog.Infof("Stopping analyzing")
|
rlog.Infof("Stopping analyzing")
|
||||||
|
|||||||
256
agent/pkg/utils/har.go
Normal file
256
agent/pkg/utils/har.go
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/martian/har"
|
||||||
|
"github.com/romana/rlog"
|
||||||
|
"github.com/up9inc/mizu/tap/api"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Keep it because we might want cookies in the future
|
||||||
|
//func BuildCookies(rawCookies []interface{}) []har.Cookie {
|
||||||
|
// cookies := make([]har.Cookie, 0, len(rawCookies))
|
||||||
|
//
|
||||||
|
// for _, cookie := range rawCookies {
|
||||||
|
// c := cookie.(map[string]interface{})
|
||||||
|
// expiresStr := ""
|
||||||
|
// if c["expires"] != nil {
|
||||||
|
// expiresStr = c["expires"].(string)
|
||||||
|
// }
|
||||||
|
// expires, _ := time.Parse(time.RFC3339, expiresStr)
|
||||||
|
// httpOnly := false
|
||||||
|
// if c["httponly"] != nil {
|
||||||
|
// httpOnly, _ = strconv.ParseBool(c["httponly"].(string))
|
||||||
|
// }
|
||||||
|
// secure := false
|
||||||
|
// if c["secure"] != nil {
|
||||||
|
// secure, _ = strconv.ParseBool(c["secure"].(string))
|
||||||
|
// }
|
||||||
|
// path := ""
|
||||||
|
// if c["path"] != nil {
|
||||||
|
// path = c["path"].(string)
|
||||||
|
// }
|
||||||
|
// domain := ""
|
||||||
|
// if c["domain"] != nil {
|
||||||
|
// domain = c["domain"].(string)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// cookies = append(cookies, har.Cookie{
|
||||||
|
// Name: c["name"].(string),
|
||||||
|
// Value: c["value"].(string),
|
||||||
|
// Path: path,
|
||||||
|
// Domain: domain,
|
||||||
|
// HTTPOnly: httpOnly,
|
||||||
|
// Secure: secure,
|
||||||
|
// Expires: expires,
|
||||||
|
// Expires8601: expiresStr,
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return cookies
|
||||||
|
//}
|
||||||
|
|
||||||
|
func BuildHeaders(rawHeaders []interface{}) ([]har.Header, string, string, string, string, string) {
|
||||||
|
var host, scheme, authority, path, status string
|
||||||
|
headers := make([]har.Header, 0, len(rawHeaders))
|
||||||
|
|
||||||
|
for _, header := range rawHeaders {
|
||||||
|
h := header.(map[string]interface{})
|
||||||
|
|
||||||
|
headers = append(headers, har.Header{
|
||||||
|
Name: h["name"].(string),
|
||||||
|
Value: h["value"].(string),
|
||||||
|
})
|
||||||
|
|
||||||
|
if h["name"] == "Host" {
|
||||||
|
host = h["value"].(string)
|
||||||
|
}
|
||||||
|
if h["name"] == ":authority" {
|
||||||
|
authority = h["value"].(string)
|
||||||
|
}
|
||||||
|
if h["name"] == ":scheme" {
|
||||||
|
scheme = h["value"].(string)
|
||||||
|
}
|
||||||
|
if h["name"] == ":path" {
|
||||||
|
path = h["value"].(string)
|
||||||
|
}
|
||||||
|
if h["name"] == ":status" {
|
||||||
|
status = h["value"].(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers, host, scheme, authority, path, status
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildPostParams(rawParams []interface{}) []har.Param {
|
||||||
|
params := make([]har.Param, 0, len(rawParams))
|
||||||
|
for _, param := range rawParams {
|
||||||
|
p := param.(map[string]interface{})
|
||||||
|
name := ""
|
||||||
|
if p["name"] != nil {
|
||||||
|
name = p["name"].(string)
|
||||||
|
}
|
||||||
|
value := ""
|
||||||
|
if p["value"] != nil {
|
||||||
|
value = p["value"].(string)
|
||||||
|
}
|
||||||
|
fileName := ""
|
||||||
|
if p["fileName"] != nil {
|
||||||
|
fileName = p["fileName"].(string)
|
||||||
|
}
|
||||||
|
contentType := ""
|
||||||
|
if p["contentType"] != nil {
|
||||||
|
contentType = p["contentType"].(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
params = append(params, har.Param{
|
||||||
|
Name: name,
|
||||||
|
Value: value,
|
||||||
|
Filename: fileName,
|
||||||
|
ContentType: contentType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRequest(request *api.GenericMessage) (harRequest *har.Request, err error) {
|
||||||
|
reqDetails := request.Payload.(map[string]interface{})["details"].(map[string]interface{})
|
||||||
|
|
||||||
|
headers, host, scheme, authority, path, _ := BuildHeaders(reqDetails["headers"].([]interface{}))
|
||||||
|
cookies := make([]har.Cookie, 0) // BuildCookies(reqDetails["cookies"].([]interface{}))
|
||||||
|
|
||||||
|
postData, _ := reqDetails["postData"].(map[string]interface{})
|
||||||
|
mimeType, _ := postData["mimeType"]
|
||||||
|
if mimeType == nil || len(mimeType.(string)) == 0 {
|
||||||
|
mimeType = "text/html"
|
||||||
|
}
|
||||||
|
text, _ := postData["text"]
|
||||||
|
postDataText := ""
|
||||||
|
if text != nil {
|
||||||
|
postDataText = text.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
queryString := make([]har.QueryString, 0)
|
||||||
|
for _, _qs := range reqDetails["queryString"].([]interface{}) {
|
||||||
|
qs := _qs.(map[string]interface{})
|
||||||
|
queryString = append(queryString, har.QueryString{
|
||||||
|
Name: qs["name"].(string),
|
||||||
|
Value: qs["value"].(string),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("http://%s%s", host, reqDetails["url"].(string))
|
||||||
|
if strings.HasPrefix(mimeType.(string), "application/grpc") {
|
||||||
|
url = fmt.Sprintf("%s://%s%s", scheme, authority, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
harParams := make([]har.Param, 0)
|
||||||
|
if postData["params"] != nil {
|
||||||
|
harParams = BuildPostParams(postData["params"].([]interface{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
harRequest = &har.Request{
|
||||||
|
Method: reqDetails["method"].(string),
|
||||||
|
URL: url,
|
||||||
|
HTTPVersion: reqDetails["httpVersion"].(string),
|
||||||
|
HeadersSize: -1,
|
||||||
|
BodySize: int64(bytes.NewBufferString(postDataText).Len()),
|
||||||
|
QueryString: queryString,
|
||||||
|
Headers: headers,
|
||||||
|
Cookies: cookies,
|
||||||
|
PostData: &har.PostData{
|
||||||
|
MimeType: mimeType.(string),
|
||||||
|
Params: harParams,
|
||||||
|
Text: postDataText,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResponse(response *api.GenericMessage) (harResponse *har.Response, err error) {
|
||||||
|
resDetails := response.Payload.(map[string]interface{})["details"].(map[string]interface{})
|
||||||
|
|
||||||
|
headers, _, _, _, _, _status := BuildHeaders(resDetails["headers"].([]interface{}))
|
||||||
|
cookies := make([]har.Cookie, 0) // BuildCookies(resDetails["cookies"].([]interface{}))
|
||||||
|
|
||||||
|
content, _ := resDetails["content"].(map[string]interface{})
|
||||||
|
mimeType, _ := content["mimeType"]
|
||||||
|
if mimeType == nil || len(mimeType.(string)) == 0 {
|
||||||
|
mimeType = "text/html"
|
||||||
|
}
|
||||||
|
encoding, _ := content["encoding"]
|
||||||
|
text, _ := content["text"]
|
||||||
|
bodyText := ""
|
||||||
|
if text != nil {
|
||||||
|
bodyText = text.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
harContent := &har.Content{
|
||||||
|
Encoding: encoding.(string),
|
||||||
|
MimeType: mimeType.(string),
|
||||||
|
Text: []byte(bodyText),
|
||||||
|
Size: int64(len(bodyText)),
|
||||||
|
}
|
||||||
|
|
||||||
|
status := int(resDetails["status"].(float64))
|
||||||
|
if strings.HasPrefix(mimeType.(string), "application/grpc") {
|
||||||
|
status, err = strconv.Atoi(_status)
|
||||||
|
if err != nil {
|
||||||
|
rlog.Errorf("Failed converting status to int %s (%v,%+v)", err, err, err)
|
||||||
|
return nil, errors.New("failed converting response status to int for HAR")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
harResponse = &har.Response{
|
||||||
|
HTTPVersion: resDetails["httpVersion"].(string),
|
||||||
|
Status: status,
|
||||||
|
StatusText: resDetails["statusText"].(string),
|
||||||
|
HeadersSize: -1,
|
||||||
|
BodySize: int64(bytes.NewBufferString(bodyText).Len()),
|
||||||
|
Headers: headers,
|
||||||
|
Cookies: cookies,
|
||||||
|
Content: harContent,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEntry(pair *api.RequestResponsePair) (*har.Entry, error) {
|
||||||
|
harRequest, err := NewRequest(&pair.Request)
|
||||||
|
if err != nil {
|
||||||
|
rlog.Errorf("Failed converting request to HAR %s (%v,%+v)", err, err, err)
|
||||||
|
return nil, errors.New("failed converting request to HAR")
|
||||||
|
}
|
||||||
|
|
||||||
|
harResponse, err := NewResponse(&pair.Response)
|
||||||
|
if err != nil {
|
||||||
|
rlog.Errorf("Failed converting response to HAR %s (%v,%+v)", err, err, err)
|
||||||
|
return nil, errors.New("failed converting response to HAR")
|
||||||
|
}
|
||||||
|
|
||||||
|
totalTime := pair.Response.CaptureTime.Sub(pair.Request.CaptureTime).Round(time.Millisecond).Milliseconds()
|
||||||
|
if totalTime < 1 {
|
||||||
|
totalTime = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
harEntry := har.Entry{
|
||||||
|
StartedDateTime: pair.Request.CaptureTime,
|
||||||
|
Time: totalTime,
|
||||||
|
Request: harRequest,
|
||||||
|
Response: harResponse,
|
||||||
|
Cache: &har.Cache{},
|
||||||
|
Timings: &har.Timings{
|
||||||
|
Send: -1,
|
||||||
|
Wait: -1,
|
||||||
|
Receive: totalTime,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return &harEntry, nil
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/romana/rlog"
|
||||||
"gorm.io/gorm/logger"
|
"gorm.io/gorm/logger"
|
||||||
"gorm.io/gorm/utils"
|
"gorm.io/gorm/utils"
|
||||||
"time"
|
"time"
|
||||||
@@ -10,7 +11,7 @@ import (
|
|||||||
|
|
||||||
// TruncatingLogger implements the gorm logger.Interface interface. Its purpose is to act as gorm's logger while truncating logs to a max of 50 characters to minimise the performance impact
|
// TruncatingLogger implements the gorm logger.Interface interface. Its purpose is to act as gorm's logger while truncating logs to a max of 50 characters to minimise the performance impact
|
||||||
type TruncatingLogger struct {
|
type TruncatingLogger struct {
|
||||||
LogLevel logger.LogLevel
|
LogLevel logger.LogLevel
|
||||||
SlowThreshold time.Duration
|
SlowThreshold time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,21 +24,21 @@ func (truncatingLogger *TruncatingLogger) Info(_ context.Context, message string
|
|||||||
if truncatingLogger.LogLevel < logger.Info {
|
if truncatingLogger.LogLevel < logger.Info {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("gorm info: %.150s\n", message)
|
rlog.Errorf("gorm info: %.150s", message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (truncatingLogger *TruncatingLogger) Warn(_ context.Context, message string, __ ...interface{}) {
|
func (truncatingLogger *TruncatingLogger) Warn(_ context.Context, message string, __ ...interface{}) {
|
||||||
if truncatingLogger.LogLevel < logger.Warn {
|
if truncatingLogger.LogLevel < logger.Warn {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("gorm warning: %.150s\n", message)
|
rlog.Errorf("gorm warning: %.150s", message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (truncatingLogger *TruncatingLogger) Error(_ context.Context, message string, __ ...interface{}) {
|
func (truncatingLogger *TruncatingLogger) Error(_ context.Context, message string, __ ...interface{}) {
|
||||||
if truncatingLogger.LogLevel < logger.Error {
|
if truncatingLogger.LogLevel < logger.Error {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("gorm error: %.150s\n", message)
|
rlog.Errorf("gorm error: %.150s", message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (truncatingLogger *TruncatingLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
|
func (truncatingLogger *TruncatingLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/romana/rlog"
|
"github.com/romana/rlog"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@@ -18,8 +17,8 @@ import (
|
|||||||
func StartServer(app *gin.Engine) {
|
func StartServer(app *gin.Engine) {
|
||||||
signals := make(chan os.Signal, 2)
|
signals := make(chan os.Signal, 2)
|
||||||
signal.Notify(signals,
|
signal.Notify(signals,
|
||||||
os.Interrupt, // this catch ctrl + c
|
os.Interrupt, // this catch ctrl + c
|
||||||
syscall.SIGTSTP, // this catch ctrl + z
|
syscall.SIGTSTP, // this catch ctrl + z
|
||||||
)
|
)
|
||||||
|
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
@@ -36,8 +35,9 @@ func StartServer(app *gin.Engine) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Run server.
|
// Run server.
|
||||||
|
rlog.Infof("Starting the server...")
|
||||||
if err := app.Run(":8899"); err != nil {
|
if err := app.Run(":8899"); err != nil {
|
||||||
log.Printf("Oops... Server is not running! Reason: %v", err)
|
rlog.Errorf("Server is not running! Reason: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,15 +54,14 @@ func ReverseSlice(data interface{}) {
|
|||||||
|
|
||||||
func CheckErr(e error) {
|
func CheckErr(e error) {
|
||||||
if e != nil {
|
if e != nil {
|
||||||
log.Printf("%v", e)
|
rlog.Errorf("%v", e)
|
||||||
//panic(e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetHostname(address, newHostname string) string {
|
func SetHostname(address, newHostname string) string {
|
||||||
replacedUrl, err := url.Parse(address)
|
replacedUrl, err := url.Parse(address)
|
||||||
if err != nil{
|
if err != nil {
|
||||||
log.Printf("error replacing hostname to %s in address %s, returning original %v",newHostname, address, err)
|
rlog.Errorf("error replacing hostname to %s in address %s, returning original %v", newHostname, address, err)
|
||||||
return address
|
return address
|
||||||
}
|
}
|
||||||
replacedUrl.Host = newHostname
|
replacedUrl.Host = newHostname
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
./mizuagent -i any -hardump -targets ${TAPPED_ADDRESSES}
|
|
||||||
BIN
assets/validation-example1.png
Normal file
BIN
assets/validation-example1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
BIN
assets/validation-example2.png
Normal file
BIN
assets/validation-example2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
@@ -24,7 +24,7 @@ build: ## Build mizu CLI binary (select platform via GOOS / GOARCH env variables
|
|||||||
|
|
||||||
build-all: ## Build for all supported platforms.
|
build-all: ## Build for all supported platforms.
|
||||||
@echo "Compiling for every OS and Platform"
|
@echo "Compiling for every OS and Platform"
|
||||||
@mkdir -p bin && echo "SHA256 checksums available for compiled binaries \n\nRun \`shasum -a 256 -c mizu_OS_ARCH.sha256\` to verify\n\n" > bin/README.md
|
@mkdir -p bin && sed s/_SEM_VER_/$(SEM_VER)/g README.md.TEMPLATE > bin/README.md
|
||||||
@$(MAKE) build GOOS=darwin GOARCH=amd64
|
@$(MAKE) build GOOS=darwin GOARCH=amd64
|
||||||
@$(MAKE) build GOOS=linux GOARCH=amd64
|
@$(MAKE) build GOOS=linux GOARCH=amd64
|
||||||
@# $(MAKE) build GOOS=darwin GOARCH=arm64
|
@# $(MAKE) build GOOS=darwin GOARCH=arm64
|
||||||
@@ -39,3 +39,6 @@ build-all: ## Build for all supported platforms.
|
|||||||
clean: ## Clean all build artifacts.
|
clean: ## Clean all build artifacts.
|
||||||
go clean
|
go clean
|
||||||
rm -rf ./bin/*
|
rm -rf ./bin/*
|
||||||
|
|
||||||
|
test: ## Run cli tests.
|
||||||
|
@go test ./... -coverpkg=./... -race -coverprofile=coverage.out -covermode=atomic
|
||||||
|
|||||||
@@ -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
|
|
||||||
20
cli/README.md.TEMPLATE
Normal file
20
cli/README.md.TEMPLATE
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Mizu release _SEM_VER_
|
||||||
|
|
||||||
|
Download Mizu for your platform
|
||||||
|
|
||||||
|
**Mac** (on Intel chip)
|
||||||
|
```
|
||||||
|
curl -Lo mizu https://github.com/up9inc/mizu/releases/download/_SEM_VER_/mizu_darwin_amd64 && chmod 755 mizu
|
||||||
|
```
|
||||||
|
|
||||||
|
**Linux**
|
||||||
|
```
|
||||||
|
curl -Lo mizu https://github.com/up9inc/mizu/releases/download/_SEM_VER_/mizu_linux_amd64 && chmod 755 mizu
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Checksums
|
||||||
|
SHA256 checksums available for compiled binaries.
|
||||||
|
Run `shasum -a 256 -c mizu_OS_ARCH.sha256` to verify.
|
||||||
|
|
||||||
|
|
||||||
155
cli/apiserver/provider.go
Normal file
155
cli/apiserver/provider.go
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
package apiserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/up9inc/mizu/cli/config"
|
||||||
|
"github.com/up9inc/mizu/cli/logger"
|
||||||
|
"github.com/up9inc/mizu/cli/uiUtils"
|
||||||
|
"github.com/up9inc/mizu/shared"
|
||||||
|
"io/ioutil"
|
||||||
|
core "k8s.io/api/core/v1"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type apiServerProvider struct {
|
||||||
|
url string
|
||||||
|
isReady bool
|
||||||
|
retries int
|
||||||
|
}
|
||||||
|
|
||||||
|
var Provider = apiServerProvider{retries: config.GetIntEnvConfig(config.ApiServerRetries, 20)}
|
||||||
|
|
||||||
|
func (provider *apiServerProvider) InitAndTestConnection(url string) error {
|
||||||
|
healthUrl := fmt.Sprintf("%s/", url)
|
||||||
|
retriesLeft := provider.retries
|
||||||
|
for retriesLeft > 0 {
|
||||||
|
if response, err := http.Get(healthUrl); err != nil {
|
||||||
|
logger.Log.Debugf("[ERROR] failed connecting to api server %v", err)
|
||||||
|
} else if response.StatusCode != 200 {
|
||||||
|
responseBody := ""
|
||||||
|
data, readErr := ioutil.ReadAll(response.Body)
|
||||||
|
if readErr == nil {
|
||||||
|
responseBody = string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Debugf("can't connect to api server yet, response status code: %v, body: %v", response.StatusCode, responseBody)
|
||||||
|
|
||||||
|
response.Body.Close()
|
||||||
|
} else {
|
||||||
|
logger.Log.Debugf("connection test to api server passed successfully")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
retriesLeft -= 1
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
if retriesLeft == 0 {
|
||||||
|
provider.isReady = false
|
||||||
|
return fmt.Errorf("couldn't reach the api server after %v retries", provider.retries)
|
||||||
|
}
|
||||||
|
provider.url = url
|
||||||
|
provider.isReady = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *apiServerProvider) ReportTappedPods(pods []core.Pod) error {
|
||||||
|
if !provider.isReady {
|
||||||
|
return fmt.Errorf("trying to reach api server when not initialized yet")
|
||||||
|
}
|
||||||
|
tappedPodsUrl := fmt.Sprintf("%s/status/tappedPods", provider.url)
|
||||||
|
|
||||||
|
podInfos := make([]shared.PodInfo, 0)
|
||||||
|
for _, pod := range pods {
|
||||||
|
podInfos = append(podInfos, shared.PodInfo{Name: pod.Name, Namespace: pod.Namespace})
|
||||||
|
}
|
||||||
|
tapStatus := shared.TapStatus{Pods: podInfos}
|
||||||
|
|
||||||
|
if jsonValue, err := json.Marshal(tapStatus); err != nil {
|
||||||
|
return fmt.Errorf("failed Marshal the tapped pods %w", err)
|
||||||
|
} else {
|
||||||
|
if response, err := http.Post(tappedPodsUrl, "application/json", bytes.NewBuffer(jsonValue)); err != nil {
|
||||||
|
return fmt.Errorf("failed sending to API server the tapped pods %w", err)
|
||||||
|
} else if response.StatusCode != 200 {
|
||||||
|
return fmt.Errorf("failed sending to API server the tapped pods, response status code %v", response.StatusCode)
|
||||||
|
} else {
|
||||||
|
logger.Log.Debugf("Reported to server API about %d taped pods successfully", len(podInfos))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *apiServerProvider) RequestAnalysis(analysisDestination string, sleepIntervalSec int) error {
|
||||||
|
if !provider.isReady {
|
||||||
|
return fmt.Errorf("trying to reach api server when not initialized yet")
|
||||||
|
}
|
||||||
|
urlPath := fmt.Sprintf("%s/api/uploadEntries?dest=%s&interval=%v", provider.url, url.QueryEscape(analysisDestination), sleepIntervalSec)
|
||||||
|
u, parseErr := url.ParseRequestURI(urlPath)
|
||||||
|
if parseErr != nil {
|
||||||
|
logger.Log.Fatal("Failed parsing the URL (consider changing the analysis dest URL), err: %v", parseErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Debugf("Analysis url %v", u.String())
|
||||||
|
if response, requestErr := http.Get(u.String()); requestErr != nil {
|
||||||
|
return fmt.Errorf("failed to notify agent for analysis, err: %w", requestErr)
|
||||||
|
} else if response.StatusCode != 200 {
|
||||||
|
return fmt.Errorf("failed to notify agent for analysis, status code: %v", response.StatusCode)
|
||||||
|
} else {
|
||||||
|
logger.Log.Infof(uiUtils.Purple, "Traffic is uploading to UP9 for further analysis")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *apiServerProvider) GetGeneralStats() (map[string]interface{}, error) {
|
||||||
|
if !provider.isReady {
|
||||||
|
return nil, fmt.Errorf("trying to reach api server when not initialized yet")
|
||||||
|
}
|
||||||
|
generalStatsUrl := fmt.Sprintf("%s/api/generalStats", provider.url)
|
||||||
|
|
||||||
|
response, requestErr := http.Get(generalStatsUrl)
|
||||||
|
if requestErr != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get general stats for telemetry, err: %w", requestErr)
|
||||||
|
} else if response.StatusCode != 200 {
|
||||||
|
return nil, fmt.Errorf("failed to get general stats for telemetry, status code: %v", response.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { _ = response.Body.Close() }()
|
||||||
|
|
||||||
|
data, readErr := ioutil.ReadAll(response.Body)
|
||||||
|
if readErr != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read general stats for telemetry, err: %v", readErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
var generalStats map[string]interface{}
|
||||||
|
if parseErr := json.Unmarshal(data, &generalStats); parseErr != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse general stats for telemetry, err: %v", parseErr)
|
||||||
|
}
|
||||||
|
return generalStats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (provider *apiServerProvider) GetVersion() (string, error) {
|
||||||
|
if !provider.isReady {
|
||||||
|
return "", fmt.Errorf("trying to reach api server when not initialized yet")
|
||||||
|
}
|
||||||
|
versionUrl, _ := url.Parse(fmt.Sprintf("%s/metadata/version", provider.url))
|
||||||
|
req := &http.Request{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
URL: versionUrl,
|
||||||
|
}
|
||||||
|
statusResp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer statusResp.Body.Close()
|
||||||
|
|
||||||
|
versionResponse := &shared.VersionResponse{}
|
||||||
|
if err := json.NewDecoder(statusResp.Body).Decode(&versionResponse); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return versionResponse.SemVer, nil
|
||||||
|
}
|
||||||
69
cli/cmd/common.go
Normal file
69
cli/cmd/common.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/up9inc/mizu/cli/config"
|
||||||
|
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||||
|
"github.com/up9inc/mizu/cli/errormessage"
|
||||||
|
"github.com/up9inc/mizu/cli/kubernetes"
|
||||||
|
"github.com/up9inc/mizu/cli/logger"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
|
"github.com/up9inc/mizu/cli/uiUtils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetApiServerUrl() string {
|
||||||
|
return fmt.Sprintf("http://%s", kubernetes.GetMizuApiServerProxiedHostAndPath(config.Config.Tap.GuiPort))
|
||||||
|
}
|
||||||
|
|
||||||
|
func startProxyReportErrorIfAny(kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
|
||||||
|
err := kubernetes.StartProxy(kubernetesProvider, config.Config.Tap.GuiPort, config.Config.MizuResourcesNamespace, mizu.ApiServerPodName)
|
||||||
|
if err != nil {
|
||||||
|
logger.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()
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Debugf("proxy ended")
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForFinish(ctx context.Context, cancel context.CancelFunc) {
|
||||||
|
logger.Log.Debugf("waiting for finish...")
|
||||||
|
sigChan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||||
|
|
||||||
|
// block until ctx cancel is called or termination signal is received
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
logger.Log.Debugf("ctx done")
|
||||||
|
break
|
||||||
|
case <-sigChan:
|
||||||
|
logger.Log.Debugf("Got termination signal, canceling execution...")
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func openBrowser(url string) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "linux":
|
||||||
|
err = exec.Command("xdg-open", url).Start()
|
||||||
|
case "windows":
|
||||||
|
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
|
||||||
|
case "darwin":
|
||||||
|
err = exec.Command("open", url).Start()
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unsupported platform")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Errorf("error while opening browser, %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,32 +2,36 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/creasty/defaults"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/up9inc/mizu/cli/mizu"
|
"github.com/up9inc/mizu/cli/config"
|
||||||
|
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||||
|
"github.com/up9inc/mizu/cli/logger"
|
||||||
|
"github.com/up9inc/mizu/cli/telemetry"
|
||||||
"github.com/up9inc/mizu/cli/uiUtils"
|
"github.com/up9inc/mizu/cli/uiUtils"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
var regenerateFile bool
|
|
||||||
|
|
||||||
var configCmd = &cobra.Command{
|
var configCmd = &cobra.Command{
|
||||||
Use: "config",
|
Use: "config",
|
||||||
Short: "Generate config with default values",
|
Short: "Generate config with default values",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
template, err := mizu.GetConfigWithDefaults()
|
go telemetry.ReportRun("config", config.Config.Config)
|
||||||
|
|
||||||
|
template, err := config.GetConfigWithDefaults()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mizu.Log.Errorf("Failed generating config with defaults %v", err)
|
logger.Log.Errorf("Failed generating config with defaults %v", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if regenerateFile {
|
if config.Config.Config.Regenerate {
|
||||||
data := []byte(template)
|
data := []byte(template)
|
||||||
if err := ioutil.WriteFile(mizu.GetConfigFilePath(), data, 0644); err != nil {
|
if err := ioutil.WriteFile(config.Config.ConfigFilePath, data, 0644); err != nil {
|
||||||
mizu.Log.Errorf("Failed writing config %v", err)
|
logger.Log.Errorf("Failed writing config %v", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
mizu.Log.Infof(fmt.Sprintf("Template File written to %s", fmt.Sprintf(uiUtils.Purple, mizu.GetConfigFilePath())))
|
logger.Log.Infof(fmt.Sprintf("Template File written to %s", fmt.Sprintf(uiUtils.Purple, config.Config.ConfigFilePath)))
|
||||||
} else {
|
} else {
|
||||||
mizu.Log.Debugf("Writing template config.\n%v", template)
|
logger.Log.Debugf("Writing template config.\n%v", template)
|
||||||
fmt.Printf("%v", template)
|
fmt.Printf("%v", template)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -36,5 +40,9 @@ var configCmd = &cobra.Command{
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(configCmd)
|
rootCmd.AddCommand(configCmd)
|
||||||
configCmd.Flags().BoolVarP(®enerateFile, "regenerate", "r", false, fmt.Sprintf("Regenerate the config file with default values %s", mizu.GetConfigFilePath()))
|
|
||||||
|
defaultConfig := config.ConfigStruct{}
|
||||||
|
defaults.Set(&defaultConfig)
|
||||||
|
|
||||||
|
configCmd.Flags().BoolP(configStructs.RegenerateConfigName, "r", defaultConfig.Config.Regenerate, fmt.Sprintf("Regenerate the config file with default values to path %s or to chosen path using --%s", defaultConfig.ConfigFilePath, config.ConfigFilePathCommandName))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/creasty/defaults"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/up9inc/mizu/cli/mizu"
|
|
||||||
"github.com/up9inc/mizu/cli/mizu/configStructs"
|
|
||||||
)
|
|
||||||
|
|
||||||
var fetchCmd = &cobra.Command{
|
|
||||||
Use: "fetch",
|
|
||||||
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 {
|
|
||||||
return err
|
|
||||||
} else if !isCompatible {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
RunMizuFetch()
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(fetchCmd)
|
|
||||||
|
|
||||||
defaultFetchConfig := configStructs.FetchConfig{}
|
|
||||||
defaults.Set(&defaultFetchConfig)
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/zip"
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"github.com/up9inc/mizu/cli/kubernetes"
|
|
||||||
"github.com/up9inc/mizu/cli/mizu"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RunMizuFetch() {
|
|
||||||
mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Fetch.MizuPort)
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() { _ = resp.Body.Close() }()
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
zipReader, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = Unzip(zipReader, mizu.Config.Fetch.Directory)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Unzip(reader *zip.Reader, dest string) error {
|
|
||||||
dest, _ = filepath.Abs(dest)
|
|
||||||
_ = os.MkdirAll(dest, os.ModePerm)
|
|
||||||
|
|
||||||
// Closure to address file descriptors issue with all the deferred .Close() methods
|
|
||||||
extractAndWriteFile := func(f *zip.File) error {
|
|
||||||
rc, err := f.Open()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err := rc.Close(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
path := filepath.Join(dest, f.Name)
|
|
||||||
|
|
||||||
// Check for ZipSlip (Directory traversal)
|
|
||||||
if !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) {
|
|
||||||
return fmt.Errorf("illegal file path: %s", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.FileInfo().IsDir() {
|
|
||||||
_ = os.MkdirAll(path, f.Mode())
|
|
||||||
} else {
|
|
||||||
_ = os.MkdirAll(filepath.Dir(path), f.Mode())
|
|
||||||
mizu.Log.Infof("writing HAR file [ %v ]", path)
|
|
||||||
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err := f.Close(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
mizu.Log.Info(" done")
|
|
||||||
}()
|
|
||||||
|
|
||||||
_, err = io.Copy(f, rc)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range reader.File {
|
|
||||||
err := extractAndWriteFile(f)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
50
cli/cmd/logs.go
Normal file
50
cli/cmd/logs.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/creasty/defaults"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/up9inc/mizu/cli/config"
|
||||||
|
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||||
|
"github.com/up9inc/mizu/cli/errormessage"
|
||||||
|
"github.com/up9inc/mizu/cli/kubernetes"
|
||||||
|
"github.com/up9inc/mizu/cli/logger"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu/fsUtils"
|
||||||
|
"github.com/up9inc/mizu/cli/telemetry"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logsCmd = &cobra.Command{
|
||||||
|
Use: "logs",
|
||||||
|
Short: "Create a zip file with logs for Github issue or troubleshoot",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
go telemetry.ReportRun("logs", config.Config.Logs)
|
||||||
|
|
||||||
|
kubernetesProvider, err := kubernetes.NewProvider(config.Config.KubeConfigPath())
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Error(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ctx, _ := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
if validationErr := config.Config.Logs.Validate(); validationErr != nil {
|
||||||
|
return errormessage.FormatError(validationErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Debugf("Using file path %s", config.Config.Logs.FilePath())
|
||||||
|
|
||||||
|
if dumpLogsErr := fsUtils.DumpLogs(ctx, kubernetesProvider, config.Config.Logs.FilePath()); dumpLogsErr != nil {
|
||||||
|
logger.Log.Errorf("Failed dump logs %v", dumpLogsErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(logsCmd)
|
||||||
|
|
||||||
|
defaultLogsConfig := configStructs.LogsConfig{}
|
||||||
|
defaults.Set(&defaultLogsConfig)
|
||||||
|
|
||||||
|
logsCmd.Flags().StringP(configStructs.FileLogsName, "f", defaultLogsConfig.FileStr, "Path for zip file (default current <pwd>\\mizu_logs.zip)")
|
||||||
|
}
|
||||||
@@ -1,10 +1,16 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/creasty/defaults"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/up9inc/mizu/cli/config"
|
||||||
|
"github.com/up9inc/mizu/cli/logger"
|
||||||
"github.com/up9inc/mizu/cli/mizu"
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu/fsUtils"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu/version"
|
||||||
|
"github.com/up9inc/mizu/cli/uiUtils"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
@@ -13,21 +19,42 @@ var rootCmd = &cobra.Command{
|
|||||||
Long: `A web traffic viewer for kubernetes
|
Long: `A web traffic viewer for kubernetes
|
||||||
Further info is available at https://github.com/up9inc/mizu`,
|
Further info is available at https://github.com/up9inc/mizu`,
|
||||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if err := mizu.InitConfig(cmd); err != nil {
|
if err := config.InitConfig(cmd); err != nil {
|
||||||
mizu.Log.Errorf("Invalid config, Exit %s", err)
|
logger.Log.Fatal(err)
|
||||||
return errors.New(fmt.Sprintf("%v", err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.PersistentFlags().StringSlice(mizu.SetCommandName, []string{}, fmt.Sprintf("Override values using --%s", mizu.SetCommandName))
|
defaultConfig := config.ConfigStruct{}
|
||||||
|
defaults.Set(&defaultConfig)
|
||||||
|
|
||||||
|
rootCmd.PersistentFlags().StringSlice(config.SetCommandName, []string{}, fmt.Sprintf("Override values using --%s", config.SetCommandName))
|
||||||
|
rootCmd.PersistentFlags().String(config.ConfigFilePathCommandName, defaultConfig.ConfigFilePath, fmt.Sprintf("Override config file path using --%s", config.ConfigFilePathCommandName))
|
||||||
|
}
|
||||||
|
|
||||||
|
func printNewVersionIfNeeded(versionChan chan string) {
|
||||||
|
select {
|
||||||
|
case versionMsg := <-versionChan:
|
||||||
|
if versionMsg != "" {
|
||||||
|
logger.Log.Infof(uiUtils.Yellow, versionMsg)
|
||||||
|
}
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||||
// This is called by main.main(). It only needs to happen once to the tapCmd.
|
// This is called by main.main(). It only needs to happen once to the tapCmd.
|
||||||
func Execute() {
|
func Execute() {
|
||||||
|
if err := fsUtils.EnsureDir(mizu.GetMizuFolderPath()); err != nil {
|
||||||
|
logger.Log.Errorf("Failed to use mizu folder, %v", err)
|
||||||
|
}
|
||||||
|
logger.InitLogger()
|
||||||
|
|
||||||
|
versionChan := make(chan string)
|
||||||
|
defer printNewVersionIfNeeded(versionChan)
|
||||||
|
go version.CheckNewerVersion(versionChan)
|
||||||
|
|
||||||
cobra.CheckErr(rootCmd.Execute())
|
cobra.CheckErr(rootCmd.Execute())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,18 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/up9inc/mizu/cli/config"
|
||||||
|
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||||
|
"github.com/up9inc/mizu/cli/logger"
|
||||||
|
"github.com/up9inc/mizu/cli/telemetry"
|
||||||
|
|
||||||
"github.com/creasty/defaults"
|
"github.com/creasty/defaults"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/up9inc/mizu/cli/mizu"
|
"github.com/up9inc/mizu/cli/errormessage"
|
||||||
"github.com/up9inc/mizu/cli/mizu/configStructs"
|
|
||||||
"github.com/up9inc/mizu/cli/uiUtils"
|
"github.com/up9inc/mizu/cli/uiUtils"
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const analysisMessageToConfirm = `NOTE: running mizu with --analysis flag will upload recorded traffic for further analysis and enriched presentation options.`
|
const analysisMessageToConfirm = `NOTE: running mizu with --analysis flag will upload recorded traffic for further analysis and enriched presentation options.`
|
||||||
@@ -18,27 +24,27 @@ var tapCmd = &cobra.Command{
|
|||||||
Long: `Record the ingoing traffic of a kubernetes pod.
|
Long: `Record the ingoing traffic of a kubernetes pod.
|
||||||
Supported protocols are HTTP and gRPC.`,
|
Supported protocols are HTTP and gRPC.`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
go mizu.ReportRun("tap", mizu.Config.Tap)
|
go telemetry.ReportRun("tap", config.Config.Tap)
|
||||||
RunMizuTap()
|
RunMizuTap()
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) == 1 {
|
if len(args) == 1 {
|
||||||
mizu.Config.Tap.PodRegexStr = args[0]
|
config.Config.Tap.PodRegexStr = args[0]
|
||||||
} else if len(args) > 1 {
|
} else if len(args) > 1 {
|
||||||
return errors.New("unexpected number of arguments")
|
return errors.New("unexpected number of arguments")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := mizu.Config.Tap.Validate(); err != nil {
|
if err := config.Config.Tap.Validate(); err != nil {
|
||||||
return err
|
return errormessage.FormatError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mizu.Log.Infof("Mizu will store up to %s of traffic, old traffic will be cleared once the limit is reached.", mizu.Config.Tap.HumanMaxEntriesDBSize)
|
logger.Log.Infof("Mizu will store up to %s of traffic, old traffic will be cleared once the limit is reached.", config.Config.Tap.HumanMaxEntriesDBSize)
|
||||||
|
|
||||||
if mizu.Config.Tap.Analysis {
|
if config.Config.Tap.Analysis {
|
||||||
mizu.Log.Infof(analysisMessageToConfirm)
|
logger.Log.Infof(analysisMessageToConfirm)
|
||||||
if !uiUtils.AskForConfirmation("Would you like to proceed [Y/n]: ") {
|
if !uiUtils.AskForConfirmation("Would you like to proceed [Y/n]: ") {
|
||||||
mizu.Log.Infof("You can always run mizu without analysis, aborting")
|
logger.Log.Infof("You can always run mizu without analysis, aborting")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,14 +60,15 @@ func init() {
|
|||||||
defaults.Set(&defaultTapConfig)
|
defaults.Set(&defaultTapConfig)
|
||||||
|
|
||||||
tapCmd.Flags().Uint16P(configStructs.GuiPortTapName, "p", defaultTapConfig.GuiPort, "Provide a custom port for the web interface webserver")
|
tapCmd.Flags().Uint16P(configStructs.GuiPortTapName, "p", defaultTapConfig.GuiPort, "Provide a custom port for the web interface webserver")
|
||||||
tapCmd.Flags().StringP(configStructs.NamespaceTapName, "n", defaultTapConfig.Namespace, "Namespace 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().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().BoolP(configStructs.AllNamespacesTapName, "A", defaultTapConfig.AllNamespaces, "Tap all namespaces")
|
||||||
tapCmd.Flags().StringP(configStructs.KubeConfigPathTapName, "k", defaultTapConfig.KubeConfigPath, "Path to kube-config file")
|
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().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().Bool(configStructs.DisableRedactionTapName, defaultTapConfig.DisableRedaction, "Disables redaction of potentially sensitive request/response headers and body values")
|
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().Bool(configStructs.DryRunTapName, defaultTapConfig.DryRun, "Preview of all pods matching the regex, without tapping them")
|
||||||
|
tapCmd.Flags().String(configStructs.EnforcePolicyFile, defaultTapConfig.EnforcePolicyFile, "Yaml file path with policy rules")
|
||||||
|
|
||||||
|
tapCmd.Flags().String(configStructs.EnforcePolicyFileDeprecated, defaultTapConfig.EnforcePolicyFileDeprecated, "Yaml file with policy rules")
|
||||||
|
tapCmd.Flags().MarkDeprecated(configStructs.EnforcePolicyFileDeprecated, fmt.Sprintf("Use --%s instead", configStructs.EnforcePolicyFile))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,179 +1,233 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
core "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
|
||||||
|
"github.com/up9inc/mizu/cli/apiserver"
|
||||||
|
"github.com/up9inc/mizu/cli/config"
|
||||||
|
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||||
|
"github.com/up9inc/mizu/cli/errormessage"
|
||||||
"github.com/up9inc/mizu/cli/kubernetes"
|
"github.com/up9inc/mizu/cli/kubernetes"
|
||||||
|
"github.com/up9inc/mizu/cli/logger"
|
||||||
"github.com/up9inc/mizu/cli/mizu"
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu/fsUtils"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu/goUtils"
|
||||||
|
"github.com/up9inc/mizu/cli/telemetry"
|
||||||
"github.com/up9inc/mizu/cli/uiUtils"
|
"github.com/up9inc/mizu/cli/uiUtils"
|
||||||
"github.com/up9inc/mizu/shared"
|
"github.com/up9inc/mizu/shared"
|
||||||
"github.com/up9inc/mizu/shared/debounce"
|
"github.com/up9inc/mizu/shared/debounce"
|
||||||
core "k8s.io/api/core/v1"
|
"github.com/up9inc/mizu/tap/api"
|
||||||
errors "k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"regexp"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var mizuServiceAccountExists bool
|
|
||||||
var apiServerService *core.Service
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
updateTappersDelay = 5 * time.Second
|
|
||||||
cleanupTimeout = time.Minute
|
cleanupTimeout = time.Minute
|
||||||
|
updateTappersDelay = 5 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
var currentlyTappedPods []core.Pod
|
type tapState struct {
|
||||||
|
apiServerService *core.Service
|
||||||
|
currentlyTappedPods []core.Pod
|
||||||
|
mizuServiceAccountExists bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var state tapState
|
||||||
|
|
||||||
func RunMizuTap() {
|
func RunMizuTap() {
|
||||||
mizuApiFilteringOptions, err := getMizuApiFilteringOptions()
|
mizuApiFilteringOptions, err := getMizuApiFilteringOptions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error parsing regex-masking: %v", errormessage.FormatError(err)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
kubernetesProvider, err := kubernetes.NewProvider(mizu.Config.Tap.KubeConfigPath)
|
var mizuValidationRules string
|
||||||
if err != nil {
|
if config.Config.Tap.EnforcePolicyFile != "" || config.Config.Tap.EnforcePolicyFileDeprecated != "" {
|
||||||
if clientcmd.IsEmptyConfig(err) {
|
var trafficValidation string
|
||||||
mizu.Log.Infof(uiUtils.Red, "Couldn't find the kube config file, or file is empty. Try adding '--kube-config=<path to kube config file>'\n")
|
if config.Config.Tap.EnforcePolicyFile != "" {
|
||||||
return
|
trafficValidation = config.Config.Tap.EnforcePolicyFile
|
||||||
|
} else {
|
||||||
|
trafficValidation = config.Config.Tap.EnforcePolicyFileDeprecated
|
||||||
}
|
}
|
||||||
if clientcmd.IsConfigurationInvalid(err) {
|
|
||||||
mizu.Log.Infof(uiUtils.Red, "Invalid kube config file. Try using a different config with '--kube-config=<path to kube config file>'\n")
|
mizuValidationRules, err = readValidationRules(trafficValidation)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error reading policy file: %v", errormessage.FormatError(err)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defer cleanUpMizuResources(kubernetesProvider)
|
kubernetesProvider, err := kubernetes.NewProvider(config.Config.KubeConfigPath())
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel() // cancel will be called when this function exits
|
defer cancel() // cancel will be called when this function exits
|
||||||
|
|
||||||
targetNamespace := getNamespace(kubernetesProvider)
|
targetNamespaces := getNamespaces(kubernetesProvider)
|
||||||
|
|
||||||
|
if config.Config.IsNsRestrictedMode() {
|
||||||
|
if len(targetNamespaces) != 1 || !shared.Contains(targetNamespaces, config.Config.MizuResourcesNamespace) {
|
||||||
|
logger.Log.Errorf("Not supported mode. Mizu can't resolve IPs in other namespaces when running in namespace restricted mode.\n"+
|
||||||
|
"You can use the same namespace for --%s and --%s", configStructs.NamespacesTapName, config.MizuResourcesNamespaceConfigName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var namespacesStr string
|
var namespacesStr string
|
||||||
if targetNamespace != mizu.K8sAllNamespaces {
|
if !shared.Contains(targetNamespaces, mizu.K8sAllNamespaces) {
|
||||||
namespacesStr = fmt.Sprintf("namespace \"%s\"", targetNamespace)
|
namespacesStr = fmt.Sprintf("namespaces \"%s\"", strings.Join(targetNamespaces, "\", \""))
|
||||||
} else {
|
} else {
|
||||||
namespacesStr = "all namespaces"
|
namespacesStr = "all namespaces"
|
||||||
}
|
}
|
||||||
mizu.CheckNewerVersion()
|
|
||||||
mizu.Log.Infof("Tapping pods in %s", namespacesStr)
|
|
||||||
|
|
||||||
if err, _ := updateCurrentlyTappedPods(kubernetesProvider, ctx, targetNamespace); err != nil {
|
logger.Log.Infof("Tapping pods in %s", namespacesStr)
|
||||||
mizu.Log.Infof("Error listing pods: %v", err)
|
|
||||||
|
if err, _ := updateCurrentlyTappedPods(kubernetesProvider, ctx, targetNamespaces); err != nil {
|
||||||
|
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error getting pods by regex: %v", errormessage.FormatError(err)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(currentlyTappedPods) == 0 {
|
if len(state.currentlyTappedPods) == 0 {
|
||||||
var suggestionStr string
|
var suggestionStr string
|
||||||
if targetNamespace != mizu.K8sAllNamespaces {
|
if !shared.Contains(targetNamespaces, mizu.K8sAllNamespaces) {
|
||||||
suggestionStr = "\nSelect a different namespace with -n or tap all namespaces with -A"
|
suggestionStr = ". Select a different namespace with -n or tap all namespaces with -A"
|
||||||
}
|
}
|
||||||
mizu.Log.Infof("Did not find any pods matching the regex argument%s", suggestionStr)
|
logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Did not find any pods matching the regex argument%s", suggestionStr))
|
||||||
}
|
}
|
||||||
|
|
||||||
if mizu.Config.Tap.DryRun {
|
if config.Config.Tap.DryRun {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeToTappedPodIPMap, err := getNodeHostToTappedPodIpsMap(currentlyTappedPods)
|
defer finishMizuExecution(kubernetesProvider)
|
||||||
if err != nil {
|
if err := createMizuResources(ctx, kubernetesProvider, mizuApiFilteringOptions, mizuValidationRules); err != nil {
|
||||||
|
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error creating resources: %v", errormessage.FormatError(err)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := createMizuResources(ctx, kubernetesProvider, nodeToTappedPodIPMap, mizuApiFilteringOptions); err != nil {
|
go goUtils.HandleExcWrapper(watchApiServerPod, ctx, kubernetesProvider, cancel, mizuApiFilteringOptions)
|
||||||
return
|
go goUtils.HandleExcWrapper(watchTapperPod, ctx, kubernetesProvider, cancel)
|
||||||
}
|
go goUtils.HandleExcWrapper(watchPodsForTapping, ctx, kubernetesProvider, targetNamespaces, cancel, mizuApiFilteringOptions)
|
||||||
|
|
||||||
go createProxyToApiServerPod(ctx, kubernetesProvider, cancel)
|
// block until exit signal or error
|
||||||
go watchPodsForTapping(ctx, kubernetesProvider, cancel)
|
|
||||||
|
|
||||||
//block until exit signal or error
|
|
||||||
waitForFinish(ctx, cancel)
|
waitForFinish(ctx, cancel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string, mizuApiFilteringOptions *shared.TrafficFilteringOptions) error {
|
func readValidationRules(file string) (string, error) {
|
||||||
if err := createMizuNamespace(ctx, kubernetesProvider); err != nil {
|
rules, err := shared.DecodeEnforcePolicy(file)
|
||||||
return err
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
newContent, _ := yaml.Marshal(&rules)
|
||||||
|
return string(newContent), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, mizuApiFilteringOptions *api.TrafficFilteringOptions, mizuValidationRules string) error {
|
||||||
|
if !config.Config.IsNsRestrictedMode() {
|
||||||
|
if err := createMizuNamespace(ctx, kubernetesProvider); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := createMizuApiServer(ctx, kubernetesProvider, mizuApiFilteringOptions); err != nil {
|
if err := createMizuApiServer(ctx, kubernetesProvider, mizuApiFilteringOptions); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := updateMizuTappers(ctx, kubernetesProvider, nodeToTappedPodIPMap); err != nil {
|
if err := createMizuConfigmap(ctx, kubernetesProvider, mizuValidationRules); err != nil {
|
||||||
return err
|
logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Failed to create resources required for policy validation. Mizu will not validate policy rules. error: %v\n", errormessage.FormatError(err)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createMizuConfigmap(ctx context.Context, kubernetesProvider *kubernetes.Provider, data string) error {
|
||||||
|
err := kubernetesProvider.CreateConfigMap(ctx, config.Config.MizuResourcesNamespace, mizu.ConfigMapName, data)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func createMizuNamespace(ctx context.Context, kubernetesProvider *kubernetes.Provider) error {
|
func createMizuNamespace(ctx context.Context, kubernetesProvider *kubernetes.Provider) error {
|
||||||
_, err := kubernetesProvider.CreateNamespace(ctx, mizu.ResourcesNamespace)
|
_, err := kubernetesProvider.CreateNamespace(ctx, config.Config.MizuResourcesNamespace)
|
||||||
if err != nil {
|
return err
|
||||||
mizu.Log.Infof("Error creating Namespace %s: %v", mizu.ResourcesNamespace, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
mizu.Log.Debugf("Successfully creating Namespace %s", mizu.ResourcesNamespace)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createMizuApiServer(ctx context.Context, kubernetesProvider *kubernetes.Provider, mizuApiFilteringOptions *shared.TrafficFilteringOptions) error {
|
func createMizuApiServer(ctx context.Context, kubernetesProvider *kubernetes.Provider, mizuApiFilteringOptions *api.TrafficFilteringOptions) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
mizuServiceAccountExists = createRBACIfNecessary(ctx, kubernetesProvider)
|
state.mizuServiceAccountExists, err = createRBACIfNecessary(ctx, kubernetesProvider)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Warningf(uiUtils.Warning, fmt.Sprintf("Failed to ensure the resources required for IP resolving. Mizu will not resolve target IPs to names. error: %v", errormessage.FormatError(err)))
|
||||||
|
}
|
||||||
|
|
||||||
var serviceAccountName string
|
var serviceAccountName string
|
||||||
if mizuServiceAccountExists {
|
if state.mizuServiceAccountExists {
|
||||||
serviceAccountName = mizu.ServiceAccountName
|
serviceAccountName = mizu.ServiceAccountName
|
||||||
} else {
|
} else {
|
||||||
serviceAccountName = ""
|
serviceAccountName = ""
|
||||||
}
|
}
|
||||||
_, err = kubernetesProvider.CreateMizuApiServerPod(ctx, mizu.ResourcesNamespace, mizu.ApiServerPodName, mizu.Config.MizuImage, serviceAccountName, mizuApiFilteringOptions, mizu.Config.Tap.MaxEntriesDBSizeBytes())
|
|
||||||
if err != nil {
|
|
||||||
mizu.Log.Infof("Error creating mizu %s pod: %v", mizu.ApiServerPodName, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
mizu.Log.Debugf("Successfully created API server pod: %s", mizu.ApiServerPodName)
|
|
||||||
|
|
||||||
apiServerService, err = kubernetesProvider.CreateService(ctx, mizu.ResourcesNamespace, mizu.ApiServerPodName, mizu.ApiServerPodName)
|
opts := &kubernetes.ApiServerOptions{
|
||||||
|
Namespace: config.Config.MizuResourcesNamespace,
|
||||||
|
PodName: mizu.ApiServerPodName,
|
||||||
|
PodImage: config.Config.AgentImage,
|
||||||
|
ServiceAccountName: serviceAccountName,
|
||||||
|
IsNamespaceRestricted: config.Config.IsNsRestrictedMode(),
|
||||||
|
MizuApiFilteringOptions: mizuApiFilteringOptions,
|
||||||
|
MaxEntriesDBSizeBytes: config.Config.Tap.MaxEntriesDBSizeBytes(),
|
||||||
|
Resources: config.Config.Tap.ApiServerResources,
|
||||||
|
ImagePullPolicy: config.Config.ImagePullPolicy(),
|
||||||
|
}
|
||||||
|
_, err = kubernetesProvider.CreateMizuApiServerPod(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mizu.Log.Infof("Error creating mizu %s service: %v", mizu.ApiServerPodName, err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
mizu.Log.Debugf("Successfully created service: %s", mizu.ApiServerPodName)
|
logger.Log.Debugf("Successfully created API server pod: %s", mizu.ApiServerPodName)
|
||||||
|
|
||||||
|
state.apiServerService, err = kubernetesProvider.CreateService(ctx, config.Config.MizuResourcesNamespace, mizu.ApiServerPodName, mizu.ApiServerPodName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Log.Debugf("Successfully created service: %s", mizu.ApiServerPodName)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMizuApiFilteringOptions() (*shared.TrafficFilteringOptions, error) {
|
func getMizuApiFilteringOptions() (*api.TrafficFilteringOptions, error) {
|
||||||
|
var compiledRegexSlice []*api.SerializableRegexp
|
||||||
|
|
||||||
var compiledRegexSlice []*shared.SerializableRegexp
|
if config.Config.Tap.PlainTextFilterRegexes != nil && len(config.Config.Tap.PlainTextFilterRegexes) > 0 {
|
||||||
|
compiledRegexSlice = make([]*api.SerializableRegexp, 0)
|
||||||
if mizu.Config.Tap.PlainTextFilterRegexes != nil && len(mizu.Config.Tap.PlainTextFilterRegexes) > 0 {
|
for _, regexStr := range config.Config.Tap.PlainTextFilterRegexes {
|
||||||
compiledRegexSlice = make([]*shared.SerializableRegexp, 0)
|
compiledRegex, err := api.CompileRegexToSerializableRegexp(regexStr)
|
||||||
for _, regexStr := range mizu.Config.Tap.PlainTextFilterRegexes {
|
|
||||||
compiledRegex, err := shared.CompileRegexToSerializableRegexp(regexStr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mizu.Log.Infof("Regex %s is invalid: %v", regexStr, err)
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
compiledRegexSlice = append(compiledRegexSlice, compiledRegex)
|
compiledRegexSlice = append(compiledRegexSlice, compiledRegex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &shared.TrafficFilteringOptions{PlainTextMaskingRegexes: compiledRegexSlice, HideHealthChecks: mizu.Config.Tap.HideHealthChecks, DisableRedaction: mizu.Config.Tap.DisableRedaction}, nil
|
return &api.TrafficFilteringOptions{
|
||||||
|
PlainTextMaskingRegexes: compiledRegexSlice,
|
||||||
|
HealthChecksUserAgentHeaders: config.Config.Tap.HealthChecksUserAgentHeaders,
|
||||||
|
DisableRedaction: config.Config.Tap.DisableRedaction,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provider, nodeToTappedPodIPMap map[string][]string) error {
|
func updateMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provider, mizuApiFilteringOptions *api.TrafficFilteringOptions) error {
|
||||||
|
nodeToTappedPodIPMap := getNodeHostToTappedPodIpsMap(state.currentlyTappedPods)
|
||||||
|
|
||||||
if len(nodeToTappedPodIPMap) > 0 {
|
if len(nodeToTappedPodIPMap) > 0 {
|
||||||
var serviceAccountName string
|
var serviceAccountName string
|
||||||
if mizuServiceAccountExists {
|
if state.mizuServiceAccountExists {
|
||||||
serviceAccountName = mizu.ServiceAccountName
|
serviceAccountName = mizu.ServiceAccountName
|
||||||
} else {
|
} else {
|
||||||
serviceAccountName = ""
|
serviceAccountName = ""
|
||||||
@@ -181,22 +235,22 @@ func updateMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provi
|
|||||||
|
|
||||||
if err := kubernetesProvider.ApplyMizuTapperDaemonSet(
|
if err := kubernetesProvider.ApplyMizuTapperDaemonSet(
|
||||||
ctx,
|
ctx,
|
||||||
mizu.ResourcesNamespace,
|
config.Config.MizuResourcesNamespace,
|
||||||
mizu.TapperDaemonSetName,
|
mizu.TapperDaemonSetName,
|
||||||
mizu.Config.MizuImage,
|
config.Config.AgentImage,
|
||||||
mizu.TapperPodName,
|
mizu.TapperPodName,
|
||||||
fmt.Sprintf("%s.%s.svc.cluster.local", apiServerService.Name, apiServerService.Namespace),
|
fmt.Sprintf("%s.%s.svc.cluster.local", state.apiServerService.Name, state.apiServerService.Namespace),
|
||||||
nodeToTappedPodIPMap,
|
nodeToTappedPodIPMap,
|
||||||
serviceAccountName,
|
serviceAccountName,
|
||||||
mizu.Config.Tap.TapOutgoing(),
|
config.Config.Tap.TapperResources,
|
||||||
|
config.Config.ImagePullPolicy(),
|
||||||
|
mizuApiFilteringOptions,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
mizu.Log.Infof("Error creating mizu tapper daemonset: %v", err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
mizu.Log.Debugf("Successfully created %v tappers", len(nodeToTappedPodIPMap))
|
logger.Log.Debugf("Successfully created %v tappers", len(nodeToTappedPodIPMap))
|
||||||
} else {
|
} else {
|
||||||
if err := kubernetesProvider.RemoveDaemonSet(ctx, mizu.ResourcesNamespace, mizu.TapperDaemonSetName); err != nil {
|
if err := kubernetesProvider.RemoveDaemonSet(ctx, config.Config.MizuResourcesNamespace, mizu.TapperDaemonSetName); err != nil {
|
||||||
mizu.Log.Errorf("Error deleting mizu tapper daemonset: %v", err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,90 +258,153 @@ func updateMizuTappers(ctx context.Context, kubernetesProvider *kubernetes.Provi
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanUpMizuResources(kubernetesProvider *kubernetes.Provider) {
|
func finishMizuExecution(kubernetesProvider *kubernetes.Provider) {
|
||||||
mizu.Log.Infof("\nRemoving mizu resources\n")
|
telemetry.ReportAPICalls()
|
||||||
|
|
||||||
removalCtx, cancel := context.WithTimeout(context.Background(), cleanupTimeout)
|
removalCtx, cancel := context.WithTimeout(context.Background(), cleanupTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
dumpLogsIfNeeded(removalCtx, kubernetesProvider)
|
||||||
|
cleanUpMizuResources(removalCtx, cancel, kubernetesProvider)
|
||||||
|
}
|
||||||
|
|
||||||
if err := kubernetesProvider.RemoveNamespace(removalCtx, mizu.ResourcesNamespace); err != nil {
|
func dumpLogsIfNeeded(ctx context.Context, kubernetesProvider *kubernetes.Provider) {
|
||||||
mizu.Log.Infof("Error removing Namespace %s: %s (%v,%+v)", mizu.ResourcesNamespace, err, err, err)
|
if !config.Config.DumpLogs {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
mizuDir := mizu.GetMizuFolderPath()
|
||||||
|
filePath := path.Join(mizuDir, fmt.Sprintf("mizu_logs_%s.zip", time.Now().Format("2006_01_02__15_04_05")))
|
||||||
|
if err := fsUtils.DumpLogs(ctx, kubernetesProvider, filePath); err != nil {
|
||||||
|
logger.Log.Errorf("Failed dump logs %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if mizuServiceAccountExists {
|
func cleanUpMizuResources(ctx context.Context, cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider) {
|
||||||
if err := kubernetesProvider.RemoveNonNamespacedResources(removalCtx, mizu.ClusterRoleName, mizu.ClusterRoleBindingName); err != nil {
|
logger.Log.Infof("\nRemoving mizu resources\n")
|
||||||
mizu.Log.Infof("Error removing non-namespaced resources: %s (%v,%+v)", err, err, err)
|
|
||||||
return
|
var leftoverResources []string
|
||||||
}
|
|
||||||
|
if config.Config.IsNsRestrictedMode() {
|
||||||
|
leftoverResources = cleanUpRestrictedMode(ctx, kubernetesProvider)
|
||||||
|
} else {
|
||||||
|
leftoverResources = cleanUpNonRestrictedMode(ctx, cancel, kubernetesProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(leftoverResources) > 0 {
|
||||||
|
errMsg := fmt.Sprintf("Failed to remove the following resources, for more info check logs at %s:", logger.GetLogFilePath())
|
||||||
|
for _, resource := range leftoverResources {
|
||||||
|
errMsg += "\n- " + resource
|
||||||
|
}
|
||||||
|
logger.Log.Errorf(uiUtils.Error, errMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanUpRestrictedMode(ctx context.Context, kubernetesProvider *kubernetes.Provider) []string {
|
||||||
|
leftoverResources := make([]string, 0)
|
||||||
|
|
||||||
|
if err := kubernetesProvider.RemovePod(ctx, config.Config.MizuResourcesNamespace, mizu.ApiServerPodName); err != nil {
|
||||||
|
resourceDesc := fmt.Sprintf("Pod %s in namespace %s", mizu.ApiServerPodName, config.Config.MizuResourcesNamespace)
|
||||||
|
handleDeletionError(err, resourceDesc, &leftoverResources)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := kubernetesProvider.RemoveService(ctx, config.Config.MizuResourcesNamespace, mizu.ApiServerPodName); err != nil {
|
||||||
|
resourceDesc := fmt.Sprintf("Service %s in namespace %s", mizu.ApiServerPodName, config.Config.MizuResourcesNamespace)
|
||||||
|
handleDeletionError(err, resourceDesc, &leftoverResources)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := kubernetesProvider.RemoveDaemonSet(ctx, config.Config.MizuResourcesNamespace, mizu.TapperDaemonSetName); err != nil {
|
||||||
|
resourceDesc := fmt.Sprintf("DaemonSet %s in namespace %s", mizu.TapperDaemonSetName, config.Config.MizuResourcesNamespace)
|
||||||
|
handleDeletionError(err, resourceDesc, &leftoverResources)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := kubernetesProvider.RemoveConfigMap(ctx, config.Config.MizuResourcesNamespace, mizu.ConfigMapName); err != nil {
|
||||||
|
resourceDesc := fmt.Sprintf("ConfigMap %s in namespace %s", mizu.ConfigMapName, config.Config.MizuResourcesNamespace)
|
||||||
|
handleDeletionError(err, resourceDesc, &leftoverResources)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := kubernetesProvider.RemoveServicAccount(ctx, config.Config.MizuResourcesNamespace, mizu.ServiceAccountName); err != nil {
|
||||||
|
resourceDesc := fmt.Sprintf("Service Account %s in namespace %s", mizu.ServiceAccountName, config.Config.MizuResourcesNamespace)
|
||||||
|
handleDeletionError(err, resourceDesc, &leftoverResources)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := kubernetesProvider.RemoveRole(ctx, config.Config.MizuResourcesNamespace, mizu.RoleName); err != nil {
|
||||||
|
resourceDesc := fmt.Sprintf("Role %s in namespace %s", mizu.RoleName, config.Config.MizuResourcesNamespace)
|
||||||
|
handleDeletionError(err, resourceDesc, &leftoverResources)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := kubernetesProvider.RemoveRoleBinding(ctx, config.Config.MizuResourcesNamespace, mizu.RoleBindingName); err != nil {
|
||||||
|
resourceDesc := fmt.Sprintf("RoleBinding %s in namespace %s", mizu.RoleBindingName, config.Config.MizuResourcesNamespace)
|
||||||
|
handleDeletionError(err, resourceDesc, &leftoverResources)
|
||||||
|
}
|
||||||
|
|
||||||
|
return leftoverResources
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanUpNonRestrictedMode(ctx context.Context, cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider) []string {
|
||||||
|
leftoverResources := make([]string, 0)
|
||||||
|
|
||||||
|
if err := kubernetesProvider.RemoveNamespace(ctx, config.Config.MizuResourcesNamespace); err != nil {
|
||||||
|
resourceDesc := fmt.Sprintf("Namespace %s", config.Config.MizuResourcesNamespace)
|
||||||
|
handleDeletionError(err, resourceDesc, &leftoverResources)
|
||||||
|
} else {
|
||||||
|
defer waitUntilNamespaceDeleted(ctx, cancel, kubernetesProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := kubernetesProvider.RemoveClusterRole(ctx, mizu.ClusterRoleName); err != nil {
|
||||||
|
resourceDesc := fmt.Sprintf("ClusterRole %s", mizu.ClusterRoleName)
|
||||||
|
handleDeletionError(err, resourceDesc, &leftoverResources)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := kubernetesProvider.RemoveClusterRoleBinding(ctx, mizu.ClusterRoleBindingName); err != nil {
|
||||||
|
resourceDesc := fmt.Sprintf("ClusterRoleBinding %s", mizu.ClusterRoleBindingName)
|
||||||
|
handleDeletionError(err, resourceDesc, &leftoverResources)
|
||||||
|
}
|
||||||
|
|
||||||
|
return leftoverResources
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleDeletionError(err error, resourceDesc string, leftoverResources *[]string) {
|
||||||
|
logger.Log.Debugf("Error removing %s: %v", resourceDesc, errormessage.FormatError(err))
|
||||||
|
*leftoverResources = append(*leftoverResources, resourceDesc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitUntilNamespaceDeleted(ctx context.Context, cancel context.CancelFunc, kubernetesProvider *kubernetes.Provider) {
|
||||||
// Call cancel if a terminating signal was received. Allows user to skip the wait.
|
// Call cancel if a terminating signal was received. Allows user to skip the wait.
|
||||||
go func() {
|
go func() {
|
||||||
waitForFinish(removalCtx, cancel)
|
waitForFinish(ctx, cancel)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := kubernetesProvider.WaitUtilNamespaceDeleted(removalCtx, mizu.ResourcesNamespace); err != nil {
|
if err := kubernetesProvider.WaitUtilNamespaceDeleted(ctx, config.Config.MizuResourcesNamespace); err != nil {
|
||||||
switch {
|
switch {
|
||||||
case removalCtx.Err() == context.Canceled:
|
case ctx.Err() == context.Canceled:
|
||||||
// Do nothing. User interrupted the wait.
|
logger.Log.Debugf("Do nothing. User interrupted the wait")
|
||||||
case err == wait.ErrWaitTimeout:
|
case err == wait.ErrWaitTimeout:
|
||||||
mizu.Log.Infof("Timeout while removing Namespace %s", mizu.ResourcesNamespace)
|
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Timeout while removing Namespace %s", config.Config.MizuResourcesNamespace))
|
||||||
default:
|
default:
|
||||||
mizu.Log.Infof("Error while waiting for Namespace %s to be deleted: %s (%v,%+v)", mizu.ResourcesNamespace, err, err, err)
|
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error while waiting for Namespace %s to be deleted: %v", config.Config.MizuResourcesNamespace, errormessage.FormatError(err)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func reportTappedPods() {
|
func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Provider, targetNamespaces []string, cancel context.CancelFunc, mizuApiFilteringOptions *api.TrafficFilteringOptions) {
|
||||||
mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Fetch.MizuPort)
|
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider, targetNamespaces, config.Config.Tap.PodRegex())
|
||||||
tappedPodsUrl := fmt.Sprintf("http://%s/status/tappedPods", mizuProxiedUrl)
|
|
||||||
|
|
||||||
podInfos := make([]shared.PodInfo, 0)
|
|
||||||
for _, pod := range currentlyTappedPods {
|
|
||||||
podInfos = append(podInfos, shared.PodInfo{Name: pod.Name, Namespace: pod.Namespace})
|
|
||||||
}
|
|
||||||
tapStatus := shared.TapStatus{Pods: podInfos}
|
|
||||||
|
|
||||||
if jsonValue, err := json.Marshal(tapStatus); err != nil {
|
|
||||||
mizu.Log.Debugf("[ERROR] failed Marshal the tapped pods %v", err)
|
|
||||||
} else {
|
|
||||||
if response, err := http.Post(tappedPodsUrl, "application/json", bytes.NewBuffer(jsonValue)); err != nil {
|
|
||||||
mizu.Log.Debugf("[ERROR] failed sending to API server the tapped pods %v", err)
|
|
||||||
} else if response.StatusCode != 200 {
|
|
||||||
mizu.Log.Debugf("[ERROR] failed sending to API server the tapped pods, response status code %v", response.StatusCode)
|
|
||||||
} else {
|
|
||||||
mizu.Log.Debugf("Reported to server API about %d taped pods successfully", len(podInfos))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
|
|
||||||
targetNamespace := getNamespace(kubernetesProvider)
|
|
||||||
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider.GetPodWatcher(ctx, targetNamespace), mizu.Config.Tap.PodRegex())
|
|
||||||
|
|
||||||
restartTappers := func() {
|
restartTappers := func() {
|
||||||
err, changeFound := updateCurrentlyTappedPods(kubernetesProvider, ctx, targetNamespace)
|
err, changeFound := updateCurrentlyTappedPods(kubernetesProvider, ctx, targetNamespaces)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mizu.Log.Errorf("Error getting pods by regex: %s (%v,%+v)", err, err, err)
|
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Failed to update currently tapped pods: %v", err))
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !changeFound {
|
if !changeFound {
|
||||||
mizu.Log.Debugf("Nothing changed update tappers not needed")
|
logger.Log.Debugf("Nothing changed update tappers not needed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
reportTappedPods()
|
if err := apiserver.Provider.ReportTappedPods(state.currentlyTappedPods); err != nil {
|
||||||
|
logger.Log.Debugf("[Error] failed update tapped pods %v", err)
|
||||||
nodeToTappedPodIPMap, err := getNodeHostToTappedPodIpsMap(currentlyTappedPods)
|
|
||||||
if err != nil {
|
|
||||||
mizu.Log.Errorf("Error building node to ips map: %s (%v,%+v)", err, err, err)
|
|
||||||
cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := updateMizuTappers(ctx, kubernetesProvider, nodeToTappedPodIPMap); err != nil {
|
if err := updateMizuTappers(ctx, kubernetesProvider, mizuApiFilteringOptions); err != nil {
|
||||||
mizu.Log.Errorf("Error updating daemonset: %s (%v,%+v)", err, err, err)
|
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error updating tappers: %v", errormessage.FormatError(err)))
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,14 +412,29 @@ func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Pro
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case pod := <-added:
|
case pod, ok := <-added:
|
||||||
mizu.Log.Debugf("Added matching pod %s, ns: %s", pod.Name, pod.Namespace)
|
if !ok {
|
||||||
|
added = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Debugf("Added matching pod %s, ns: %s", pod.Name, pod.Namespace)
|
||||||
restartTappersDebouncer.SetOn()
|
restartTappersDebouncer.SetOn()
|
||||||
case pod := <-removed:
|
case pod, ok := <-removed:
|
||||||
mizu.Log.Debugf("Removed matching pod %s, ns: %s", pod.Name, pod.Namespace)
|
if !ok {
|
||||||
|
removed = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Debugf("Removed matching pod %s, ns: %s", pod.Name, pod.Namespace)
|
||||||
restartTappersDebouncer.SetOn()
|
restartTappersDebouncer.SetOn()
|
||||||
case pod := <-modified:
|
case pod, ok := <-modified:
|
||||||
mizu.Log.Debugf("Modified matching pod %s, ns: %s, phase: %s, ip: %s", pod.Name, pod.Namespace, pod.Status.Phase, pod.Status.PodIP)
|
if !ok {
|
||||||
|
modified = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Debugf("Modified matching pod %s, ns: %s, phase: %s, ip: %s", pod.Name, pod.Namespace, pod.Status.Phase, pod.Status.PodIP)
|
||||||
// Act only if the modified pod has already obtained an IP address.
|
// Act only if the modified pod has already obtained an IP address.
|
||||||
// After filtering for IPs, on a normal pod restart this includes the following events:
|
// After filtering for IPs, on a normal pod restart this includes the following events:
|
||||||
// - Pod deletion
|
// - Pod deletion
|
||||||
@@ -312,38 +444,59 @@ func watchPodsForTapping(ctx context.Context, kubernetesProvider *kubernetes.Pro
|
|||||||
if pod.Status.PodIP != "" {
|
if pod.Status.PodIP != "" {
|
||||||
restartTappersDebouncer.SetOn()
|
restartTappersDebouncer.SetOn()
|
||||||
}
|
}
|
||||||
|
case err, ok := <-errorChan:
|
||||||
|
if !ok {
|
||||||
|
errorChan = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
case <-errorChan:
|
logger.Log.Debugf("Watching pods loop, got error %v, stopping `restart tappers debouncer`", err)
|
||||||
|
restartTappersDebouncer.Cancel()
|
||||||
// TODO: Does this also perform cleanup?
|
// TODO: Does this also perform cleanup?
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
logger.Log.Debugf("Watching pods loop, context done, stopping `restart tappers debouncer`")
|
||||||
|
restartTappersDebouncer.Cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateCurrentlyTappedPods(kubernetesProvider *kubernetes.Provider, ctx context.Context, targetNamespace string) (error, bool) {
|
func updateCurrentlyTappedPods(kubernetesProvider *kubernetes.Provider, ctx context.Context, targetNamespaces []string) (error, bool) {
|
||||||
changeFound := false
|
changeFound := false
|
||||||
if matchingPods, err := kubernetesProvider.GetAllRunningPodsMatchingRegex(ctx, mizu.Config.Tap.PodRegex(), targetNamespace); err != nil {
|
if matchingPods, err := kubernetesProvider.ListAllRunningPodsMatchingRegex(ctx, config.Config.Tap.PodRegex(), targetNamespaces); err != nil {
|
||||||
mizu.Log.Infof("Error getting pods by regex: %s (%v,%+v)", err, err, err)
|
|
||||||
return err, false
|
return err, false
|
||||||
} else {
|
} else {
|
||||||
addedPods, removedPods := getPodArrayDiff(currentlyTappedPods, matchingPods)
|
podsToTap := excludeMizuPods(matchingPods)
|
||||||
|
addedPods, removedPods := getPodArrayDiff(state.currentlyTappedPods, podsToTap)
|
||||||
for _, addedPod := range addedPods {
|
for _, addedPod := range addedPods {
|
||||||
changeFound = true
|
changeFound = true
|
||||||
mizu.Log.Infof(uiUtils.Green, fmt.Sprintf("+%s", addedPod.Name))
|
logger.Log.Infof(uiUtils.Green, fmt.Sprintf("+%s", addedPod.Name))
|
||||||
}
|
}
|
||||||
for _, removedPod := range removedPods {
|
for _, removedPod := range removedPods {
|
||||||
changeFound = true
|
changeFound = true
|
||||||
mizu.Log.Infof(uiUtils.Red, fmt.Sprintf("-%s", removedPod.Name))
|
logger.Log.Infof(uiUtils.Red, fmt.Sprintf("-%s", removedPod.Name))
|
||||||
}
|
}
|
||||||
currentlyTappedPods = matchingPods
|
state.currentlyTappedPods = podsToTap
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, changeFound
|
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) {
|
func getPodArrayDiff(oldPods []core.Pod, newPods []core.Pod) (added []core.Pod, removed []core.Pod) {
|
||||||
added = getMissingPods(newPods, oldPods)
|
added = getMissingPods(newPods, oldPods)
|
||||||
removed = getMissingPods(oldPods, newPods)
|
removed = getMissingPods(oldPods, newPods)
|
||||||
@@ -369,89 +522,187 @@ func getMissingPods(pods1 []core.Pod, pods2 []core.Pod) []core.Pod {
|
|||||||
return missingPods
|
return missingPods
|
||||||
}
|
}
|
||||||
|
|
||||||
func createProxyToApiServerPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
|
func watchApiServerPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc, mizuApiFilteringOptions *api.TrafficFilteringOptions) {
|
||||||
podExactRegex := regexp.MustCompile(fmt.Sprintf("^%s$", mizu.ApiServerPodName))
|
podExactRegex := regexp.MustCompile(fmt.Sprintf("^%s$", mizu.ApiServerPodName))
|
||||||
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider.GetPodWatcher(ctx, mizu.ResourcesNamespace), podExactRegex)
|
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider, []string{config.Config.MizuResourcesNamespace}, podExactRegex)
|
||||||
isPodReady := false
|
isPodReady := false
|
||||||
timeAfter := time.After(25 * time.Second)
|
timeAfter := time.After(25 * time.Second)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case _, ok := <-added:
|
||||||
return
|
if !ok {
|
||||||
case <-added:
|
added = nil
|
||||||
mizu.Log.Debugf("Got agent pod added event")
|
continue
|
||||||
continue
|
}
|
||||||
case <-removed:
|
|
||||||
mizu.Log.Infof("%s removed", mizu.ApiServerPodName)
|
logger.Log.Debugf("Watching API Server pod loop, added")
|
||||||
|
case _, ok := <-removed:
|
||||||
|
if !ok {
|
||||||
|
removed = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Infof("%s removed", mizu.ApiServerPodName)
|
||||||
cancel()
|
cancel()
|
||||||
return
|
return
|
||||||
case modifiedPod := <-modified:
|
case modifiedPod, ok := <-modified:
|
||||||
mizu.Log.Debugf("Got agent pod modified event, status phase: %v", modifiedPod.Status.Phase)
|
if !ok {
|
||||||
|
modified = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Debugf("Watching API Server pod loop, modified: %v", modifiedPod.Status.Phase)
|
||||||
|
|
||||||
|
if modifiedPod.Status.Phase == core.PodPending {
|
||||||
|
if modifiedPod.Status.Conditions[0].Type == core.PodScheduled && modifiedPod.Status.Conditions[0].Status != core.ConditionTrue {
|
||||||
|
logger.Log.Debugf("Wasn't able to deploy the API server. Reason: \"%s\"", modifiedPod.Status.Conditions[0].Message)
|
||||||
|
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Wasn't able to deploy the API server, for more info check logs at %s", logger.GetLogFilePath()))
|
||||||
|
cancel()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(modifiedPod.Status.ContainerStatuses) > 0 && modifiedPod.Status.ContainerStatuses[0].State.Waiting != nil && modifiedPod.Status.ContainerStatuses[0].State.Waiting.Reason == "ErrImagePull" {
|
||||||
|
logger.Log.Debugf("Wasn't able to deploy the API server. (ErrImagePull) Reason: \"%s\"", modifiedPod.Status.ContainerStatuses[0].State.Waiting.Message)
|
||||||
|
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Wasn't able to deploy the API server: failed to pull the image, for more info check logs at %v", logger.GetLogFilePath()))
|
||||||
|
cancel()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if modifiedPod.Status.Phase == core.PodRunning && !isPodReady {
|
if modifiedPod.Status.Phase == core.PodRunning && !isPodReady {
|
||||||
isPodReady = true
|
isPodReady = true
|
||||||
go func() {
|
go startProxyReportErrorIfAny(kubernetesProvider, cancel)
|
||||||
err := kubernetes.StartProxy(kubernetesProvider, mizu.Config.Tap.GuiPort, mizu.ResourcesNamespace, mizu.ApiServerPodName)
|
|
||||||
if err != nil {
|
url := GetApiServerUrl()
|
||||||
mizu.Log.Errorf("Error occurred while running k8s proxy %v", err)
|
if err := apiserver.Provider.InitAndTestConnection(url); err != nil {
|
||||||
cancel()
|
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Couldn't connect to API server, for more info check logs at %s", logger.GetLogFilePath()))
|
||||||
}
|
cancel()
|
||||||
}()
|
break
|
||||||
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
|
if err := updateMizuTappers(ctx, kubernetesProvider, mizuApiFilteringOptions); err != nil {
|
||||||
requestForAnalysis()
|
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error updating tappers: %v", errormessage.FormatError(err)))
|
||||||
reportTappedPods()
|
cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Infof("Mizu is available at %s\n", url)
|
||||||
|
openBrowser(url)
|
||||||
|
requestForAnalysisIfNeeded()
|
||||||
|
if err := apiserver.Provider.ReportTappedPods(state.currentlyTappedPods); err != nil {
|
||||||
|
logger.Log.Debugf("[Error] failed update tapped pods %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
case err, ok := <-errorChan:
|
||||||
|
if !ok {
|
||||||
|
errorChan = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Debugf("[ERROR] Agent creation, watching %v namespace, error: %v", config.Config.MizuResourcesNamespace, err)
|
||||||
|
cancel()
|
||||||
|
|
||||||
case <-timeAfter:
|
case <-timeAfter:
|
||||||
if !isPodReady {
|
if !isPodReady {
|
||||||
mizu.Log.Errorf("Error: %s pod was not ready in time", mizu.ApiServerPodName)
|
logger.Log.Errorf(uiUtils.Error, "Mizu API server was not ready in time")
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
case <-errorChan:
|
case <-ctx.Done():
|
||||||
mizu.Log.Debugf("[ERROR] Agent creation, watching %v namespace", mizu.ResourcesNamespace)
|
logger.Log.Debugf("Watching API Server pod loop, ctx done")
|
||||||
cancel()
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestForAnalysis() {
|
func watchTapperPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
|
||||||
if !mizu.Config.Tap.Analysis {
|
podExactRegex := regexp.MustCompile(fmt.Sprintf("^%s.*", mizu.TapperDaemonSetName))
|
||||||
|
added, modified, removed, errorChan := kubernetes.FilteredWatch(ctx, kubernetesProvider, []string{config.Config.MizuResourcesNamespace}, podExactRegex)
|
||||||
|
var prevPodPhase core.PodPhase
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case addedPod, ok := <-added:
|
||||||
|
if !ok {
|
||||||
|
added = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Debugf("Tapper is created [%s]", addedPod.Name)
|
||||||
|
case removedPod, ok := <-removed:
|
||||||
|
if !ok {
|
||||||
|
removed = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Debugf("Tapper is removed [%s]", removedPod.Name)
|
||||||
|
case modifiedPod, ok := <-modified:
|
||||||
|
if !ok {
|
||||||
|
modified = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if modifiedPod.Status.Phase == core.PodPending && modifiedPod.Status.Conditions[0].Type == core.PodScheduled && modifiedPod.Status.Conditions[0].Status != core.ConditionTrue {
|
||||||
|
logger.Log.Infof(uiUtils.Red, "Wasn't able to deploy the tapper %s. Reason: \"%s\"", modifiedPod.Name, modifiedPod.Status.Conditions[0].Message)
|
||||||
|
cancel()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
podStatus := modifiedPod.Status
|
||||||
|
if podStatus.Phase == core.PodPending && prevPodPhase == podStatus.Phase {
|
||||||
|
logger.Log.Debugf("Tapper %s is %s", modifiedPod.Name, strings.ToLower(string(podStatus.Phase)))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
prevPodPhase = podStatus.Phase
|
||||||
|
|
||||||
|
if podStatus.Phase == core.PodRunning {
|
||||||
|
state := podStatus.ContainerStatuses[0].State
|
||||||
|
if state.Terminated != nil {
|
||||||
|
switch state.Terminated.Reason {
|
||||||
|
case "OOMKilled":
|
||||||
|
logger.Log.Infof(uiUtils.Red, "Tapper %s was terminated (reason: OOMKilled). You should consider increasing machine resources.", modifiedPod.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Debugf("Tapper %s is %s", modifiedPod.Name, strings.ToLower(string(podStatus.Phase)))
|
||||||
|
case err, ok := <-errorChan:
|
||||||
|
if !ok {
|
||||||
|
errorChan = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Debugf("[Error] Error in mizu tapper watch, err: %v", err)
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
logger.Log.Debugf("Watching tapper pod loop, ctx done")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestForAnalysisIfNeeded() {
|
||||||
|
if !config.Config.Tap.Analysis {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err := apiserver.Provider.RequestAnalysis(config.Config.Tap.AnalysisDestination, config.Config.Tap.SleepIntervalSec); err != nil {
|
||||||
mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.Tap.GuiPort)
|
logger.Log.Debugf("[Error] failed requesting for analysis %v", err)
|
||||||
urlPath := fmt.Sprintf("http://%s/api/uploadEntries?dest=%s&interval=%v", mizuProxiedUrl, url.QueryEscape(mizu.Config.Tap.AnalysisDestination), mizu.Config.Tap.SleepIntervalSec)
|
|
||||||
u, parseErr := url.ParseRequestURI(urlPath)
|
|
||||||
if parseErr != nil {
|
|
||||||
mizu.Log.Fatal("Failed parsing the URL (consider changing the analysis dest URL), err: %v", parseErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
mizu.Log.Debugf("Sending get request to %v", u.String())
|
|
||||||
if response, requestErr := http.Get(u.String()); requestErr != nil {
|
|
||||||
mizu.Log.Errorf("Failed to notify agent for analysis, err: %v", requestErr)
|
|
||||||
} else if response.StatusCode != 200 {
|
|
||||||
mizu.Log.Errorf("Failed to notify agent for analysis, status code: %v", response.StatusCode)
|
|
||||||
} else {
|
|
||||||
mizu.Log.Infof(uiUtils.Purple, "Traffic is uploading to UP9 for further analysis")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createRBACIfNecessary(ctx context.Context, kubernetesProvider *kubernetes.Provider) bool {
|
func createRBACIfNecessary(ctx context.Context, kubernetesProvider *kubernetes.Provider) (bool, error) {
|
||||||
mizuRBACExists, err := kubernetesProvider.DoesServiceAccountExist(ctx, mizu.ResourcesNamespace, mizu.ServiceAccountName)
|
if !config.Config.IsNsRestrictedMode() {
|
||||||
if err != nil {
|
err := kubernetesProvider.CreateMizuRBAC(ctx, config.Config.MizuResourcesNamespace, mizu.ServiceAccountName, mizu.ClusterRoleName, mizu.ClusterRoleBindingName, mizu.RBACVersion)
|
||||||
mizu.Log.Infof("warning: could not ensure mizu rbac resources exist %v", err)
|
if err != nil {
|
||||||
return false
|
return false, err
|
||||||
}
|
}
|
||||||
if !mizuRBACExists {
|
} else {
|
||||||
err := kubernetesProvider.CreateMizuRBAC(ctx, mizu.ResourcesNamespace, mizu.ServiceAccountName, mizu.ClusterRoleName, mizu.ClusterRoleBindingName, mizu.RBACVersion)
|
err := kubernetesProvider.CreateMizuRBACNamespaceRestricted(ctx, config.Config.MizuResourcesNamespace, mizu.ServiceAccountName, mizu.RoleName, mizu.RoleBindingName, mizu.RBACVersion)
|
||||||
if err != nil && !errors.IsAlreadyExists(err) {
|
if err != nil {
|
||||||
mizu.Log.Infof("warning: could not create mizu rbac resources %v", err)
|
return false, err
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNodeHostToTappedPodIpsMap(tappedPods []core.Pod) (map[string][]string, error) {
|
func getNodeHostToTappedPodIpsMap(tappedPods []core.Pod) map[string][]string {
|
||||||
nodeToTappedPodIPMap := make(map[string][]string, 0)
|
nodeToTappedPodIPMap := make(map[string][]string, 0)
|
||||||
for _, pod := range tappedPods {
|
for _, pod := range tappedPods {
|
||||||
existingList := nodeToTappedPodIPMap[pod.Spec.NodeName]
|
existingList := nodeToTappedPodIPMap[pod.Spec.NodeName]
|
||||||
@@ -461,28 +712,15 @@ func getNodeHostToTappedPodIpsMap(tappedPods []core.Pod) (map[string][]string, e
|
|||||||
nodeToTappedPodIPMap[pod.Spec.NodeName] = append(nodeToTappedPodIPMap[pod.Spec.NodeName], pod.Status.PodIP)
|
nodeToTappedPodIPMap[pod.Spec.NodeName] = append(nodeToTappedPodIPMap[pod.Spec.NodeName], pod.Status.PodIP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nodeToTappedPodIPMap, nil
|
return nodeToTappedPodIPMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForFinish(ctx context.Context, cancel context.CancelFunc) {
|
func getNamespaces(kubernetesProvider *kubernetes.Provider) []string {
|
||||||
sigChan := make(chan os.Signal, 1)
|
if config.Config.Tap.AllNamespaces {
|
||||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
return []string{mizu.K8sAllNamespaces}
|
||||||
|
} else if len(config.Config.Tap.Namespaces) > 0 {
|
||||||
// block until ctx cancel is called or termination signal is received
|
return shared.Unique(config.Config.Tap.Namespaces)
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
break
|
|
||||||
case <-sigChan:
|
|
||||||
cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNamespace(kubernetesProvider *kubernetes.Provider) string {
|
|
||||||
if mizu.Config.Tap.AllNamespaces {
|
|
||||||
return mizu.K8sAllNamespaces
|
|
||||||
} else if len(mizu.Config.Tap.Namespace) > 0 {
|
|
||||||
return mizu.Config.Tap.Namespace
|
|
||||||
} else {
|
} else {
|
||||||
return kubernetesProvider.CurrentNamespace()
|
return []string{kubernetesProvider.CurrentNamespace()}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,31 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/up9inc/mizu/cli/config"
|
||||||
|
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||||
|
"github.com/up9inc/mizu/cli/logger"
|
||||||
|
"github.com/up9inc/mizu/cli/telemetry"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/creasty/defaults"
|
"github.com/creasty/defaults"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/up9inc/mizu/cli/mizu"
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
"github.com/up9inc/mizu/cli/mizu/configStructs"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var versionCmd = &cobra.Command{
|
var versionCmd = &cobra.Command{
|
||||||
Use: "version",
|
Use: "version",
|
||||||
Short: "Print version info",
|
Short: "Print version info",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
go mizu.ReportRun("version", mizu.Config.Version)
|
go telemetry.ReportRun("version", config.Config.Version)
|
||||||
if mizu.Config.Version.DebugInfo {
|
|
||||||
|
if config.Config.Version.DebugInfo {
|
||||||
timeStampInt, _ := strconv.ParseInt(mizu.BuildTimestamp, 10, 0)
|
timeStampInt, _ := strconv.ParseInt(mizu.BuildTimestamp, 10, 0)
|
||||||
mizu.Log.Infof("Version: %s \nBranch: %s (%s)", mizu.SemVer, mizu.Branch, mizu.GitCommitHash)
|
logger.Log.Infof("Version: %s \nBranch: %s (%s)", mizu.SemVer, mizu.Branch, mizu.GitCommitHash)
|
||||||
mizu.Log.Infof("Build Time: %s (%s)", mizu.BuildTimestamp, time.Unix(timeStampInt, 0))
|
logger.Log.Infof("Build Time: %s (%s)", mizu.BuildTimestamp, time.Unix(timeStampInt, 0))
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
mizu.Log.Infof("Version: %s (%s)", mizu.SemVer, mizu.Branch)
|
logger.Log.Infof("Version: %s (%s)", mizu.SemVer, mizu.Branch)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,20 +3,16 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"github.com/creasty/defaults"
|
"github.com/creasty/defaults"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/up9inc/mizu/cli/mizu"
|
"github.com/up9inc/mizu/cli/config"
|
||||||
"github.com/up9inc/mizu/cli/mizu/configStructs"
|
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||||
|
"github.com/up9inc/mizu/cli/telemetry"
|
||||||
)
|
)
|
||||||
|
|
||||||
var viewCmd = &cobra.Command{
|
var viewCmd = &cobra.Command{
|
||||||
Use: "view",
|
Use: "view",
|
||||||
Short: "Open GUI in browser",
|
Short: "Open GUI in browser",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
go mizu.ReportRun("view", mizu.Config.View)
|
go telemetry.ReportRun("view", config.Config.View)
|
||||||
if isCompatible, err := mizu.CheckVersionCompatibility(mizu.Config.View.GuiPort); err != nil {
|
|
||||||
return err
|
|
||||||
} else if !isCompatible {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
runMizuView()
|
runMizuView()
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@@ -29,5 +25,7 @@ func init() {
|
|||||||
defaults.Set(&defaultViewConfig)
|
defaults.Set(&defaultViewConfig)
|
||||||
|
|
||||||
viewCmd.Flags().Uint16P(configStructs.GuiPortViewName, "p", defaultViewConfig.GuiPort, "Provide a custom port for the web interface webserver")
|
viewCmd.Flags().Uint16P(configStructs.GuiPortViewName, "p", defaultViewConfig.GuiPort, "Provide a custom port for the web interface webserver")
|
||||||
viewCmd.Flags().StringP(configStructs.KubeConfigPathViewName, "k", defaultViewConfig.KubeConfigPath, "Path to kube-config file")
|
viewCmd.Flags().StringP(configStructs.UrlViewName, "u", defaultViewConfig.Url, "Provide a custom host")
|
||||||
|
|
||||||
|
viewCmd.Flags().MarkHidden(configStructs.UrlViewName)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,49 +3,69 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/up9inc/mizu/cli/kubernetes"
|
|
||||||
"github.com/up9inc/mizu/cli/mizu"
|
|
||||||
"github.com/up9inc/mizu/cli/uiUtils"
|
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/up9inc/mizu/cli/apiserver"
|
||||||
|
"github.com/up9inc/mizu/cli/config"
|
||||||
|
"github.com/up9inc/mizu/cli/kubernetes"
|
||||||
|
"github.com/up9inc/mizu/cli/logger"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu/version"
|
||||||
|
"github.com/up9inc/mizu/cli/uiUtils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runMizuView() {
|
func runMizuView() {
|
||||||
kubernetesProvider, err := kubernetes.NewProvider(mizu.Config.View.KubeConfigPath)
|
kubernetesProvider, err := kubernetes.NewProvider(config.Config.KubeConfigPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if clientcmd.IsEmptyConfig(err) {
|
logger.Log.Error(err)
|
||||||
mizu.Log.Infof("Couldn't find the kube config file, or file is empty. Try adding '--kube-config=<path to kube config file>'")
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
if clientcmd.IsConfigurationInvalid(err) {
|
|
||||||
mizu.Log.Infof(uiUtils.Red, "Invalid kube config file. Try using a different config with '--kube-config=<path to kube config file>'")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
exists, err := kubernetesProvider.DoesServicesExist(ctx, mizu.ResourcesNamespace, mizu.ApiServerPodName)
|
url := config.Config.View.Url
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
if url == "" {
|
||||||
|
exists, err := kubernetesProvider.DoesServicesExist(ctx, config.Config.MizuResourcesNamespace, mizu.ApiServerPodName)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Errorf("Failed to found mizu service %v", err)
|
||||||
|
cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
logger.Log.Infof("%s service not found, you should run `mizu tap` command first", mizu.ApiServerPodName)
|
||||||
|
cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
url = GetApiServerUrl()
|
||||||
|
|
||||||
|
response, err := http.Get(fmt.Sprintf("%s/", url))
|
||||||
|
if err == nil && response.StatusCode == 200 {
|
||||||
|
logger.Log.Infof("Found a running service %s and open port %d", mizu.ApiServerPodName, config.Config.View.GuiPort)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.Log.Infof("Establishing connection to k8s cluster...")
|
||||||
|
go startProxyReportErrorIfAny(kubernetesProvider, cancel)
|
||||||
|
|
||||||
|
if err := apiserver.Provider.InitAndTestConnection(GetApiServerUrl()); err != nil {
|
||||||
|
logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Couldn't connect to API server, for more info check logs at %s", logger.GetLogFilePath()))
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !exists {
|
|
||||||
mizu.Log.Infof("The %s service not found", mizu.ApiServerPodName)
|
logger.Log.Infof("Mizu is available at %s\n", url)
|
||||||
|
|
||||||
|
openBrowser(url)
|
||||||
|
if isCompatible, err := version.CheckVersionCompatibility(); err != nil {
|
||||||
|
logger.Log.Errorf("Failed to check versions compatibility %v", err)
|
||||||
|
cancel()
|
||||||
|
return
|
||||||
|
} else if !isCompatible {
|
||||||
|
cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mizuProxiedUrl := kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.View.GuiPort)
|
waitForFinish(ctx, cancel)
|
||||||
_, err = http.Get(fmt.Sprintf("http://%s/", mizuProxiedUrl))
|
|
||||||
if err == nil {
|
|
||||||
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.Infof("Mizu is available at http://%s\n", kubernetes.GetMizuApiServerProxiedHostAndPath(mizu.Config.View.GuiPort))
|
|
||||||
err = kubernetes.StartProxy(kubernetesProvider, mizu.Config.View.GuiPort, mizu.ResourcesNamespace, mizu.ApiServerPodName)
|
|
||||||
if err != nil {
|
|
||||||
mizu.Log.Infof("Error occured while running k8s proxy %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
329
cli/config/config.go
Normal file
329
cli/config/config.go
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/up9inc/mizu/cli/logger"
|
||||||
|
"github.com/up9inc/mizu/shared"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/creasty/defaults"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
"github.com/up9inc/mizu/cli/uiUtils"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Separator = "="
|
||||||
|
SetCommandName = "set"
|
||||||
|
FieldNameTag = "yaml"
|
||||||
|
ReadonlyTag = "readonly"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Config = ConfigStruct{}
|
||||||
|
cmdName string
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitConfig(cmd *cobra.Command) error {
|
||||||
|
cmdName = cmd.Name()
|
||||||
|
|
||||||
|
if err := defaults.Set(&Config); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
configFilePathFlag := cmd.Flags().Lookup(ConfigFilePathCommandName)
|
||||||
|
configFilePath := configFilePathFlag.Value.String()
|
||||||
|
if err := mergeConfigFile(configFilePath); err != nil {
|
||||||
|
if configFilePathFlag.Changed || !os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("invalid config, %w\n"+
|
||||||
|
"you can regenerate the file by removing it (%v) and using `mizu config -r`", err, configFilePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().Visit(initFlag)
|
||||||
|
|
||||||
|
finalConfigPrettified, _ := uiUtils.PrettyJson(Config)
|
||||||
|
logger.Log.Debugf("Init config finished\n Final config: %v", finalConfigPrettified)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetConfigWithDefaults() (string, error) {
|
||||||
|
defaultConf := ConfigStruct{}
|
||||||
|
if err := defaults.Set(&defaultConf); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
configElem := reflect.ValueOf(&defaultConf).Elem()
|
||||||
|
setZeroForReadonlyFields(configElem)
|
||||||
|
|
||||||
|
return uiUtils.PrettyYaml(defaultConf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeConfigFile(configFilePath string) error {
|
||||||
|
reader, openErr := os.Open(configFilePath)
|
||||||
|
if openErr != nil {
|
||||||
|
return openErr
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, readErr := ioutil.ReadAll(reader)
|
||||||
|
if readErr != nil {
|
||||||
|
return readErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := yaml.Unmarshal(buf, &Config); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Log.Debugf("Found config file, merged to default options")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initFlag(f *pflag.Flag) {
|
||||||
|
configElemValue := reflect.ValueOf(&Config).Elem()
|
||||||
|
|
||||||
|
var flagPath []string
|
||||||
|
if shared.Contains([]string{ConfigFilePathCommandName}, f.Name) {
|
||||||
|
flagPath = []string{f.Name}
|
||||||
|
} else {
|
||||||
|
flagPath = []string{cmdName, f.Name}
|
||||||
|
}
|
||||||
|
|
||||||
|
sliceValue, isSliceValue := f.Value.(pflag.SliceValue)
|
||||||
|
if !isSliceValue {
|
||||||
|
if err := mergeFlagValue(configElemValue, flagPath, strings.Join(flagPath, "."), f.Value.String()); err != nil {
|
||||||
|
logger.Log.Warningf(uiUtils.Warning, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.Name == SetCommandName {
|
||||||
|
if err := mergeSetFlag(configElemValue, sliceValue.GetSlice()); err != nil {
|
||||||
|
logger.Log.Warningf(uiUtils.Warning, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mergeFlagValues(configElemValue, flagPath, strings.Join(flagPath, "."), sliceValue.GetSlice()); err != nil {
|
||||||
|
logger.Log.Warningf(uiUtils.Warning, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeSetFlag(configElemValue reflect.Value, setValues []string) error {
|
||||||
|
var setErrors []string
|
||||||
|
setMap := map[string][]string{}
|
||||||
|
|
||||||
|
for _, setValue := range setValues {
|
||||||
|
if !strings.Contains(setValue, Separator) {
|
||||||
|
setErrors = append(setErrors, fmt.Sprintf("Ignoring set argument %s (set argument format: <flag name>=<flag value>)", setValue))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
split := strings.SplitN(setValue, Separator, 2)
|
||||||
|
argumentKey, argumentValue := split[0], split[1]
|
||||||
|
|
||||||
|
setMap[argumentKey] = append(setMap[argumentKey], argumentValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
for argumentKey, argumentValues := range setMap {
|
||||||
|
flagPath := strings.Split(argumentKey, ".")
|
||||||
|
|
||||||
|
if len(argumentValues) > 1 {
|
||||||
|
if err := mergeFlagValues(configElemValue, flagPath, argumentKey, argumentValues); err != nil {
|
||||||
|
setErrors = append(setErrors, fmt.Sprintf("%v", err))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := mergeFlagValue(configElemValue, flagPath, argumentKey, argumentValues[0]); err != nil {
|
||||||
|
setErrors = append(setErrors, fmt.Sprintf("%v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(setErrors) > 0 {
|
||||||
|
return fmt.Errorf(strings.Join(setErrors, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeFlagValue(configElemValue reflect.Value, flagPath []string, fullFlagName string, flagValue string) error {
|
||||||
|
mergeFunction := func(flagName string, currentFieldStruct reflect.StructField, currentFieldElemValue reflect.Value, currentElemValue reflect.Value) error {
|
||||||
|
currentFieldKind := currentFieldStruct.Type.Kind()
|
||||||
|
|
||||||
|
if currentFieldKind == reflect.Slice {
|
||||||
|
return mergeFlagValues(currentElemValue, []string{flagName}, fullFlagName, []string{flagValue})
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedValue, err := getParsedValue(currentFieldKind, flagValue)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid value %s for flag name %s, expected %s", flagValue, flagName, currentFieldKind)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFieldElemValue.Set(parsedValue)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergeFlag(configElemValue, flagPath, fullFlagName, mergeFunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeFlagValues(configElemValue reflect.Value, flagPath []string, fullFlagName string, flagValues []string) error {
|
||||||
|
mergeFunction := func(flagName string, currentFieldStruct reflect.StructField, currentFieldElemValue reflect.Value, currentElemValue reflect.Value) error {
|
||||||
|
currentFieldKind := currentFieldStruct.Type.Kind()
|
||||||
|
|
||||||
|
if currentFieldKind != reflect.Slice {
|
||||||
|
return fmt.Errorf("invalid values %s for flag name %s, expected %s", strings.Join(flagValues, ","), flagName, currentFieldKind)
|
||||||
|
}
|
||||||
|
|
||||||
|
flagValueKind := currentFieldStruct.Type.Elem().Kind()
|
||||||
|
|
||||||
|
parsedValues := reflect.MakeSlice(reflect.SliceOf(currentFieldStruct.Type.Elem()), 0, 0)
|
||||||
|
for _, flagValue := range flagValues {
|
||||||
|
parsedValue, err := getParsedValue(flagValueKind, flagValue)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid value %s for flag name %s, expected %s", flagValue, flagName, flagValueKind)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedValues = reflect.Append(parsedValues, parsedValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFieldElemValue.Set(parsedValues)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergeFlag(configElemValue, flagPath, fullFlagName, mergeFunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeFlag(currentElemValue reflect.Value, currentFlagPath []string, fullFlagName string, mergeFunction func(flagName string, currentFieldStruct reflect.StructField, currentFieldElemValue reflect.Value, currentElemValue reflect.Value) error) error {
|
||||||
|
if len(currentFlagPath) == 0 {
|
||||||
|
return fmt.Errorf("flag \"%s\" not found", fullFlagName)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < currentElemValue.NumField(); i++ {
|
||||||
|
currentFieldStruct := currentElemValue.Type().Field(i)
|
||||||
|
currentFieldElemValue := currentElemValue.FieldByName(currentFieldStruct.Name)
|
||||||
|
|
||||||
|
if currentFieldStruct.Type.Kind() == reflect.Struct && getFieldNameByTag(currentFieldStruct) == currentFlagPath[0] {
|
||||||
|
return mergeFlag(currentFieldElemValue, currentFlagPath[1:], fullFlagName, mergeFunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(currentFlagPath) > 1 || getFieldNameByTag(currentFieldStruct) != currentFlagPath[0] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergeFunction(currentFlagPath[0], currentFieldStruct, currentFieldElemValue, currentElemValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("flag \"%s\" not found", fullFlagName)
|
||||||
|
}
|
||||||
|
|
||||||
|
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:
|
||||||
|
return reflect.ValueOf(value), nil
|
||||||
|
case reflect.Bool:
|
||||||
|
boolArgumentValue, err := strconv.ParseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(boolArgumentValue), nil
|
||||||
|
case reflect.Int:
|
||||||
|
intArgumentValue, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(int(intArgumentValue)), nil
|
||||||
|
case reflect.Int8:
|
||||||
|
intArgumentValue, err := strconv.ParseInt(value, 10, 8)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(int8(intArgumentValue)), nil
|
||||||
|
case reflect.Int16:
|
||||||
|
intArgumentValue, err := strconv.ParseInt(value, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(int16(intArgumentValue)), nil
|
||||||
|
case reflect.Int32:
|
||||||
|
intArgumentValue, err := strconv.ParseInt(value, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(int32(intArgumentValue)), nil
|
||||||
|
case reflect.Int64:
|
||||||
|
intArgumentValue, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(intArgumentValue), nil
|
||||||
|
case reflect.Uint:
|
||||||
|
uintArgumentValue, err := strconv.ParseUint(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(uint(uintArgumentValue)), nil
|
||||||
|
case reflect.Uint8:
|
||||||
|
uintArgumentValue, err := strconv.ParseUint(value, 10, 8)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(uint8(uintArgumentValue)), nil
|
||||||
|
case reflect.Uint16:
|
||||||
|
uintArgumentValue, err := strconv.ParseUint(value, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(uint16(uintArgumentValue)), nil
|
||||||
|
case reflect.Uint32:
|
||||||
|
uintArgumentValue, err := strconv.ParseUint(value, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(uint32(uintArgumentValue)), nil
|
||||||
|
case reflect.Uint64:
|
||||||
|
uintArgumentValue, err := strconv.ParseUint(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.ValueOf(uintArgumentValue), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
cli/config/configStruct.go
Normal file
59
cli/config/configStruct.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/client-go/util/homedir"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MizuResourcesNamespaceConfigName = "mizu-resources-namespace"
|
||||||
|
ConfigFilePathCommandName = "config-path"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConfigStruct struct {
|
||||||
|
Tap configStructs.TapConfig `yaml:"tap"`
|
||||||
|
Version configStructs.VersionConfig `yaml:"version"`
|
||||||
|
View configStructs.ViewConfig `yaml:"view"`
|
||||||
|
Logs configStructs.LogsConfig `yaml:"logs"`
|
||||||
|
Config configStructs.ConfigConfig `yaml:"config,omitempty"`
|
||||||
|
AgentImage string `yaml:"agent-image,omitempty" readonly:""`
|
||||||
|
ImagePullPolicyStr string `yaml:"image-pull-policy" default:"Always"`
|
||||||
|
MizuResourcesNamespace string `yaml:"mizu-resources-namespace" default:"mizu"`
|
||||||
|
Telemetry bool `yaml:"telemetry" default:"true"`
|
||||||
|
DumpLogs bool `yaml:"dump-logs" default:"false"`
|
||||||
|
KubeConfigPathStr string `yaml:"kube-config-path"`
|
||||||
|
ConfigFilePath string `yaml:"config-path,omitempty" readonly:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *ConfigStruct) SetDefaults() {
|
||||||
|
config.AgentImage = fmt.Sprintf("gcr.io/up9-docker-hub/mizu/%s:%s", mizu.Branch, mizu.SemVer)
|
||||||
|
config.ConfigFilePath = path.Join(mizu.GetMizuFolderPath(), "config.yaml")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *ConfigStruct) ImagePullPolicy() v1.PullPolicy {
|
||||||
|
return v1.PullPolicy(config.ImagePullPolicyStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *ConfigStruct) IsNsRestrictedMode() bool {
|
||||||
|
return config.MizuResourcesNamespace != "mizu" // Notice "mizu" string must match the default MizuResourcesNamespace
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *ConfigStruct) KubeConfigPath() string {
|
||||||
|
if config.KubeConfigPathStr != "" {
|
||||||
|
return config.KubeConfigPathStr
|
||||||
|
}
|
||||||
|
|
||||||
|
envKubeConfigPath := os.Getenv("KUBECONFIG")
|
||||||
|
if envKubeConfigPath != "" {
|
||||||
|
return envKubeConfigPath
|
||||||
|
}
|
||||||
|
|
||||||
|
home := homedir.HomeDir()
|
||||||
|
return filepath.Join(home, ".kube", "config")
|
||||||
|
}
|
||||||
9
cli/config/configStructs/configConfig.go
Normal file
9
cli/config/configStructs/configConfig.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package configStructs
|
||||||
|
|
||||||
|
const (
|
||||||
|
RegenerateConfigName = "regenerate"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConfigConfig struct {
|
||||||
|
Regenerate bool `yaml:"regenerate,omitempty" default:"false" readonly:""`
|
||||||
|
}
|
||||||
35
cli/config/configStructs/logsConfig.go
Normal file
35
cli/config/configStructs/logsConfig.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package configStructs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
FileLogsName = "file"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogsConfig struct {
|
||||||
|
FileStr string `yaml:"file"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *LogsConfig) Validate() error {
|
||||||
|
if config.FileStr == "" {
|
||||||
|
_, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get PWD, %v (try using `mizu logs -f <full path dest zip file>)`", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *LogsConfig) FilePath() string {
|
||||||
|
if config.FileStr == "" {
|
||||||
|
pwd, _ := os.Getwd()
|
||||||
|
return path.Join(pwd, "mizu_logs.zip")
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.FileStr
|
||||||
|
}
|
||||||
71
cli/config/configStructs/tapConfig.go
Normal file
71
cli/config/configStructs/tapConfig.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package configStructs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/up9inc/mizu/shared/units"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
GuiPortTapName = "gui-port"
|
||||||
|
NamespacesTapName = "namespaces"
|
||||||
|
AnalysisTapName = "analysis"
|
||||||
|
AllNamespacesTapName = "all-namespaces"
|
||||||
|
PlainTextFilterRegexesTapName = "regex-masking"
|
||||||
|
DisableRedactionTapName = "no-redact"
|
||||||
|
HumanMaxEntriesDBSizeTapName = "max-entries-db-size"
|
||||||
|
DryRunTapName = "dry-run"
|
||||||
|
EnforcePolicyFile = "traffic-validation-file"
|
||||||
|
EnforcePolicyFileDeprecated = "test-rules"
|
||||||
|
)
|
||||||
|
|
||||||
|
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"`
|
||||||
|
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"`
|
||||||
|
DryRun bool `yaml:"dry-run" default:"false"`
|
||||||
|
EnforcePolicyFile string `yaml:"traffic-validation-file"`
|
||||||
|
EnforcePolicyFileDeprecated string `yaml:"test-rules,omitempty" readonly:""`
|
||||||
|
ApiServerResources Resources `yaml:"api-server-resources"`
|
||||||
|
TapperResources Resources `yaml:"tapper-resources"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Resources struct {
|
||||||
|
CpuLimit string `yaml:"cpu-limit" default:"750m"`
|
||||||
|
MemoryLimit string `yaml:"memory-limit" default:"1Gi"`
|
||||||
|
CpuRequests string `yaml:"cpu-requests" default:"50m"`
|
||||||
|
MemoryRequests string `yaml:"memory-requests" default:"50Mi"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *TapConfig) PodRegex() *regexp.Regexp {
|
||||||
|
podRegex, _ := regexp.Compile(config.PodRegexStr)
|
||||||
|
return podRegex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *TapConfig) MaxEntriesDBSizeBytes() int64 {
|
||||||
|
maxEntriesDBSizeBytes, _ := units.HumanReadableToBytes(config.HumanMaxEntriesDBSize)
|
||||||
|
return maxEntriesDBSizeBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config *TapConfig) Validate() error {
|
||||||
|
_, compileErr := regexp.Compile(config.PodRegexStr)
|
||||||
|
if compileErr != nil {
|
||||||
|
return errors.New(fmt.Sprintf("%s is not a valid regex %s", config.PodRegexStr, compileErr))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, parseHumanDataSizeErr := units.HumanReadableToBytes(config.HumanMaxEntriesDBSize)
|
||||||
|
if parseHumanDataSizeErr != nil {
|
||||||
|
return errors.New(fmt.Sprintf("Could not parse --%s value %s", HumanMaxEntriesDBSizeTapName, config.HumanMaxEntriesDBSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
11
cli/config/configStructs/viewConfig.go
Normal file
11
cli/config/configStructs/viewConfig.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package configStructs
|
||||||
|
|
||||||
|
const (
|
||||||
|
GuiPortViewName = "gui-port"
|
||||||
|
UrlViewName = "url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ViewConfig struct {
|
||||||
|
GuiPort uint16 `yaml:"gui-port" default:"8899"`
|
||||||
|
Url string `yaml:"url,omitempty" readonly:""`
|
||||||
|
}
|
||||||
385
cli/config/config_internal_test.go
Normal file
385
cli/config/config_internal_test.go
Normal file
@@ -0,0 +1,385 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConfigMock struct {
|
||||||
|
SectionMock SectionMock `yaml:"section"`
|
||||||
|
Test string `yaml:"test"`
|
||||||
|
StringField string `yaml:"string-field"`
|
||||||
|
IntField int `yaml:"int-field"`
|
||||||
|
BoolField bool `yaml:"bool-field"`
|
||||||
|
UintField uint `yaml:"uint-field"`
|
||||||
|
StringSliceField []string `yaml:"string-slice-field"`
|
||||||
|
IntSliceField []int `yaml:"int-slice-field"`
|
||||||
|
BoolSliceField []bool `yaml:"bool-slice-field"`
|
||||||
|
UintSliceField []uint `yaml:"uint-slice-field"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SectionMock struct {
|
||||||
|
Test string `yaml:"test"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FieldSetValues struct {
|
||||||
|
SetValues []string
|
||||||
|
FieldName string
|
||||||
|
FieldValue interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMergeSetFlagNoSeparator(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Name string
|
||||||
|
SetValues []string
|
||||||
|
}{
|
||||||
|
{Name: "empty value", SetValues: []string{""}},
|
||||||
|
{Name: "single char", SetValues: []string{"t"}},
|
||||||
|
{Name: "combine empty value and single char", SetValues: []string{"", "t"}},
|
||||||
|
{Name: "two values without separator", SetValues: []string{"test", "test:true"}},
|
||||||
|
{Name: "four values without separator", SetValues: []string{"test", "test:true", "testing!", "true"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.Name, func(t *testing.T) {
|
||||||
|
configMock := ConfigMock{}
|
||||||
|
configMockElemValue := reflect.ValueOf(&configMock).Elem()
|
||||||
|
|
||||||
|
err := mergeSetFlag(configMockElemValue, test.SetValues)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("unexpected unhandled error - SetValues: %v", test.SetValues)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < configMockElemValue.NumField(); i++ {
|
||||||
|
currentField := configMockElemValue.Type().Field(i)
|
||||||
|
currentFieldByName := configMockElemValue.FieldByName(currentField.Name)
|
||||||
|
|
||||||
|
if !currentFieldByName.IsZero() {
|
||||||
|
t.Errorf("unexpected value with not default value - SetValues: %v", test.SetValues)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMergeSetFlagInvalidFlagName(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Name string
|
||||||
|
SetValues []string
|
||||||
|
}{
|
||||||
|
{Name: "invalid flag name", SetValues: []string{"invalid_flag=true"}},
|
||||||
|
{Name: "invalid flag name inside section struct", SetValues: []string{"section.invalid_flag=test"}},
|
||||||
|
{Name: "flag name is a struct", SetValues: []string{"section=test"}},
|
||||||
|
{Name: "empty flag name", SetValues: []string{"=true"}},
|
||||||
|
{Name: "four tests combined", SetValues: []string{"invalid_flag=true", "config.invalid_flag=test", "section=test", "=true"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.Name, func(t *testing.T) {
|
||||||
|
configMock := ConfigMock{}
|
||||||
|
configMockElemValue := reflect.ValueOf(&configMock).Elem()
|
||||||
|
|
||||||
|
err := mergeSetFlag(configMockElemValue, test.SetValues)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("unexpected unhandled error - SetValues: %v", test.SetValues)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < configMockElemValue.NumField(); i++ {
|
||||||
|
currentField := configMockElemValue.Type().Field(i)
|
||||||
|
currentFieldByName := configMockElemValue.FieldByName(currentField.Name)
|
||||||
|
|
||||||
|
if !currentFieldByName.IsZero() {
|
||||||
|
t.Errorf("unexpected case - SetValues: %v", test.SetValues)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMergeSetFlagInvalidFlagValue(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Name string
|
||||||
|
SetValues []string
|
||||||
|
}{
|
||||||
|
{Name: "bool value to int field", SetValues: []string{"int-field=true"}},
|
||||||
|
{Name: "int value to bool field", SetValues: []string{"bool-field:5"}},
|
||||||
|
{Name: "int value to uint field", SetValues: []string{"uint-field=-1"}},
|
||||||
|
{Name: "bool value to int slice field", SetValues: []string{"int-slice-field=true"}},
|
||||||
|
{Name: "int value to bool slice field", SetValues: []string{"bool-slice-field=5"}},
|
||||||
|
{Name: "int value to uint slice field", SetValues: []string{"uint-slice-field=-1"}},
|
||||||
|
{Name: "int slice value to int field", SetValues: []string{"int-field=6", "int-field=66"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.Name, func(t *testing.T) {
|
||||||
|
configMock := ConfigMock{}
|
||||||
|
configMockElemValue := reflect.ValueOf(&configMock).Elem()
|
||||||
|
|
||||||
|
err := mergeSetFlag(configMockElemValue, test.SetValues)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("unexpected unhandled error - SetValues: %v", test.SetValues)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < configMockElemValue.NumField(); i++ {
|
||||||
|
currentField := configMockElemValue.Type().Field(i)
|
||||||
|
currentFieldByName := configMockElemValue.FieldByName(currentField.Name)
|
||||||
|
|
||||||
|
if !currentFieldByName.IsZero() {
|
||||||
|
t.Errorf("unexpected case - SetValues: %v", test.SetValues)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMergeSetFlagNotSliceValues(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Name string
|
||||||
|
FieldsSetValues []FieldSetValues
|
||||||
|
}{
|
||||||
|
{Name: "string field", FieldsSetValues: []FieldSetValues{{SetValues: []string{"string-field=test"}, FieldName: "StringField", FieldValue: "test"}}},
|
||||||
|
{Name: "int field", FieldsSetValues: []FieldSetValues{{SetValues: []string{"int-field=6"}, FieldName: "IntField", FieldValue: 6}}},
|
||||||
|
{Name: "bool field", FieldsSetValues: []FieldSetValues{{SetValues: []string{"bool-field=true"}, FieldName: "BoolField", FieldValue: true}}},
|
||||||
|
{Name: "uint field", FieldsSetValues: []FieldSetValues{{SetValues: []string{"uint-field=6"}, FieldName: "UintField", FieldValue: uint(6)}}},
|
||||||
|
{Name: "four fields combined", FieldsSetValues: []FieldSetValues {
|
||||||
|
{SetValues: []string{"string-field=test"}, FieldName: "StringField", FieldValue: "test"},
|
||||||
|
{SetValues: []string{"int-field=6"}, FieldName: "IntField", FieldValue: 6},
|
||||||
|
{SetValues: []string{"bool-field=true"}, FieldName: "BoolField", FieldValue: true},
|
||||||
|
{SetValues: []string{"uint-field=6"}, FieldName: "UintField", FieldValue: uint(6)},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.Name, func(t *testing.T) {
|
||||||
|
configMock := ConfigMock{}
|
||||||
|
configMockElemValue := reflect.ValueOf(&configMock).Elem()
|
||||||
|
|
||||||
|
var setValues []string
|
||||||
|
for _, fieldSetValues := range test.FieldsSetValues {
|
||||||
|
setValues = append(setValues, fieldSetValues.SetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mergeSetFlag(configMockElemValue, setValues)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error result - err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fieldSetValues := range test.FieldsSetValues {
|
||||||
|
fieldValue := configMockElemValue.FieldByName(fieldSetValues.FieldName).Interface()
|
||||||
|
if fieldValue != fieldSetValues.FieldValue {
|
||||||
|
t.Errorf("unexpected result - expected: %v, actual: %v", fieldSetValues.FieldValue, fieldValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMergeSetFlagSliceValues(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Name string
|
||||||
|
FieldsSetValues []FieldSetValues
|
||||||
|
}{
|
||||||
|
{Name: "string slice field single value", FieldsSetValues: []FieldSetValues{{SetValues: []string{"string-slice-field=test"}, FieldName: "StringSliceField", FieldValue: []string{"test"}}}},
|
||||||
|
{Name: "int slice field single value", FieldsSetValues: []FieldSetValues{{SetValues: []string{"int-slice-field=6"}, FieldName: "IntSliceField", FieldValue: []int{6}}}},
|
||||||
|
{Name: "bool slice field single value", FieldsSetValues: []FieldSetValues{{SetValues: []string{"bool-slice-field=true"}, FieldName: "BoolSliceField", FieldValue: []bool{true}}}},
|
||||||
|
{Name: "uint slice field single value", FieldsSetValues: []FieldSetValues{{SetValues: []string{"uint-slice-field=6"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6)}}}},
|
||||||
|
{Name: "four single value fields combined", FieldsSetValues: []FieldSetValues{
|
||||||
|
{SetValues: []string{"string-slice-field=test"}, FieldName: "StringSliceField", FieldValue: []string{"test"}},
|
||||||
|
{SetValues: []string{"int-slice-field=6"}, FieldName: "IntSliceField", FieldValue: []int{6}},
|
||||||
|
{SetValues: []string{"bool-slice-field=true"}, FieldName: "BoolSliceField", FieldValue: []bool{true}},
|
||||||
|
{SetValues: []string{"uint-slice-field=6"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6)}},
|
||||||
|
}},
|
||||||
|
{Name: "string slice field two values", FieldsSetValues: []FieldSetValues{{SetValues: []string{"string-slice-field=test", "string-slice-field=test2"}, FieldName: "StringSliceField", FieldValue: []string{"test", "test2"}}}},
|
||||||
|
{Name: "int slice field two values", FieldsSetValues: []FieldSetValues{{SetValues: []string{"int-slice-field=6", "int-slice-field=66"}, FieldName: "IntSliceField", FieldValue: []int{6, 66}}}},
|
||||||
|
{Name: "bool slice field two values", FieldsSetValues: []FieldSetValues{{SetValues: []string{"bool-slice-field=true", "bool-slice-field=false"}, FieldName: "BoolSliceField", FieldValue: []bool{true, false}}}},
|
||||||
|
{Name: "uint slice field two values", FieldsSetValues: []FieldSetValues{{SetValues: []string{"uint-slice-field=6", "uint-slice-field=66"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6), uint(66)}}}},
|
||||||
|
{Name: "four two values fields combined", FieldsSetValues: []FieldSetValues{
|
||||||
|
{SetValues: []string{"string-slice-field=test", "string-slice-field=test2"}, FieldName: "StringSliceField", FieldValue: []string{"test", "test2"}},
|
||||||
|
{SetValues: []string{"int-slice-field=6", "int-slice-field=66"}, FieldName: "IntSliceField", FieldValue: []int{6, 66}},
|
||||||
|
{SetValues: []string{"bool-slice-field=true", "bool-slice-field=false"}, FieldName: "BoolSliceField", FieldValue: []bool{true, false}},
|
||||||
|
{SetValues: []string{"uint-slice-field=6", "uint-slice-field=66"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6), uint(66)}},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.Name, func(t *testing.T) {
|
||||||
|
configMock := ConfigMock{}
|
||||||
|
configMockElemValue := reflect.ValueOf(&configMock).Elem()
|
||||||
|
|
||||||
|
var setValues []string
|
||||||
|
for _, fieldSetValues := range test.FieldsSetValues {
|
||||||
|
setValues = append(setValues, fieldSetValues.SetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mergeSetFlag(configMockElemValue, setValues)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error result - err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fieldSetValues := range test.FieldsSetValues {
|
||||||
|
fieldValue := configMockElemValue.FieldByName(fieldSetValues.FieldName).Interface()
|
||||||
|
if !reflect.DeepEqual(fieldValue, fieldSetValues.FieldValue) {
|
||||||
|
t.Errorf("unexpected result - expected: %v, actual: %v", fieldSetValues.FieldValue, fieldValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMergeSetFlagMixValues(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Name string
|
||||||
|
FieldsSetValues []FieldSetValues
|
||||||
|
}{
|
||||||
|
{Name: "single value all fields", FieldsSetValues: []FieldSetValues{
|
||||||
|
{SetValues: []string{"string-slice-field=test"}, FieldName: "StringSliceField", FieldValue: []string{"test"}},
|
||||||
|
{SetValues: []string{"int-slice-field=6"}, FieldName: "IntSliceField", FieldValue: []int{6}},
|
||||||
|
{SetValues: []string{"bool-slice-field=true"}, FieldName: "BoolSliceField", FieldValue: []bool{true}},
|
||||||
|
{SetValues: []string{"uint-slice-field=6"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6)}},
|
||||||
|
{SetValues: []string{"string-field=test"}, FieldName: "StringField", FieldValue: "test"},
|
||||||
|
{SetValues: []string{"int-field=6"}, FieldName: "IntField", FieldValue: 6},
|
||||||
|
{SetValues: []string{"bool-field=true"}, FieldName: "BoolField", FieldValue: true},
|
||||||
|
{SetValues: []string{"uint-field=6"}, FieldName: "UintField", FieldValue: uint(6)},
|
||||||
|
}},
|
||||||
|
{Name: "two values slice fields and single value fields", FieldsSetValues: []FieldSetValues{
|
||||||
|
{SetValues: []string{"string-slice-field=test", "string-slice-field=test2"}, FieldName: "StringSliceField", FieldValue: []string{"test", "test2"}},
|
||||||
|
{SetValues: []string{"int-slice-field=6", "int-slice-field=66"}, FieldName: "IntSliceField", FieldValue: []int{6, 66}},
|
||||||
|
{SetValues: []string{"bool-slice-field=true", "bool-slice-field=false"}, FieldName: "BoolSliceField", FieldValue: []bool{true, false}},
|
||||||
|
{SetValues: []string{"uint-slice-field=6", "uint-slice-field=66"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6), uint(66)}},
|
||||||
|
{SetValues: []string{"string-field=test"}, FieldName: "StringField", FieldValue: "test"},
|
||||||
|
{SetValues: []string{"int-field=6"}, FieldName: "IntField", FieldValue: 6},
|
||||||
|
{SetValues: []string{"bool-field=true"}, FieldName: "BoolField", FieldValue: true},
|
||||||
|
{SetValues: []string{"uint-field=6"}, FieldName: "UintField", FieldValue: uint(6)},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.Name, func(t *testing.T) {
|
||||||
|
configMock := ConfigMock{}
|
||||||
|
configMockElemValue := reflect.ValueOf(&configMock).Elem()
|
||||||
|
|
||||||
|
var setValues []string
|
||||||
|
for _, fieldSetValues := range test.FieldsSetValues {
|
||||||
|
setValues = append(setValues, fieldSetValues.SetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mergeSetFlag(configMockElemValue, setValues)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error result - err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fieldSetValues := range test.FieldsSetValues {
|
||||||
|
fieldValue := configMockElemValue.FieldByName(fieldSetValues.FieldName).Interface()
|
||||||
|
if !reflect.DeepEqual(fieldValue, fieldSetValues.FieldValue) {
|
||||||
|
t.Errorf("unexpected result - expected: %v, actual: %v", fieldSetValues.FieldValue, fieldValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetParsedValueValidValue(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
StringValue string
|
||||||
|
Kind reflect.Kind
|
||||||
|
ActualValue interface{}
|
||||||
|
}{
|
||||||
|
{StringValue: "test", Kind: reflect.String, ActualValue: "test"},
|
||||||
|
{StringValue: "123", Kind: reflect.String, ActualValue: "123"},
|
||||||
|
{StringValue: "true", Kind: reflect.Bool, ActualValue: true},
|
||||||
|
{StringValue: "false", Kind: reflect.Bool, ActualValue: false},
|
||||||
|
{StringValue: "6", Kind: reflect.Int, ActualValue: 6},
|
||||||
|
{StringValue: "-6", Kind: reflect.Int, ActualValue: -6},
|
||||||
|
{StringValue: "6", Kind: reflect.Int8, ActualValue: int8(6)},
|
||||||
|
{StringValue: "-6", Kind: reflect.Int8, ActualValue: int8(-6)},
|
||||||
|
{StringValue: "6", Kind: reflect.Int16, ActualValue: int16(6)},
|
||||||
|
{StringValue: "-6", Kind: reflect.Int16, ActualValue: int16(-6)},
|
||||||
|
{StringValue: "6", Kind: reflect.Int32, ActualValue: int32(6)},
|
||||||
|
{StringValue: "-6", Kind: reflect.Int32, ActualValue: int32(-6)},
|
||||||
|
{StringValue: "6", Kind: reflect.Int64, ActualValue: int64(6)},
|
||||||
|
{StringValue: "-6", Kind: reflect.Int64, ActualValue: int64(-6)},
|
||||||
|
{StringValue: "6", Kind: reflect.Uint, ActualValue: uint(6)},
|
||||||
|
{StringValue: "66", Kind: reflect.Uint, ActualValue: uint(66)},
|
||||||
|
{StringValue: "6", Kind: reflect.Uint8, ActualValue: uint8(6)},
|
||||||
|
{StringValue: "66", Kind: reflect.Uint8, ActualValue: uint8(66)},
|
||||||
|
{StringValue: "6", Kind: reflect.Uint16, ActualValue: uint16(6)},
|
||||||
|
{StringValue: "66", Kind: reflect.Uint16, ActualValue: uint16(66)},
|
||||||
|
{StringValue: "6", Kind: reflect.Uint32, ActualValue: uint32(6)},
|
||||||
|
{StringValue: "66", Kind: reflect.Uint32, ActualValue: uint32(66)},
|
||||||
|
{StringValue: "6", Kind: reflect.Uint64, ActualValue: uint64(6)},
|
||||||
|
{StringValue: "66", Kind: reflect.Uint64, ActualValue: uint64(66)},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%v %v", test.Kind, test.StringValue), func(t *testing.T) {
|
||||||
|
parsedValue, err := getParsedValue(test.Kind, test.StringValue)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error result - err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedValue.Interface() != test.ActualValue {
|
||||||
|
t.Errorf("unexpected result - expected: %v, actual: %v", test.ActualValue, parsedValue)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetParsedValueInvalidValue(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
StringValue string
|
||||||
|
Kind reflect.Kind
|
||||||
|
}{
|
||||||
|
{StringValue: "test", Kind: reflect.Bool},
|
||||||
|
{StringValue: "123", Kind: reflect.Bool},
|
||||||
|
{StringValue: "test", Kind: reflect.Int},
|
||||||
|
{StringValue: "true", Kind: reflect.Int},
|
||||||
|
{StringValue: "test", Kind: reflect.Int8},
|
||||||
|
{StringValue: "true", Kind: reflect.Int8},
|
||||||
|
{StringValue: "test", Kind: reflect.Int16},
|
||||||
|
{StringValue: "true", Kind: reflect.Int16},
|
||||||
|
{StringValue: "test", Kind: reflect.Int32},
|
||||||
|
{StringValue: "true", Kind: reflect.Int32},
|
||||||
|
{StringValue: "test", Kind: reflect.Int64},
|
||||||
|
{StringValue: "true", Kind: reflect.Int64},
|
||||||
|
{StringValue: "test", Kind: reflect.Uint},
|
||||||
|
{StringValue: "-6", Kind: reflect.Uint},
|
||||||
|
{StringValue: "test", Kind: reflect.Uint8},
|
||||||
|
{StringValue: "-6", Kind: reflect.Uint8},
|
||||||
|
{StringValue: "test", Kind: reflect.Uint16},
|
||||||
|
{StringValue: "-6", Kind: reflect.Uint16},
|
||||||
|
{StringValue: "test", Kind: reflect.Uint32},
|
||||||
|
{StringValue: "-6", Kind: reflect.Uint32},
|
||||||
|
{StringValue: "test", Kind: reflect.Uint64},
|
||||||
|
{StringValue: "-6", Kind: reflect.Uint64},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("%v %v", test.Kind, test.StringValue), func(t *testing.T) {
|
||||||
|
parsedValue, err := getParsedValue(test.Kind, test.StringValue)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("unexpected unhandled error - stringValue: %v, Kind: %v", test.StringValue, test.Kind)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedValue != reflect.ValueOf(nil) {
|
||||||
|
t.Errorf("unexpected parsed value - parsedValue: %v", parsedValue)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
43
cli/config/config_test.go
Normal file
43
cli/config/config_test.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package config_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/up9inc/mizu/cli/config"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfigWriteIgnoresReadonlyFields(t *testing.T) {
|
||||||
|
var readonlyFields []string
|
||||||
|
|
||||||
|
configElem := reflect.ValueOf(&config.ConfigStruct{}).Elem()
|
||||||
|
getFieldsWithReadonlyTag(configElem, &readonlyFields)
|
||||||
|
|
||||||
|
configWithDefaults, _ := config.GetConfigWithDefaults()
|
||||||
|
for _, readonlyField := range readonlyFields {
|
||||||
|
t.Run(readonlyField, func(t *testing.T) {
|
||||||
|
readonlyFieldToCheck := fmt.Sprintf("\n%s:", readonlyField)
|
||||||
|
if strings.Contains(configWithDefaults, readonlyFieldToCheck) {
|
||||||
|
t.Errorf("unexpected result - readonly field: %v, config: %v", readonlyField, configWithDefaults)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(config.ReadonlyTag); ok {
|
||||||
|
fieldNameByTag := strings.Split(currentField.Tag.Get(config.FieldNameTag), ",")[0]
|
||||||
|
*readonlyFields = append(*readonlyFields, fieldNameByTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
cli/config/envConfig.go
Normal file
24
cli/config/envConfig.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ApiServerRetries = "API_SERVER_RETRIES"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetIntEnvConfig(key string, defaultValue int) int {
|
||||||
|
value := os.Getenv(key)
|
||||||
|
if value == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
intValue, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return intValue
|
||||||
|
}
|
||||||
36
cli/errormessage/errormessage.go
Normal file
36
cli/errormessage/errormessage.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package errormessage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/up9inc/mizu/cli/config"
|
||||||
|
regexpsyntax "regexp/syntax"
|
||||||
|
|
||||||
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// formatError wraps error with a detailed message that is meant for the user.
|
||||||
|
// While the errors are meant to be displayed, they are not meant to be exported as classes outsite of CLI.
|
||||||
|
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 %s "+
|
||||||
|
"in the config file or setting the tapped namespace with --%s %s=<NAMEPSACE>",
|
||||||
|
err,
|
||||||
|
config.MizuResourcesNamespaceConfigName,
|
||||||
|
config.SetCommandName,
|
||||||
|
config.MizuResourcesNamespaceConfigName)
|
||||||
|
} else if syntaxError, isSyntaxError := asRegexSyntaxError(err); isSyntaxError {
|
||||||
|
errorNew = fmt.Errorf("regex %s is invalid: %w", syntaxError.Expr, err)
|
||||||
|
} else {
|
||||||
|
errorNew = err
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorNew
|
||||||
|
}
|
||||||
|
|
||||||
|
func asRegexSyntaxError(err error) (*regexpsyntax.Error, bool) {
|
||||||
|
var syntaxError *regexpsyntax.Error
|
||||||
|
return syntaxError, errors.As(err, &syntaxError)
|
||||||
|
}
|
||||||
@@ -4,12 +4,14 @@ go 1.16
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/creasty/defaults v1.5.1
|
github.com/creasty/defaults v1.5.1
|
||||||
|
github.com/denisbrodbeck/machineid v1.0.1
|
||||||
github.com/google/go-github/v37 v37.0.0
|
github.com/google/go-github/v37 v37.0.0
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/gorilla/websocket v1.4.2
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/spf13/cobra v1.1.3
|
github.com/spf13/cobra v1.1.3
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/up9inc/mizu/shared v0.0.0
|
github.com/up9inc/mizu/shared v0.0.0
|
||||||
|
github.com/up9inc/mizu/tap/api v0.0.0
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||||
k8s.io/api v0.21.2
|
k8s.io/api v0.21.2
|
||||||
k8s.io/apimachinery v0.21.2
|
k8s.io/apimachinery v0.21.2
|
||||||
@@ -18,3 +20,5 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/up9inc/mizu/shared v0.0.0 => ../shared
|
replace github.com/up9inc/mizu/shared v0.0.0 => ../shared
|
||||||
|
|
||||||
|
replace github.com/up9inc/mizu/tap/api v0.0.0 => ../tap/api
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE=
|
github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE=
|
||||||
|
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
|
||||||
|
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
|
|||||||
@@ -1,26 +1,30 @@
|
|||||||
package kubernetes
|
package kubernetes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
_ "bytes"
|
_ "bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/up9inc/mizu/cli/mizu"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/client-go/tools/cache"
|
|
||||||
"k8s.io/client-go/util/homedir"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/up9inc/mizu/cli/config/configStructs"
|
||||||
|
"github.com/up9inc/mizu/cli/logger"
|
||||||
|
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
"github.com/up9inc/mizu/shared"
|
"github.com/up9inc/mizu/shared"
|
||||||
|
"github.com/up9inc/mizu/tap/api"
|
||||||
core "k8s.io/api/core/v1"
|
core "k8s.io/api/core/v1"
|
||||||
rbac "k8s.io/api/rbac/v1"
|
rbac "k8s.io/api/rbac/v1"
|
||||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
resource "k8s.io/apimachinery/pkg/api/resource"
|
resource "k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
applyconfapp "k8s.io/client-go/applyconfigurations/apps/v1"
|
applyconfapp "k8s.io/client-go/applyconfigurations/apps/v1"
|
||||||
@@ -32,6 +36,7 @@ import (
|
|||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
|
_ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
_ "k8s.io/client-go/tools/portforward"
|
_ "k8s.io/client-go/tools/portforward"
|
||||||
watchtools "k8s.io/client-go/tools/watch"
|
watchtools "k8s.io/client-go/tools/watch"
|
||||||
@@ -52,9 +57,24 @@ func NewProvider(kubeConfigPath string) (*Provider, error) {
|
|||||||
kubernetesConfig := loadKubernetesConfiguration(kubeConfigPath)
|
kubernetesConfig := loadKubernetesConfiguration(kubeConfigPath)
|
||||||
restClientConfig, err := kubernetesConfig.ClientConfig()
|
restClientConfig, err := kubernetesConfig.ClientConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
if clientcmd.IsEmptyConfig(err) {
|
||||||
|
return nil, fmt.Errorf("couldn't find the kube config file, or file is empty (%s)\n"+
|
||||||
|
"you can set alternative kube config file path by adding the kube-config-path field to the mizu config file, err: %w", kubeConfigPath, err)
|
||||||
|
}
|
||||||
|
if clientcmd.IsConfigurationInvalid(err) {
|
||||||
|
return nil, fmt.Errorf("invalid kube config file (%s)\n"+
|
||||||
|
"you can set alternative kube config file path by adding the kube-config-path field to the mizu config file, err: %w", kubeConfigPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("error while using kube config (%s)\n"+
|
||||||
|
"you can set alternative kube config file path by adding the kube-config-path field to the mizu config file, err: %w", kubeConfigPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientSet, err := getClientSet(restClientConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error while using kube config (%s)\n"+
|
||||||
|
"you can set alternative kube config file path by adding the kube-config-path field to the mizu config file, err: %w", kubeConfigPath, err)
|
||||||
}
|
}
|
||||||
clientSet := getClientSet(restClientConfig)
|
|
||||||
|
|
||||||
return &Provider{
|
return &Provider{
|
||||||
clientSet: clientSet,
|
clientSet: clientSet,
|
||||||
@@ -125,42 +145,69 @@ func (provider *Provider) CreateNamespace(ctx context.Context, name string) (*co
|
|||||||
return provider.clientSet.CoreV1().Namespaces().Create(ctx, namespaceSpec, metav1.CreateOptions{})
|
return provider.clientSet.CoreV1().Namespaces().Create(ctx, namespaceSpec, metav1.CreateOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) CreateMizuApiServerPod(ctx context.Context, namespace string, podName string, podImage string, serviceAccountName string, mizuApiFilteringOptions *shared.TrafficFilteringOptions, maxEntriesDBSizeBytes int64) (*core.Pod, error) {
|
type ApiServerOptions struct {
|
||||||
marshaledFilteringOptions, err := json.Marshal(mizuApiFilteringOptions)
|
Namespace string
|
||||||
|
PodName string
|
||||||
|
PodImage string
|
||||||
|
ServiceAccountName string
|
||||||
|
IsNamespaceRestricted bool
|
||||||
|
MizuApiFilteringOptions *api.TrafficFilteringOptions
|
||||||
|
MaxEntriesDBSizeBytes int64
|
||||||
|
Resources configStructs.Resources
|
||||||
|
ImagePullPolicy core.PullPolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) CreateMizuApiServerPod(ctx context.Context, opts *ApiServerOptions) (*core.Pod, error) {
|
||||||
|
marshaledFilteringOptions, err := json.Marshal(opts.MizuApiFilteringOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
configMapVolumeName := &core.ConfigMapVolumeSource{}
|
||||||
|
configMapVolumeName.Name = mizu.ConfigMapName
|
||||||
|
configMapOptional := true
|
||||||
|
configMapVolumeName.Optional = &configMapOptional
|
||||||
|
|
||||||
cpuLimit, err := resource.ParseQuantity("750m")
|
cpuLimit, err := resource.ParseQuantity(opts.Resources.CpuLimit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New(fmt.Sprintf("invalid cpu limit for %s container", podName))
|
return nil, errors.New(fmt.Sprintf("invalid cpu limit for %s container", opts.PodName))
|
||||||
}
|
}
|
||||||
memLimit, err := resource.ParseQuantity("512Mi")
|
memLimit, err := resource.ParseQuantity(opts.Resources.MemoryLimit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New(fmt.Sprintf("invalid memory limit for %s container", podName))
|
return nil, errors.New(fmt.Sprintf("invalid memory limit for %s container", opts.PodName))
|
||||||
}
|
}
|
||||||
cpuRequests, err := resource.ParseQuantity("50m")
|
cpuRequests, err := resource.ParseQuantity(opts.Resources.CpuRequests)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New(fmt.Sprintf("invalid cpu request for %s container", podName))
|
return nil, errors.New(fmt.Sprintf("invalid cpu request for %s container", opts.PodName))
|
||||||
}
|
}
|
||||||
memRequests, err := resource.ParseQuantity("50Mi")
|
memRequests, err := resource.ParseQuantity(opts.Resources.MemoryRequests)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New(fmt.Sprintf("invalid memory request for %s container", podName))
|
return nil, errors.New(fmt.Sprintf("invalid memory request for %s container", opts.PodName))
|
||||||
|
}
|
||||||
|
|
||||||
|
command := []string{"./mizuagent", "--api-server"}
|
||||||
|
if opts.IsNamespaceRestricted {
|
||||||
|
command = append(command, "--namespace", opts.Namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
pod := &core.Pod{
|
pod := &core.Pod{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: podName,
|
Name: opts.PodName,
|
||||||
Namespace: namespace,
|
Namespace: opts.Namespace,
|
||||||
Labels: map[string]string{"app": podName},
|
Labels: map[string]string{"app": opts.PodName},
|
||||||
},
|
},
|
||||||
Spec: core.PodSpec{
|
Spec: core.PodSpec{
|
||||||
Containers: []core.Container{
|
Containers: []core.Container{
|
||||||
{
|
{
|
||||||
Name: podName,
|
Name: opts.PodName,
|
||||||
Image: podImage,
|
Image: opts.PodImage,
|
||||||
ImagePullPolicy: core.PullAlways,
|
ImagePullPolicy: opts.ImagePullPolicy,
|
||||||
Command: []string{"./mizuagent", "--api-server"},
|
VolumeMounts: []core.VolumeMount{
|
||||||
|
{
|
||||||
|
Name: mizu.ConfigMapName,
|
||||||
|
MountPath: shared.RulePolicyPath,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Command: command,
|
||||||
Env: []core.EnvVar{
|
Env: []core.EnvVar{
|
||||||
{
|
{
|
||||||
Name: shared.HostModeEnvVar,
|
Name: shared.HostModeEnvVar,
|
||||||
@@ -172,7 +219,7 @@ func (provider *Provider) CreateMizuApiServerPod(ctx context.Context, namespace
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: shared.MaxEntriesDBSizeBytesEnvVar,
|
Name: shared.MaxEntriesDBSizeBytesEnvVar,
|
||||||
Value: strconv.FormatInt(maxEntriesDBSizeBytes, 10),
|
Value: strconv.FormatInt(opts.MaxEntriesDBSizeBytes, 10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Resources: core.ResourceRequirements{
|
Resources: core.ResourceRequirements{
|
||||||
@@ -187,15 +234,23 @@ func (provider *Provider) CreateMizuApiServerPod(ctx context.Context, namespace
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Volumes: []core.Volume{
|
||||||
|
{
|
||||||
|
Name: mizu.ConfigMapName,
|
||||||
|
VolumeSource: core.VolumeSource{
|
||||||
|
ConfigMap: configMapVolumeName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
DNSPolicy: core.DNSClusterFirstWithHostNet,
|
DNSPolicy: core.DNSClusterFirstWithHostNet,
|
||||||
TerminationGracePeriodSeconds: new(int64),
|
TerminationGracePeriodSeconds: new(int64),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
//define the service account only when it exists to prevent pod crash
|
//define the service account only when it exists to prevent pod crash
|
||||||
if serviceAccountName != "" {
|
if opts.ServiceAccountName != "" {
|
||||||
pod.Spec.ServiceAccountName = serviceAccountName
|
pod.Spec.ServiceAccountName = opts.ServiceAccountName
|
||||||
}
|
}
|
||||||
return provider.clientSet.CoreV1().Pods(namespace).Create(ctx, pod, metav1.CreateOptions{})
|
return provider.clientSet.CoreV1().Pods(opts.Namespace).Create(ctx, pod, metav1.CreateOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) CreateService(ctx context.Context, namespace string, serviceName string, appLabelValue string) (*core.Service, error) {
|
func (provider *Provider) CreateService(ctx context.Context, namespace string, serviceName string, appLabelValue string) (*core.Service, error) {
|
||||||
@@ -213,35 +268,22 @@ func (provider *Provider) CreateService(ctx context.Context, namespace string, s
|
|||||||
return provider.clientSet.CoreV1().Services(namespace).Create(ctx, &service, metav1.CreateOptions{})
|
return provider.clientSet.CoreV1().Services(namespace).Create(ctx, &service, metav1.CreateOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) DoesServiceAccountExist(ctx context.Context, namespace string, serviceAccountName string) (bool, error) {
|
func (provider *Provider) DoesServicesExist(ctx context.Context, namespace string, name string) (bool, error) {
|
||||||
serviceAccount, err := provider.clientSet.CoreV1().ServiceAccounts(namespace).Get(ctx, serviceAccountName, metav1.GetOptions{})
|
resource, err := provider.clientSet.CoreV1().Services(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||||
|
return provider.doesResourceExist(resource, err)
|
||||||
var statusError *k8serrors.StatusError
|
|
||||||
if errors.As(err, &statusError) {
|
|
||||||
// expected behavior when resource does not exist
|
|
||||||
if statusError.ErrStatus.Reason == metav1.StatusReasonNotFound {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return serviceAccount != nil, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) DoesServicesExist(ctx context.Context, namespace string, serviceName string) (bool, error) {
|
func (provider *Provider) doesResourceExist(resource interface{}, err error) (bool, error) {
|
||||||
service, err := provider.clientSet.CoreV1().Services(namespace).Get(ctx, serviceName, metav1.GetOptions{})
|
// Getting NotFound error is the expected behavior when a resource does not exist.
|
||||||
|
if k8serrors.IsNotFound(err) {
|
||||||
var statusError *k8serrors.StatusError
|
return false, nil
|
||||||
if errors.As(err, &statusError) {
|
|
||||||
if statusError.ErrStatus.Reason == metav1.StatusReasonNotFound {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return service != nil, nil
|
|
||||||
|
return resource != nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) CreateMizuRBAC(ctx context.Context, namespace string, serviceAccountName string, clusterRoleName string, clusterRoleBindingName string, version string) error {
|
func (provider *Provider) CreateMizuRBAC(ctx context.Context, namespace string, serviceAccountName string, clusterRoleName string, clusterRoleBindingName string, version string) error {
|
||||||
@@ -284,199 +326,163 @@ func (provider *Provider) CreateMizuRBAC(ctx context.Context, namespace string,
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
_, err := provider.clientSet.CoreV1().ServiceAccounts(namespace).Create(ctx, serviceAccount, metav1.CreateOptions{})
|
_, err := provider.clientSet.CoreV1().ServiceAccounts(namespace).Create(ctx, serviceAccount, metav1.CreateOptions{})
|
||||||
if err != nil {
|
if err != nil && !k8serrors.IsAlreadyExists(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = provider.clientSet.RbacV1().ClusterRoles().Create(ctx, clusterRole, metav1.CreateOptions{})
|
_, err = provider.clientSet.RbacV1().ClusterRoles().Create(ctx, clusterRole, metav1.CreateOptions{})
|
||||||
if err != nil {
|
if err != nil && !k8serrors.IsAlreadyExists(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = provider.clientSet.RbacV1().ClusterRoleBindings().Create(ctx, clusterRoleBinding, metav1.CreateOptions{})
|
_, err = provider.clientSet.RbacV1().ClusterRoleBindings().Create(ctx, clusterRoleBinding, metav1.CreateOptions{})
|
||||||
if err != nil {
|
if err != nil && !k8serrors.IsAlreadyExists(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) CreateMizuRBACNamespaceRestricted(ctx context.Context, namespace string, serviceAccountName string, roleName string, roleBindingName string, version string) error {
|
||||||
|
serviceAccount := &core.ServiceAccount{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: serviceAccountName,
|
||||||
|
Namespace: namespace,
|
||||||
|
Labels: map[string]string{"mizu-cli-version": version},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
role := &rbac.Role{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: roleName,
|
||||||
|
Labels: map[string]string{"mizu-cli-version": version},
|
||||||
|
},
|
||||||
|
Rules: []rbac.PolicyRule{
|
||||||
|
{
|
||||||
|
APIGroups: []string{"", "extensions", "apps"},
|
||||||
|
Resources: []string{"pods", "services", "endpoints"},
|
||||||
|
Verbs: []string{"list", "get", "watch"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
roleBinding := &rbac.RoleBinding{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: roleBindingName,
|
||||||
|
Labels: map[string]string{"mizu-cli-version": version},
|
||||||
|
},
|
||||||
|
RoleRef: rbac.RoleRef{
|
||||||
|
Name: roleName,
|
||||||
|
Kind: "Role",
|
||||||
|
APIGroup: "rbac.authorization.k8s.io",
|
||||||
|
},
|
||||||
|
Subjects: []rbac.Subject{
|
||||||
|
{
|
||||||
|
Kind: "ServiceAccount",
|
||||||
|
Name: serviceAccountName,
|
||||||
|
Namespace: namespace,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err := provider.clientSet.CoreV1().ServiceAccounts(namespace).Create(ctx, serviceAccount, metav1.CreateOptions{})
|
||||||
|
if err != nil && !k8serrors.IsAlreadyExists(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = provider.clientSet.RbacV1().Roles(namespace).Create(ctx, role, metav1.CreateOptions{})
|
||||||
|
if err != nil && !k8serrors.IsAlreadyExists(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = provider.clientSet.RbacV1().RoleBindings(namespace).Create(ctx, roleBinding, metav1.CreateOptions{})
|
||||||
|
if err != nil && !k8serrors.IsAlreadyExists(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) RemoveNamespace(ctx context.Context, name string) error {
|
func (provider *Provider) RemoveNamespace(ctx context.Context, name string) error {
|
||||||
if isFound, err := provider.CheckNamespaceExists(ctx, name); err != nil {
|
err := provider.clientSet.CoreV1().Namespaces().Delete(ctx, name, metav1.DeleteOptions{})
|
||||||
return err
|
return provider.handleRemovalError(err)
|
||||||
} else if !isFound {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return provider.clientSet.CoreV1().Namespaces().Delete(ctx, name, metav1.DeleteOptions{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *Provider) RemoveNonNamespacedResources(ctx context.Context, clusterRoleName string, clusterRoleBindingName string) error {
|
|
||||||
if err := provider.RemoveClusterRole(ctx, clusterRoleName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := provider.RemoveClusterRoleBinding(ctx, clusterRoleBindingName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) RemoveClusterRole(ctx context.Context, name string) error {
|
func (provider *Provider) RemoveClusterRole(ctx context.Context, name string) error {
|
||||||
if isFound, err := provider.CheckClusterRoleExists(ctx, name); err != nil {
|
err := provider.clientSet.RbacV1().ClusterRoles().Delete(ctx, name, metav1.DeleteOptions{})
|
||||||
return err
|
return provider.handleRemovalError(err)
|
||||||
} else if !isFound {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return provider.clientSet.RbacV1().ClusterRoles().Delete(ctx, name, metav1.DeleteOptions{})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) RemoveClusterRoleBinding(ctx context.Context, name string) error {
|
func (provider *Provider) RemoveClusterRoleBinding(ctx context.Context, name string) error {
|
||||||
if isFound, err := provider.CheckClusterRoleBindingExists(ctx, name); err != nil {
|
err := provider.clientSet.RbacV1().ClusterRoleBindings().Delete(ctx, name, metav1.DeleteOptions{})
|
||||||
return err
|
return provider.handleRemovalError(err)
|
||||||
} else if !isFound {
|
}
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return provider.clientSet.RbacV1().ClusterRoleBindings().Delete(ctx, name, metav1.DeleteOptions{})
|
func (provider *Provider) RemoveRoleBinding(ctx context.Context, namespace string, name string) error {
|
||||||
|
err := provider.clientSet.RbacV1().RoleBindings(namespace).Delete(ctx, name, metav1.DeleteOptions{})
|
||||||
|
return provider.handleRemovalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) RemoveRole(ctx context.Context, namespace string, name string) error {
|
||||||
|
err := provider.clientSet.RbacV1().Roles(namespace).Delete(ctx, name, metav1.DeleteOptions{})
|
||||||
|
return provider.handleRemovalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) RemoveServicAccount(ctx context.Context, namespace string, name string) error {
|
||||||
|
err := provider.clientSet.CoreV1().ServiceAccounts(namespace).Delete(ctx, name, metav1.DeleteOptions{})
|
||||||
|
return provider.handleRemovalError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) RemovePod(ctx context.Context, namespace string, podName string) error {
|
func (provider *Provider) RemovePod(ctx context.Context, namespace string, podName string) error {
|
||||||
if isFound, err := provider.CheckPodExists(ctx, namespace, podName); err != nil {
|
err := provider.clientSet.CoreV1().Pods(namespace).Delete(ctx, podName, metav1.DeleteOptions{})
|
||||||
return err
|
return provider.handleRemovalError(err)
|
||||||
} else if !isFound {
|
}
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return provider.clientSet.CoreV1().Pods(namespace).Delete(ctx, podName, metav1.DeleteOptions{})
|
func (provider *Provider) RemoveConfigMap(ctx context.Context, namespace string, configMapName string) error {
|
||||||
|
err := provider.clientSet.CoreV1().ConfigMaps(namespace).Delete(ctx, configMapName, metav1.DeleteOptions{})
|
||||||
|
return provider.handleRemovalError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) RemoveService(ctx context.Context, namespace string, serviceName string) error {
|
func (provider *Provider) RemoveService(ctx context.Context, namespace string, serviceName string) error {
|
||||||
if isFound, err := provider.CheckServiceExists(ctx, namespace, serviceName); err != nil {
|
err := provider.clientSet.CoreV1().Services(namespace).Delete(ctx, serviceName, metav1.DeleteOptions{})
|
||||||
return err
|
return provider.handleRemovalError(err)
|
||||||
} else if !isFound {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return provider.clientSet.CoreV1().Services(namespace).Delete(ctx, serviceName, metav1.DeleteOptions{})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) RemoveDaemonSet(ctx context.Context, namespace string, daemonSetName string) error {
|
func (provider *Provider) RemoveDaemonSet(ctx context.Context, namespace string, daemonSetName string) error {
|
||||||
if isFound, err := provider.CheckDaemonSetExists(ctx, namespace, daemonSetName); err != nil {
|
err := provider.clientSet.AppsV1().DaemonSets(namespace).Delete(ctx, daemonSetName, metav1.DeleteOptions{})
|
||||||
return err
|
return provider.handleRemovalError(err)
|
||||||
} else if !isFound {
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) handleRemovalError(err error) error {
|
||||||
|
// Ignore NotFound - There is nothing to delete.
|
||||||
|
// Ignore Forbidden - Assume that a user could not have created the resource in the first place.
|
||||||
|
if k8serrors.IsNotFound(err) || k8serrors.IsForbidden(err) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider.clientSet.AppsV1().DaemonSets(namespace).Delete(ctx, daemonSetName, metav1.DeleteOptions{})
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) CheckNamespaceExists(ctx context.Context, name string) (bool, error) {
|
func (provider *Provider) CreateConfigMap(ctx context.Context, namespace string, configMapName string, data string) error {
|
||||||
listOptions := metav1.ListOptions{
|
if data == "" {
|
||||||
FieldSelector: fmt.Sprintf("metadata.name=%s", name),
|
return nil
|
||||||
Limit: 1,
|
|
||||||
}
|
|
||||||
resourceList, err := provider.clientSet.CoreV1().Namespaces().List(ctx, listOptions)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(resourceList.Items) > 0 {
|
configMapData := make(map[string]string, 0)
|
||||||
return true, nil
|
configMapData[shared.RulePolicyFileName] = data
|
||||||
|
configMap := &core.ConfigMap{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: "ConfigMap",
|
||||||
|
APIVersion: "v1",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: configMapName,
|
||||||
|
Namespace: namespace,
|
||||||
|
},
|
||||||
|
Data: configMapData,
|
||||||
}
|
}
|
||||||
|
if _, err := provider.clientSet.CoreV1().ConfigMaps(namespace).Create(ctx, configMap, metav1.CreateOptions{}); err != nil {
|
||||||
return false, nil
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) CheckClusterRoleExists(ctx context.Context, name string) (bool, error) {
|
func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespace string, daemonSetName string, podImage string, tapperPodName string, apiServerPodIp string, nodeToTappedPodIPMap map[string][]string, serviceAccountName string, resources configStructs.Resources, imagePullPolicy core.PullPolicy, mizuApiFilteringOptions *api.TrafficFilteringOptions) error {
|
||||||
listOptions := metav1.ListOptions{
|
logger.Log.Debugf("Applying %d tapper daemon sets, ns: %s, daemonSetName: %s, podImage: %s, tapperPodName: %s", len(nodeToTappedPodIPMap), namespace, daemonSetName, podImage, tapperPodName)
|
||||||
FieldSelector: fmt.Sprintf("metadata.name=%s", name),
|
|
||||||
Limit: 1,
|
|
||||||
}
|
|
||||||
resourceList, err := provider.clientSet.RbacV1().ClusterRoles().List(ctx, listOptions)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(resourceList.Items) > 0 {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *Provider) CheckClusterRoleBindingExists(ctx context.Context, name string) (bool, error) {
|
|
||||||
listOptions := metav1.ListOptions{
|
|
||||||
FieldSelector: fmt.Sprintf("metadata.name=%s", name),
|
|
||||||
Limit: 1,
|
|
||||||
}
|
|
||||||
resourceList, err := provider.clientSet.RbacV1().ClusterRoleBindings().List(ctx, listOptions)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(resourceList.Items) > 0 {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *Provider) CheckPodExists(ctx context.Context, namespace string, name string) (bool, error) {
|
|
||||||
listOptions := metav1.ListOptions{
|
|
||||||
FieldSelector: fmt.Sprintf("metadata.name=%s", name),
|
|
||||||
Limit: 1,
|
|
||||||
}
|
|
||||||
resourceList, err := provider.clientSet.CoreV1().Pods(namespace).List(ctx, listOptions)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(resourceList.Items) > 0 {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *Provider) CheckServiceExists(ctx context.Context, namespace string, name string) (bool, error) {
|
|
||||||
listOptions := metav1.ListOptions{
|
|
||||||
FieldSelector: fmt.Sprintf("metadata.name=%s", name),
|
|
||||||
Limit: 1,
|
|
||||||
}
|
|
||||||
resourceList, err := provider.clientSet.CoreV1().Services(namespace).List(ctx, listOptions)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(resourceList.Items) > 0 {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *Provider) CheckDaemonSetExists(ctx context.Context, namespace string, name string) (bool, error) {
|
|
||||||
listOptions := metav1.ListOptions{
|
|
||||||
FieldSelector: fmt.Sprintf("metadata.name=%s", name),
|
|
||||||
Limit: 1,
|
|
||||||
}
|
|
||||||
resourceList, err := provider.clientSet.AppsV1().DaemonSets(namespace).List(ctx, listOptions)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(resourceList.Items) > 0 {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespace string, daemonSetName string, podImage string, tapperPodName string, apiServerPodIp string, nodeToTappedPodIPMap map[string][]string, serviceAccountName string, tapOutgoing bool) error {
|
|
||||||
mizu.Log.Debugf("Applying %d tapper deamonsets, ns: %s, daemonSetName: %s, podImage: %s, tapperPodName: %s", len(nodeToTappedPodIPMap), namespace, daemonSetName, podImage, tapperPodName)
|
|
||||||
|
|
||||||
if len(nodeToTappedPodIPMap) == 0 {
|
if len(nodeToTappedPodIPMap) == 0 {
|
||||||
return fmt.Errorf("Daemon set %s must tap at least 1 pod", daemonSetName)
|
return fmt.Errorf("daemon set %s must tap at least 1 pod", daemonSetName)
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeToTappedPodIPMapJsonStr, err := json.Marshal(nodeToTappedPodIPMap)
|
nodeToTappedPodIPMapJsonStr, err := json.Marshal(nodeToTappedPodIPMap)
|
||||||
@@ -484,26 +490,30 @@ func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespac
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
marshaledFilteringOptions, err := json.Marshal(mizuApiFilteringOptions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
mizuCmd := []string{
|
mizuCmd := []string{
|
||||||
"./mizuagent",
|
"./mizuagent",
|
||||||
"-i", "any",
|
"-i", "any",
|
||||||
"--tap",
|
"--tap",
|
||||||
"--hardump",
|
|
||||||
"--api-server-address", fmt.Sprintf("ws://%s/wsTapper", apiServerPodIp),
|
"--api-server-address", fmt.Sprintf("ws://%s/wsTapper", apiServerPodIp),
|
||||||
}
|
"--nodefrag",
|
||||||
if tapOutgoing {
|
|
||||||
mizuCmd = append(mizuCmd, "--anydirection")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
agentContainer := applyconfcore.Container()
|
agentContainer := applyconfcore.Container()
|
||||||
agentContainer.WithName(tapperPodName)
|
agentContainer.WithName(tapperPodName)
|
||||||
agentContainer.WithImage(podImage)
|
agentContainer.WithImage(podImage)
|
||||||
agentContainer.WithImagePullPolicy(core.PullAlways)
|
agentContainer.WithImagePullPolicy(imagePullPolicy)
|
||||||
agentContainer.WithSecurityContext(applyconfcore.SecurityContext().WithPrivileged(true))
|
agentContainer.WithSecurityContext(applyconfcore.SecurityContext().WithPrivileged(true))
|
||||||
agentContainer.WithCommand(mizuCmd...)
|
agentContainer.WithCommand(mizuCmd...)
|
||||||
agentContainer.WithEnv(
|
agentContainer.WithEnv(
|
||||||
applyconfcore.EnvVar().WithName(shared.HostModeEnvVar).WithValue("1"),
|
applyconfcore.EnvVar().WithName(shared.HostModeEnvVar).WithValue("1"),
|
||||||
applyconfcore.EnvVar().WithName(shared.TappedAddressesPerNodeDictEnvVar).WithValue(string(nodeToTappedPodIPMapJsonStr)),
|
applyconfcore.EnvVar().WithName(shared.TappedAddressesPerNodeDictEnvVar).WithValue(string(nodeToTappedPodIPMapJsonStr)),
|
||||||
|
applyconfcore.EnvVar().WithName(shared.GoGCEnvVar).WithValue("12800"),
|
||||||
|
applyconfcore.EnvVar().WithName(shared.MizuFilteringOptionsEnvVar).WithValue(string(marshaledFilteringOptions)),
|
||||||
)
|
)
|
||||||
agentContainer.WithEnv(
|
agentContainer.WithEnv(
|
||||||
applyconfcore.EnvVar().WithName(shared.NodeNameEnvVar).WithValueFrom(
|
applyconfcore.EnvVar().WithName(shared.NodeNameEnvVar).WithValueFrom(
|
||||||
@@ -512,19 +522,19 @@ func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespac
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
cpuLimit, err := resource.ParseQuantity("500m")
|
cpuLimit, err := resource.ParseQuantity(resources.CpuLimit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(fmt.Sprintf("invalid cpu limit for %s container", tapperPodName))
|
return errors.New(fmt.Sprintf("invalid cpu limit for %s container", tapperPodName))
|
||||||
}
|
}
|
||||||
memLimit, err := resource.ParseQuantity("1Gi")
|
memLimit, err := resource.ParseQuantity(resources.MemoryLimit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(fmt.Sprintf("invalid memory limit for %s container", tapperPodName))
|
return errors.New(fmt.Sprintf("invalid memory limit for %s container", tapperPodName))
|
||||||
}
|
}
|
||||||
cpuRequests, err := resource.ParseQuantity("50m")
|
cpuRequests, err := resource.ParseQuantity(resources.CpuRequests)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(fmt.Sprintf("invalid cpu request for %s container", tapperPodName))
|
return errors.New(fmt.Sprintf("invalid cpu request for %s container", tapperPodName))
|
||||||
}
|
}
|
||||||
memRequests, err := resource.ParseQuantity("50Mi")
|
memRequests, err := resource.ParseQuantity(resources.MemoryRequests)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(fmt.Sprintf("invalid memory request for %s container", tapperPodName))
|
return errors.New(fmt.Sprintf("invalid memory request for %s container", tapperPodName))
|
||||||
}
|
}
|
||||||
@@ -588,39 +598,78 @@ func (provider *Provider) ApplyMizuTapperDaemonSet(ctx context.Context, namespac
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Provider) GetAllRunningPodsMatchingRegex(ctx context.Context, regex *regexp.Regexp, namespace string) ([]core.Pod, error) {
|
func (provider *Provider) ListAllPodsMatchingRegex(ctx context.Context, regex *regexp.Regexp, namespaces []string) ([]core.Pod, error) {
|
||||||
pods, err := provider.clientSet.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
|
var pods []core.Pod
|
||||||
if err != nil {
|
for _, namespace := range namespaces {
|
||||||
return nil, err
|
namespacePods, err := provider.clientSet.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get pods in ns: [%s], %w", namespace, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pods = append(pods, namespacePods.Items...)
|
||||||
}
|
}
|
||||||
|
|
||||||
matchingPods := make([]core.Pod, 0)
|
matchingPods := make([]core.Pod, 0)
|
||||||
for _, pod := range pods.Items {
|
for _, pod := range pods {
|
||||||
if regex.MatchString(pod.Name) && isPodRunning(&pod) {
|
if regex.MatchString(pod.Name) {
|
||||||
matchingPods = append(matchingPods, pod)
|
matchingPods = append(matchingPods, pod)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return matchingPods, err
|
return matchingPods, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getClientSet(config *restclient.Config) *kubernetes.Clientset {
|
func (provider *Provider) ListAllRunningPodsMatchingRegex(ctx context.Context, regex *regexp.Regexp, namespaces []string) ([]core.Pod, error) {
|
||||||
|
pods, err := provider.ListAllPodsMatchingRegex(ctx, regex, namespaces)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
matchingPods := make([]core.Pod, 0)
|
||||||
|
for _, pod := range pods {
|
||||||
|
if isPodRunning(&pod) {
|
||||||
|
matchingPods = append(matchingPods, pod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matchingPods, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) GetPodLogs(ctx context.Context, namespace string, podName string) (string, error) {
|
||||||
|
podLogOpts := core.PodLogOptions{}
|
||||||
|
req := provider.clientSet.CoreV1().Pods(namespace).GetLogs(podName, &podLogOpts)
|
||||||
|
podLogs, err := req.Stream(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error opening log stream on ns: %s, pod: %s, %w", namespace, podName, err)
|
||||||
|
}
|
||||||
|
defer podLogs.Close()
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if _, err = io.Copy(buf, podLogs); err != nil {
|
||||||
|
return "", fmt.Errorf("error copy information from podLogs to buf, ns: %s, pod: %s, %w", namespace, podName, err)
|
||||||
|
}
|
||||||
|
str := buf.String()
|
||||||
|
return str, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *Provider) GetNamespaceEvents(ctx context.Context, namespace string) (string, error) {
|
||||||
|
eventsOpts := metav1.ListOptions{}
|
||||||
|
eventList, err := provider.clientSet.CoreV1().Events(namespace).List(ctx, eventsOpts)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error getting events on ns: %s, %w", namespace, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return eventList.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getClientSet(config *restclient.Config) (*kubernetes.Clientset, error) {
|
||||||
clientSet, err := kubernetes.NewForConfig(config)
|
clientSet, err := kubernetes.NewForConfig(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err.Error())
|
return nil, err
|
||||||
}
|
}
|
||||||
return clientSet
|
|
||||||
|
return clientSet, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadKubernetesConfiguration(kubeConfigPath string) clientcmd.ClientConfig {
|
func loadKubernetesConfiguration(kubeConfigPath string) clientcmd.ClientConfig {
|
||||||
if kubeConfigPath == "" {
|
logger.Log.Debugf("Using kube config %s", kubeConfigPath)
|
||||||
kubeConfigPath = os.Getenv("KUBECONFIG")
|
|
||||||
}
|
|
||||||
|
|
||||||
if kubeConfigPath == "" {
|
|
||||||
home := homedir.HomeDir()
|
|
||||||
kubeConfigPath = filepath.Join(home, ".kube", "config")
|
|
||||||
}
|
|
||||||
|
|
||||||
mizu.Log.Debugf("Using kube config %s", kubeConfigPath)
|
|
||||||
configPathList := filepath.SplitList(kubeConfigPath)
|
configPathList := filepath.SplitList(kubeConfigPath)
|
||||||
configLoadingRules := &clientcmd.ClientConfigLoadingRules{}
|
configLoadingRules := &clientcmd.ClientConfigLoadingRules{}
|
||||||
if len(configPathList) <= 1 {
|
if len(configPathList) <= 1 {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package kubernetes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/up9inc/mizu/cli/mizu"
|
"github.com/up9inc/mizu/cli/logger"
|
||||||
"k8s.io/kubectl/pkg/proxy"
|
"k8s.io/kubectl/pkg/proxy"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -14,7 +14,7 @@ const k8sProxyApiPrefix = "/"
|
|||||||
const mizuServicePort = 80
|
const mizuServicePort = 80
|
||||||
|
|
||||||
func StartProxy(kubernetesProvider *Provider, mizuPort uint16, mizuNamespace string, mizuServiceName string) error {
|
func StartProxy(kubernetesProvider *Provider, mizuPort uint16, mizuNamespace string, mizuServiceName string) error {
|
||||||
mizu.Log.Debugf("Starting proxy. namespace: [%v], service name: [%s], port: [%v]", mizuNamespace, mizuServiceName, mizuPort)
|
logger.Log.Debugf("Starting proxy. namespace: [%v], service name: [%s], port: [%v]", mizuNamespace, mizuServiceName, mizuPort)
|
||||||
filter := &proxy.FilterServer{
|
filter := &proxy.FilterServer{
|
||||||
AcceptPaths: proxy.MakeRegexpArrayOrDie(proxy.DefaultPathAcceptRE),
|
AcceptPaths: proxy.MakeRegexpArrayOrDie(proxy.DefaultPathAcceptRE),
|
||||||
RejectPaths: proxy.MakeRegexpArrayOrDie(proxy.DefaultPathRejectRE),
|
RejectPaths: proxy.MakeRegexpArrayOrDie(proxy.DefaultPathRejectRE),
|
||||||
@@ -39,6 +39,7 @@ func StartProxy(kubernetesProvider *Provider, mizuPort uint16, mizuNamespace str
|
|||||||
server := http.Server{
|
server := http.Server{
|
||||||
Handler: mux,
|
Handler: mux,
|
||||||
}
|
}
|
||||||
|
|
||||||
return server.Serve(l)
|
return server.Serve(l)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,49 +4,64 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sync"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
)
|
)
|
||||||
|
|
||||||
func FilteredWatch(ctx context.Context, watcher watch.Interface, podFilter *regexp.Regexp) (chan *corev1.Pod, chan *corev1.Pod, chan *corev1.Pod, chan error) {
|
func FilteredWatch(ctx context.Context, kubernetesProvider *Provider, targetNamespaces []string, podFilter *regexp.Regexp) (chan *corev1.Pod, chan *corev1.Pod, chan *corev1.Pod, chan error) {
|
||||||
addedChan := make(chan *corev1.Pod)
|
addedChan := make(chan *corev1.Pod)
|
||||||
modifiedChan := make(chan *corev1.Pod)
|
modifiedChan := make(chan *corev1.Pod)
|
||||||
removedChan := make(chan *corev1.Pod)
|
removedChan := make(chan *corev1.Pod)
|
||||||
errorChan := make(chan error)
|
errorChan := make(chan error)
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case e := <-watcher.ResultChan():
|
|
||||||
|
|
||||||
if e.Object == nil {
|
var wg sync.WaitGroup
|
||||||
errorChan <- errors.New("kubernetes pod watch failed")
|
|
||||||
|
for _, targetNamespace := range targetNamespaces {
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go func(targetNamespace string) {
|
||||||
|
defer wg.Done()
|
||||||
|
watcher := kubernetesProvider.GetPodWatcher(ctx, targetNamespace)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case e := <-watcher.ResultChan():
|
||||||
|
if e.Object == nil {
|
||||||
|
errorChan <- errors.New("kubernetes pod watch failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pod := e.Object.(*corev1.Pod)
|
||||||
|
|
||||||
|
if !podFilter.MatchString(pod.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch e.Type {
|
||||||
|
case watch.Added:
|
||||||
|
addedChan <- pod
|
||||||
|
case watch.Modified:
|
||||||
|
modifiedChan <- pod
|
||||||
|
case watch.Deleted:
|
||||||
|
removedChan <- pod
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
watcher.Stop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pod := e.Object.(*corev1.Pod)
|
|
||||||
|
|
||||||
if !podFilter.MatchString(pod.Name) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch e.Type {
|
|
||||||
case watch.Added:
|
|
||||||
addedChan <- pod
|
|
||||||
case watch.Modified:
|
|
||||||
modifiedChan <- pod
|
|
||||||
case watch.Deleted:
|
|
||||||
removedChan <- pod
|
|
||||||
}
|
|
||||||
case <-ctx.Done():
|
|
||||||
watcher.Stop()
|
|
||||||
close(addedChan)
|
|
||||||
close(modifiedChan)
|
|
||||||
close(removedChan)
|
|
||||||
close(errorChan)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}(targetNamespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
wg.Wait()
|
||||||
|
close(addedChan)
|
||||||
|
close(modifiedChan)
|
||||||
|
close(removedChan)
|
||||||
|
close(errorChan)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return addedChan, modifiedChan, removedChan, errorChan
|
return addedChan, modifiedChan, removedChan, errorChan
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package mizu
|
package logger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"github.com/op/go-logging"
|
"github.com/op/go-logging"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
)
|
)
|
||||||
@@ -13,15 +13,15 @@ var format = logging.MustStringFormatter(
|
|||||||
`%{time} %{level:.5s} ▶ %{pid} %{shortfile} %{shortfunc} ▶ %{message}`,
|
`%{time} %{level:.5s} ▶ %{pid} %{shortfile} %{shortfunc} ▶ %{message}`,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func GetLogFilePath() string {
|
||||||
|
return path.Join(mizu.GetMizuFolderPath(), "mizu_cli.log")
|
||||||
|
}
|
||||||
|
|
||||||
func InitLogger() {
|
func InitLogger() {
|
||||||
mizuDirPath := getMizuFolderPath()
|
logPath := GetLogFilePath()
|
||||||
if err := os.MkdirAll(mizuDirPath, os.ModePerm); err != nil {
|
|
||||||
panic(fmt.Sprintf("Failed creating mizu dir: %v, err %v", mizuDirPath, err))
|
|
||||||
}
|
|
||||||
logPath := path.Join(mizuDirPath, "log.log")
|
|
||||||
f, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
f, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("Failed mizu log file: %v, err %v", logPath, err))
|
Log.Infof("Failed to open mizu log file: %v, err %v", logPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileLog := logging.NewLogBackend(f, "", 0)
|
fileLog := logging.NewLogBackend(f, "", 0)
|
||||||
@@ -35,5 +35,5 @@ func InitLogger() {
|
|||||||
logging.SetBackend(backend1Leveled, backend2Formatter)
|
logging.SetBackend(backend1Leveled, backend2Formatter)
|
||||||
|
|
||||||
Log.Debugf("\n\n\n")
|
Log.Debugf("\n\n\n")
|
||||||
Log.Debugf("Running mizu version %v", SemVer)
|
Log.Debugf("Running mizu version %v", mizu.SemVer)
|
||||||
}
|
}
|
||||||
@@ -2,10 +2,9 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/up9inc/mizu/cli/cmd"
|
"github.com/up9inc/mizu/cli/cmd"
|
||||||
"github.com/up9inc/mizu/cli/mizu"
|
"github.com/up9inc/mizu/cli/mizu/goUtils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
mizu.InitLogger()
|
goUtils.HandleExcWrapper(cmd.Execute)
|
||||||
cmd.Execute()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,199 +0,0 @@
|
|||||||
package mizu
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/creasty/defaults"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
"github.com/up9inc/mizu/cli/uiUtils"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
Separator = "="
|
|
||||||
SetCommandName = "set"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Config = ConfigStruct{}
|
|
||||||
|
|
||||||
func InitConfig(cmd *cobra.Command) error {
|
|
||||||
if err := defaults.Set(&Config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Flags().Visit(initFlag)
|
|
||||||
|
|
||||||
finalConfigPrettified, _ := uiUtils.PrettyJson(Config)
|
|
||||||
Log.Debugf("Init config finished\n Final config: %v", finalConfigPrettified)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetConfigWithDefaults() (string, error) {
|
|
||||||
defaultConf := ConfigStruct{}
|
|
||||||
if err := defaults.Set(&defaultConf); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return uiUtils.PrettyYaml(defaultConf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetConfigFilePath() string {
|
|
||||||
return path.Join(getMizuFolderPath(), "config.yaml")
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeConfigFile() error {
|
|
||||||
reader, openErr := os.Open(GetConfigFilePath())
|
|
||||||
if openErr != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, readErr := ioutil.ReadAll(reader)
|
|
||||||
if readErr != nil {
|
|
||||||
return readErr
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := yaml.Unmarshal(buf, &Config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
Log.Debugf("Found config file, merged to default options")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func initFlag(f *pflag.Flag) {
|
|
||||||
configElem := reflect.ValueOf(&Config).Elem()
|
|
||||||
|
|
||||||
sliceValue, isSliceValue := f.Value.(pflag.SliceValue)
|
|
||||||
if !isSliceValue {
|
|
||||||
mergeFlagValue(configElem, f.Name, f.Value.String())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.Name == SetCommandName {
|
|
||||||
if setError := mergeSetFlag(sliceValue.GetSlice()); setError != nil {
|
|
||||||
Log.Infof(uiUtils.Red, "Invalid set argument")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mergeFlagValues(configElem, f.Name, sliceValue.GetSlice())
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeSetFlag(setValues []string) error {
|
|
||||||
configElem := reflect.ValueOf(&Config).Elem()
|
|
||||||
|
|
||||||
for _, setValue := range setValues {
|
|
||||||
if !strings.Contains(setValue, Separator) {
|
|
||||||
return errors.New(fmt.Sprintf("invalid set argument %s", setValue))
|
|
||||||
}
|
|
||||||
|
|
||||||
split := strings.SplitN(setValue, Separator, 2)
|
|
||||||
if len(split) != 2 {
|
|
||||||
return errors.New(fmt.Sprintf("invalid set argument %s", setValue))
|
|
||||||
}
|
|
||||||
|
|
||||||
argumentKey, argumentValue := split[0], split[1]
|
|
||||||
mergeFlagValue(configElem, argumentKey, argumentValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
if currentField.Type.Kind() == reflect.Struct {
|
|
||||||
mergeFlagValue(currentFieldByName, flagKey, flagValue)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if currentField.Tag.Get("yaml") != flagKey {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
flagValueKind := currentField.Type.Kind()
|
|
||||||
|
|
||||||
parsedValue, err := getParsedValue(flagValueKind, flagValue)
|
|
||||||
if err != nil {
|
|
||||||
Log.Warningf(uiUtils.Red, fmt.Sprintf("Invalid value %v for key %s, expected %s", flagValue, flagKey, flagValueKind))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
currentFieldByName.Set(parsedValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeFlagValues(currentElem reflect.Value, flagKey string, flagValues []string) {
|
|
||||||
for i := 0; i < currentElem.NumField(); i++ {
|
|
||||||
currentField := currentElem.Type().Field(i)
|
|
||||||
currentFieldByName := currentElem.FieldByName(currentField.Name)
|
|
||||||
|
|
||||||
if currentField.Type.Kind() == reflect.Struct {
|
|
||||||
mergeFlagValues(currentFieldByName, flagKey, flagValues)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if currentField.Tag.Get("yaml") != flagKey {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
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 key %s, expected %s", flagValue, flagKey, flagValueKind))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedValues = reflect.Append(parsedValues, parsedValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
currentFieldByName.Set(parsedValues)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getParsedValue(kind reflect.Kind, value string) (reflect.Value, error) {
|
|
||||||
switch kind {
|
|
||||||
case reflect.String:
|
|
||||||
return reflect.ValueOf(value), nil
|
|
||||||
case reflect.Bool:
|
|
||||||
boolArgumentValue, err := strconv.ParseBool(value)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return reflect.ValueOf(boolArgumentValue), nil
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
intArgumentValue, err := strconv.ParseInt(value, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return reflect.ValueOf(intArgumentValue), nil
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
||||||
uintArgumentValue, err := strconv.ParseUint(value, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return reflect.ValueOf(uintArgumentValue), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return reflect.ValueOf(nil), errors.New("value to parse does not match type")
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package mizu
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/up9inc/mizu/cli/mizu/configStructs"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConfigStruct struct {
|
|
||||||
Tap configStructs.TapConfig `yaml:"tap"`
|
|
||||||
Fetch configStructs.FetchConfig `yaml:"fetch"`
|
|
||||||
Version configStructs.VersionConfig `yaml:"version"`
|
|
||||||
View configStructs.ViewConfig `yaml:"view"`
|
|
||||||
MizuImage string `yaml:"mizu-image"`
|
|
||||||
Telemetry bool `yaml:"telemetry" default:"true"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (config *ConfigStruct) SetDefaults() {
|
|
||||||
config.MizuImage = fmt.Sprintf("gcr.io/up9-docker-hub/mizu/%s:%s", Branch, SemVer)
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package configStructs
|
|
||||||
|
|
||||||
const (
|
|
||||||
DirectoryFetchName = "directory"
|
|
||||||
FromTimestampFetchName = "from"
|
|
||||||
ToTimestampFetchName = "to"
|
|
||||||
MizuPortFetchName = "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"`
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
package configStructs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/up9inc/mizu/shared/units"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
GuiPortTapName = "gui-port"
|
|
||||||
NamespaceTapName = "namespace"
|
|
||||||
AnalysisTapName = "analysis"
|
|
||||||
AllNamespacesTapName = "all-namespaces"
|
|
||||||
KubeConfigPathTapName = "kube-config"
|
|
||||||
PlainTextFilterRegexesTapName = "regex-masking"
|
|
||||||
HideHealthChecksTapName = "hide-healthchecks"
|
|
||||||
DisableRedactionTapName = "no-redact"
|
|
||||||
HumanMaxEntriesDBSizeTapName = "max-entries-db-size"
|
|
||||||
DirectionTapName = "direction"
|
|
||||||
DryRunTapName = "dry-run"
|
|
||||||
)
|
|
||||||
|
|
||||||
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"`
|
|
||||||
Namespace string `yaml:"namespace"`
|
|
||||||
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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (config *TapConfig) PodRegex() *regexp.Regexp {
|
|
||||||
podRegex, _ := regexp.Compile(config.PodRegexStr)
|
|
||||||
return podRegex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (config *TapConfig) TapOutgoing() bool {
|
|
||||||
directionLowerCase := strings.ToLower(config.Direction)
|
|
||||||
if directionLowerCase == "any" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (config *TapConfig) MaxEntriesDBSizeBytes() int64 {
|
|
||||||
maxEntriesDBSizeBytes, _ := units.HumanReadableToBytes(config.HumanMaxEntriesDBSize)
|
|
||||||
return maxEntriesDBSizeBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (config *TapConfig) Validate() error {
|
|
||||||
_, compileErr := regexp.Compile(config.PodRegexStr)
|
|
||||||
if compileErr != nil {
|
|
||||||
return errors.New(fmt.Sprintf("%s is not a valid regex %s", config.PodRegexStr, compileErr))
|
|
||||||
}
|
|
||||||
|
|
||||||
_, parseHumanDataSizeErr := units.HumanReadableToBytes(config.HumanMaxEntriesDBSize)
|
|
||||||
if parseHumanDataSizeErr != nil {
|
|
||||||
return errors.New(fmt.Sprintf("Could not parse --%s value %s", HumanMaxEntriesDBSizeTapName, config.HumanMaxEntriesDBSize))
|
|
||||||
}
|
|
||||||
|
|
||||||
directionLowerCase := strings.ToLower(config.Direction)
|
|
||||||
if directionLowerCase != "any" && directionLowerCase != "in" {
|
|
||||||
return errors.New(fmt.Sprintf("%s is not a valid value for flag --%s. Acceptable values are in/any.", config.Direction, DirectionTapName))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package configStructs
|
|
||||||
|
|
||||||
const (
|
|
||||||
GuiPortViewName = "gui-port"
|
|
||||||
KubeConfigPathViewName = "kube-config"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ViewConfig struct {
|
|
||||||
GuiPort uint16 `yaml:"gui-port" default:"8899"`
|
|
||||||
KubeConfigPath string `yaml:"kube-config"`
|
|
||||||
}
|
|
||||||
@@ -14,17 +14,20 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ApiServerPodName = "mizu-api-server"
|
MizuResourcesPrefix = "mizu-"
|
||||||
ClusterRoleBindingName = "mizu-cluster-role-binding"
|
ApiServerPodName = MizuResourcesPrefix + "api-server"
|
||||||
ClusterRoleName = "mizu-cluster-role"
|
ClusterRoleBindingName = MizuResourcesPrefix + "cluster-role-binding"
|
||||||
|
ClusterRoleName = MizuResourcesPrefix + "cluster-role"
|
||||||
K8sAllNamespaces = ""
|
K8sAllNamespaces = ""
|
||||||
ResourcesNamespace = "mizu"
|
RoleBindingName = MizuResourcesPrefix + "role-binding"
|
||||||
ServiceAccountName = "mizu-service-account"
|
RoleName = MizuResourcesPrefix + "role"
|
||||||
TapperDaemonSetName = "mizu-tapper-daemon-set"
|
ServiceAccountName = MizuResourcesPrefix + "service-account"
|
||||||
TapperPodName = "mizu-tapper"
|
TapperDaemonSetName = MizuResourcesPrefix + "tapper-daemon-set"
|
||||||
|
TapperPodName = MizuResourcesPrefix + "tapper"
|
||||||
|
ConfigMapName = MizuResourcesPrefix + "policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getMizuFolderPath() string {
|
func GetMizuFolderPath() string {
|
||||||
home, homeDirErr := os.UserHomeDir()
|
home, homeDirErr := os.UserHomeDir()
|
||||||
if homeDirErr != nil {
|
if homeDirErr != nil {
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
package mizu
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
"github.com/up9inc/mizu/shared"
|
|
||||||
core "k8s.io/api/core/v1"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ControlSocket struct {
|
|
||||||
connection *websocket.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateControlSocket(socketServerAddress string) (*ControlSocket, error) {
|
|
||||||
connection, err := shared.ConnectToSocketServer(socketServerAddress, 30, 2 * time.Second, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return &ControlSocket{connection: connection}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (controlSocket *ControlSocket) SendNewTappedPodsListMessage(pods []core.Pod) error {
|
|
||||||
podInfos := make([]shared.PodInfo, 0)
|
|
||||||
for _, pod := range pods {
|
|
||||||
podInfos = append(podInfos, shared.PodInfo{Name: pod.Name, Namespace: pod.Namespace})
|
|
||||||
}
|
|
||||||
tapStatus := shared.TapStatus{Pods: podInfos}
|
|
||||||
socketMessage := shared.CreateWebSocketStatusMessage(tapStatus)
|
|
||||||
|
|
||||||
jsonMessage, err := json.Marshal(socketMessage)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = controlSocket.connection.WriteMessage(websocket.TextMessage, jsonMessage)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
26
cli/mizu/fsUtils/dirUtils.go
Normal file
26
cli/mizu/fsUtils/dirUtils.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package fsUtils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func EnsureDir(dirName string) error {
|
||||||
|
err := os.Mkdir(dirName, 0700)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if os.IsExist(err) {
|
||||||
|
// check that the existing path is a directory
|
||||||
|
info, err := os.Stat(dirName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !info.IsDir() {
|
||||||
|
return errors.New(fmt.Sprintf("path exists but is not a directory: %s", dirName))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
77
cli/mizu/fsUtils/mizuLogsUtils.go
Normal file
77
cli/mizu/fsUtils/mizuLogsUtils.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package fsUtils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/up9inc/mizu/cli/config"
|
||||||
|
"github.com/up9inc/mizu/cli/kubernetes"
|
||||||
|
"github.com/up9inc/mizu/cli/logger"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DumpLogs(ctx context.Context, provider *kubernetes.Provider, filePath string) error {
|
||||||
|
podExactRegex := regexp.MustCompile("^" + mizu.MizuResourcesPrefix)
|
||||||
|
pods, err := provider.ListAllPodsMatchingRegex(ctx, podExactRegex, []string{config.Config.MizuResourcesNamespace})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pods) == 0 {
|
||||||
|
return fmt.Errorf("no mizu pods found in namespace %s", config.Config.MizuResourcesNamespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
newZipFile, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer newZipFile.Close()
|
||||||
|
zipWriter := zip.NewWriter(newZipFile)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
events, err := provider.GetNamespaceEvents(ctx, config.Config.MizuResourcesNamespace)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Debugf("Failed to get k8b events, %v", err)
|
||||||
|
} else {
|
||||||
|
logger.Log.Debugf("Successfully read events for k8b namespace: %s", config.Config.MizuResourcesNamespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := AddStrToZip(zipWriter, events, fmt.Sprintf("%s_events.log", config.Config.MizuResourcesNamespace)); err != nil {
|
||||||
|
logger.Log.Debugf("Failed write logs, %v", err)
|
||||||
|
} else {
|
||||||
|
logger.Log.Debugf("Successfully added events for k8b namespace: %s", config.Config.MizuResourcesNamespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := AddFileToZip(zipWriter, config.Config.ConfigFilePath); err != nil {
|
||||||
|
logger.Log.Debugf("Failed write file, %v", err)
|
||||||
|
} else {
|
||||||
|
logger.Log.Debugf("Successfully added file %s", config.Config.ConfigFilePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := AddFileToZip(zipWriter, logger.GetLogFilePath()); err != nil {
|
||||||
|
logger.Log.Debugf("Failed write file, %v", err)
|
||||||
|
} else {
|
||||||
|
logger.Log.Debugf("Successfully added file %s", logger.GetLogFilePath())
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Infof("You can find the zip file with all logs in %s\n", filePath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
114
cli/mizu/fsUtils/zipUtils.go
Normal file
114
cli/mizu/fsUtils/zipUtils.go
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package fsUtils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"fmt"
|
||||||
|
"github.com/up9inc/mizu/cli/logger"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddFileToZip(zipWriter *zip.Writer, filename string) error {
|
||||||
|
|
||||||
|
fileToZip, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open file %s, %w", filename, err)
|
||||||
|
}
|
||||||
|
defer fileToZip.Close()
|
||||||
|
|
||||||
|
// Get the file information
|
||||||
|
info, err := fileToZip.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get file information %s, %w", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
header, err := zip.FileInfoHeader(info)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using FileInfoHeader() above only uses the basename of the file. If we want
|
||||||
|
// to preserve the folder structure we can overwrite this with the full path.
|
||||||
|
header.Name = filepath.Base(filename)
|
||||||
|
|
||||||
|
// Change to deflate to gain better compression
|
||||||
|
// see http://golang.org/pkg/archive/zip/#pkg-constants
|
||||||
|
header.Method = zip.Deflate
|
||||||
|
|
||||||
|
writer, err := zipWriter.CreateHeader(header)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create header in zip for %s, %w", filename, err)
|
||||||
|
}
|
||||||
|
_, err = io.Copy(writer, fileToZip)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddStrToZip(writer *zip.Writer, logs string, fileName string) error {
|
||||||
|
if zipFile, err := writer.Create(fileName); err != nil {
|
||||||
|
return fmt.Errorf("couldn't create a log file inside zip for %s, %w", fileName, err)
|
||||||
|
} else {
|
||||||
|
if _, err = zipFile.Write([]byte(logs)); err != nil {
|
||||||
|
return fmt.Errorf("couldn't write logs to zip file: %s, %w", fileName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Unzip(reader *zip.Reader, dest string) error {
|
||||||
|
dest, _ = filepath.Abs(dest)
|
||||||
|
_ = os.MkdirAll(dest, os.ModePerm)
|
||||||
|
|
||||||
|
// Closure to address file descriptors issue with all the deferred .Close() methods
|
||||||
|
extractAndWriteFile := func(f *zip.File) error {
|
||||||
|
rc, err := f.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := rc.Close(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
path := filepath.Join(dest, f.Name)
|
||||||
|
|
||||||
|
// Check for ZipSlip (Directory traversal)
|
||||||
|
if !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) {
|
||||||
|
return fmt.Errorf("illegal file path: %s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.FileInfo().IsDir() {
|
||||||
|
_ = os.MkdirAll(path, f.Mode())
|
||||||
|
} else {
|
||||||
|
_ = os.MkdirAll(filepath.Dir(path), f.Mode())
|
||||||
|
logger.Log.Infof("writing HAR file [ %v ]", path)
|
||||||
|
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
logger.Log.Info(" done")
|
||||||
|
}()
|
||||||
|
|
||||||
|
_, err = io.Copy(f, rc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range reader.File {
|
||||||
|
err := extractAndWriteFile(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
25
cli/mizu/goUtils/funcWrappers.go
Normal file
25
cli/mizu/goUtils/funcWrappers.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package goUtils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/up9inc/mizu/cli/logger"
|
||||||
|
"reflect"
|
||||||
|
"runtime/debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleExcWrapper(fn interface{}, params ...interface{}) (result []reflect.Value) {
|
||||||
|
defer func() {
|
||||||
|
if panicMessage := recover(); panicMessage != nil {
|
||||||
|
stack := debug.Stack()
|
||||||
|
logger.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)
|
||||||
|
}
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package mizu
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
const telemetryUrl = "https://us-east4-up9-prod.cloudfunctions.net/mizu-telemetry"
|
|
||||||
|
|
||||||
func ReportRun(cmd string, args interface{}) {
|
|
||||||
if !Config.Telemetry {
|
|
||||||
Log.Debugf("not reporting due to config value")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
argsBytes, _ := json.Marshal(args)
|
|
||||||
argsMap := map[string]string{
|
|
||||||
"telemetry_type": "execution",
|
|
||||||
"cmd": cmd,
|
|
||||||
"args": string(argsBytes),
|
|
||||||
"component": "mizu_cli",
|
|
||||||
"BuildTimestamp": BuildTimestamp,
|
|
||||||
"Branch": Branch,
|
|
||||||
"version": SemVer}
|
|
||||||
argsMap["message"] = fmt.Sprintf("mizu %v - %v", argsMap["cmd"], string(argsBytes))
|
|
||||||
|
|
||||||
jsonValue, _ := json.Marshal(argsMap)
|
|
||||||
|
|
||||||
if resp, err := http.Post(telemetryUrl, "application/json", bytes.NewBuffer(jsonValue)); err != nil {
|
|
||||||
Log.Debugf("error sending telemetry err: %v, response %v", err, resp)
|
|
||||||
} else {
|
|
||||||
Log.Debugf("Successfully reported telemetry")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
84
cli/mizu/version/versionCheck.go
Normal file
84
cli/mizu/version/versionCheck.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/up9inc/mizu/cli/apiserver"
|
||||||
|
"github.com/up9inc/mizu/cli/logger"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-github/v37/github"
|
||||||
|
"github.com/up9inc/mizu/cli/uiUtils"
|
||||||
|
"github.com/up9inc/mizu/shared/semver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CheckVersionCompatibility() (bool, error) {
|
||||||
|
apiSemVer, err := apiserver.Provider.GetVersion()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if semver.SemVersion(apiSemVer).Major() == semver.SemVersion(mizu.SemVer).Major() &&
|
||||||
|
semver.SemVersion(apiSemVer).Minor() == semver.SemVersion(mizu.SemVer).Minor() {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Errorf(uiUtils.Red, fmt.Sprintf("cli version (%s) is not compatible with api version (%s)", mizu.SemVer, apiSemVer))
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckNewerVersion(versionChan chan string) {
|
||||||
|
logger.Log.Debugf("Checking for newer version...")
|
||||||
|
start := time.Now()
|
||||||
|
client := github.NewClient(nil)
|
||||||
|
latestRelease, _, err := client.Repositories.GetLatestRelease(context.Background(), "up9inc", "mizu")
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Debugf("[ERROR] Failed to get latest release")
|
||||||
|
versionChan <- ""
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
versionFileUrl := ""
|
||||||
|
for _, asset := range latestRelease.Assets {
|
||||||
|
if *asset.Name == "version.txt" {
|
||||||
|
versionFileUrl = *asset.BrowserDownloadURL
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if versionFileUrl == "" {
|
||||||
|
logger.Log.Debugf("[ERROR] Version file not found in the latest release")
|
||||||
|
versionChan <- ""
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := http.Get(versionFileUrl)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Debugf("[ERROR] Failed to get the version file %v", err)
|
||||||
|
versionChan <- ""
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Debugf("[ERROR] Failed to read the version file -> %v", err)
|
||||||
|
versionChan <- ""
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gitHubVersion := string(data)
|
||||||
|
gitHubVersion = gitHubVersion[:len(gitHubVersion)-1]
|
||||||
|
|
||||||
|
gitHubVersionSemVer := semver.SemVersion(gitHubVersion)
|
||||||
|
currentSemVer := semver.SemVersion(mizu.SemVer)
|
||||||
|
logger.Log.Debugf("Finished version validation, github version %v, current version %v, took %v", gitHubVersion, currentSemVer, time.Since(start))
|
||||||
|
|
||||||
|
if gitHubVersionSemVer.GreaterThan(currentSemVer) {
|
||||||
|
versionChan <- fmt.Sprintf("Update available! %v -> %v (curl -Lo mizu %v/mizu_%s_amd64 && chmod 755 mizu)", mizu.SemVer, gitHubVersion, *latestRelease.HTMLURL, runtime.GOOS)
|
||||||
|
} else {
|
||||||
|
versionChan <- ""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
package mizu
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"github.com/google/go-github/v37/github"
|
|
||||||
"github.com/up9inc/mizu/cli/uiUtils"
|
|
||||||
"github.com/up9inc/mizu/shared"
|
|
||||||
"github.com/up9inc/mizu/shared/semver"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getApiVersion(port uint16) (string, error) {
|
|
||||||
versionUrl, _ := url.Parse(fmt.Sprintf("http://localhost:%d/mizu/metadata/version", port))
|
|
||||||
req := &http.Request{
|
|
||||||
Method: http.MethodGet,
|
|
||||||
URL: versionUrl,
|
|
||||||
}
|
|
||||||
statusResp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer statusResp.Body.Close()
|
|
||||||
|
|
||||||
versionResponse := &shared.VersionResponse{}
|
|
||||||
if err := json.NewDecoder(statusResp.Body).Decode(&versionResponse); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return versionResponse.SemVer, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckVersionCompatibility(port uint16) (bool, error) {
|
|
||||||
apiSemVer, err := getApiVersion(port)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if semver.SemVersion(apiSemVer).Major() == semver.SemVersion(SemVer).Major() &&
|
|
||||||
semver.SemVersion(apiSemVer).Minor() == semver.SemVersion(SemVer).Minor() {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Infof(uiUtils.Red, fmt.Sprintf("cli version (%s) is not compatible with api version (%s)", SemVer, apiSemVer))
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckNewerVersion() {
|
|
||||||
Log.Debugf("Checking for newer version...")
|
|
||||||
start := time.Now()
|
|
||||||
client := github.NewClient(nil)
|
|
||||||
latestRelease, _, err := client.Repositories.GetLatestRelease(context.Background(), "up9inc", "mizu")
|
|
||||||
if err != nil {
|
|
||||||
Log.Debugf("[ERROR] Failed to get latest release")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
versionFileUrl := ""
|
|
||||||
for _, asset := range latestRelease.Assets {
|
|
||||||
if *asset.Name == "version.txt" {
|
|
||||||
versionFileUrl = *asset.BrowserDownloadURL
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if versionFileUrl == "" {
|
|
||||||
Log.Debugf("[ERROR] Version file not found in the latest release")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := http.Get(versionFileUrl)
|
|
||||||
if err != nil {
|
|
||||||
Log.Debugf("[ERROR] Failed to get the version file %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(res.Body)
|
|
||||||
res.Body.Close()
|
|
||||||
if err != nil {
|
|
||||||
Log.Debugf("[ERROR] Failed to read the version file -> %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
gitHubVersion := string(data)
|
|
||||||
gitHubVersion = gitHubVersion[:len(gitHubVersion)-1]
|
|
||||||
Log.Debugf("Finished version validation, took %v", time.Since(start))
|
|
||||||
if SemVer < gitHubVersion {
|
|
||||||
Log.Infof(uiUtils.Yellow, fmt.Sprintf("Update available! %v -> %v (%v)", SemVer, gitHubVersion, *latestRelease.HTMLURL))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
93
cli/telemetry/telemetry.go
Normal file
93
cli/telemetry/telemetry.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package telemetry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/denisbrodbeck/machineid"
|
||||||
|
"github.com/up9inc/mizu/cli/apiserver"
|
||||||
|
"github.com/up9inc/mizu/cli/config"
|
||||||
|
"github.com/up9inc/mizu/cli/logger"
|
||||||
|
"github.com/up9inc/mizu/cli/mizu"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const telemetryUrl = "https://us-east4-up9-prod.cloudfunctions.net/mizu-telemetry"
|
||||||
|
|
||||||
|
func ReportRun(cmd string, args interface{}) {
|
||||||
|
if !shouldRunTelemetry() {
|
||||||
|
logger.Log.Debugf("not reporting telemetry")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
argsBytes, _ := json.Marshal(args)
|
||||||
|
argsMap := map[string]interface{}{
|
||||||
|
"cmd": cmd,
|
||||||
|
"args": string(argsBytes),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sendTelemetry("Execution", argsMap); err != nil {
|
||||||
|
logger.Log.Debug(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Debugf("successfully reported telemetry for cmd %v", cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReportAPICalls() {
|
||||||
|
if !shouldRunTelemetry() {
|
||||||
|
logger.Log.Debugf("not reporting telemetry")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
generalStats, err := apiserver.Provider.GetGeneralStats()
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Debugf("[ERROR] failed get general stats from api server %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
argsMap := map[string]interface{}{
|
||||||
|
"apiCallsCount": generalStats["EntriesCount"],
|
||||||
|
"firstAPICallTimestamp": generalStats["FirstEntryTimestamp"],
|
||||||
|
"lastAPICallTimestamp": generalStats["LastEntryTimestamp"],
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sendTelemetry("APICalls", argsMap); err != nil {
|
||||||
|
logger.Log.Debug(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Log.Debugf("successfully reported telemetry of api calls")
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldRunTelemetry() bool {
|
||||||
|
if !config.Config.Telemetry {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if mizu.Branch != "main" && mizu.Branch != "develop" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendTelemetry(telemetryType string, argsMap map[string]interface{}) error {
|
||||||
|
argsMap["telemetryType"] = telemetryType
|
||||||
|
argsMap["component"] = "mizu_cli"
|
||||||
|
argsMap["buildTimestamp"] = mizu.BuildTimestamp
|
||||||
|
argsMap["branch"] = mizu.Branch
|
||||||
|
argsMap["version"] = mizu.SemVer
|
||||||
|
|
||||||
|
if machineId, err := machineid.ProtectedID("mizu"); err == nil {
|
||||||
|
argsMap["machineId"] = machineId
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonValue, _ := json.Marshal(argsMap)
|
||||||
|
|
||||||
|
if resp, err := http.Post(telemetryUrl, "application/json", bytes.NewBuffer(jsonValue)); err != nil {
|
||||||
|
return fmt.Errorf("ERROR: failed sending telemetry, err: %v, response %v", err, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -2,12 +2,14 @@ package uiUtils
|
|||||||
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Black = "\033[1;30m%s\033[0m"
|
Black = "\033[1;30m%s\033[0m"
|
||||||
Red = "\033[1;31m%s\033[0m"
|
Red = "\033[1;31m%s\033[0m"
|
||||||
Green = "\033[1;32m%s\033[0m"
|
Green = "\033[1;32m%s\033[0m"
|
||||||
Yellow = "\033[1;33m%s\033[0m"
|
Yellow = "\033[1;33m%s\033[0m"
|
||||||
Purple = "\033[1;34m%s\033[0m"
|
Purple = "\033[1;34m%s\033[0m"
|
||||||
Magenta = "\033[1;35m%s\033[0m"
|
Magenta = "\033[1;35m%s\033[0m"
|
||||||
Teal = "\033[1;36m%s\033[0m"
|
Teal = "\033[1;36m%s\033[0m"
|
||||||
White = "\033[1;37m%s\033[0m"
|
White = "\033[1;37m%s\033[0m"
|
||||||
)
|
Error = Red
|
||||||
|
Warning = Yellow
|
||||||
|
)
|
||||||
|
|||||||
8
codecov.yml
Normal file
8
codecov.yml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
coverage:
|
||||||
|
status:
|
||||||
|
project:
|
||||||
|
default:
|
||||||
|
threshold: 1%
|
||||||
|
patch:
|
||||||
|
default:
|
||||||
|
enabled: no
|
||||||
15
devops/build-agent-ci.sh
Executable file
15
devops/build-agent-ci.sh
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
GCP_PROJECT=up9-docker-hub
|
||||||
|
REPOSITORY=gcr.io/$GCP_PROJECT
|
||||||
|
SERVER_NAME=mizu
|
||||||
|
GIT_BRANCH=ci
|
||||||
|
|
||||||
|
DOCKER_REPO=$REPOSITORY/$SERVER_NAME/$GIT_BRANCH
|
||||||
|
SEM_VER=${SEM_VER=0.0.0}
|
||||||
|
|
||||||
|
DOCKER_TAGGED_BUILD="$DOCKER_REPO:$SEM_VER"
|
||||||
|
|
||||||
|
echo "building $DOCKER_TAGGED_BUILD"
|
||||||
|
docker build -t ${DOCKER_TAGGED_BUILD} --build-arg SEM_VER=${SEM_VER} --build-arg BUILD_TIMESTAMP=${BUILD_TIMESTAMP} --build-arg GIT_BRANCH=${GIT_BRANCH} --build-arg COMMIT_HASH=${COMMIT_HASH} .
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
SERVER_NAME=mizu
|
|
||||||
GCP_PROJECT=up9-docker-hub
|
GCP_PROJECT=up9-docker-hub
|
||||||
REPOSITORY=gcr.io/$GCP_PROJECT
|
REPOSITORY=gcr.io/$GCP_PROJECT
|
||||||
|
SERVER_NAME=mizu
|
||||||
GIT_BRANCH=$(git branch | grep \* | cut -d ' ' -f2 | tr '[:upper:]' '[:lower:]')
|
GIT_BRANCH=$(git branch | grep \* | cut -d ' ' -f2 | tr '[:upper:]' '[:lower:]')
|
||||||
SEM_VER=${SEM_VER=0.0.0}
|
|
||||||
DOCKER_REPO=$REPOSITORY/$SERVER_NAME/$GIT_BRANCH
|
DOCKER_REPO=$REPOSITORY/$SERVER_NAME/$GIT_BRANCH
|
||||||
|
SEM_VER=${SEM_VER=0.0.0}
|
||||||
|
|
||||||
DOCKER_TAGGED_BUILDS=("$DOCKER_REPO:latest" "$DOCKER_REPO:$SEM_VER")
|
DOCKER_TAGGED_BUILDS=("$DOCKER_REPO:latest" "$DOCKER_REPO:$SEM_VER")
|
||||||
|
|
||||||
if [ "$GIT_BRANCH" = 'develop' -o "$GIT_BRANCH" = 'master' -o "$GIT_BRANCH" = 'main' ]
|
if [ "$GIT_BRANCH" = 'develop' -o "$GIT_BRANCH" = 'master' -o "$GIT_BRANCH" = 'main' ]
|
||||||
@@ -21,6 +23,6 @@ docker build $DOCKER_TAGS_ARGS --build-arg SEM_VER=${SEM_VER} --build-arg BUILD_
|
|||||||
|
|
||||||
for DOCKER_TAG in "${DOCKER_TAGGED_BUILDS[@]}"
|
for DOCKER_TAG in "${DOCKER_TAGGED_BUILDS[@]}"
|
||||||
do
|
do
|
||||||
echo pushing "$DOCKER_TAG"
|
echo pushing "$DOCKER_TAG"
|
||||||
docker push "$DOCKER_TAG"
|
docker push "$DOCKER_TAG"
|
||||||
done
|
done
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user